Half Lab5

This commit is contained in:
Данила Мочалов 2023-04-15 23:50:16 +04:00
parent 78a86fb68e
commit dd87a58873
19 changed files with 348 additions and 9 deletions

View File

@ -14,11 +14,23 @@ repositories {
dependencies { dependencies {
implementation 'org.springframework.boot:spring-boot-starter-web' implementation 'org.springframework.boot:spring-boot-starter-web'
testImplementation 'org.springframework.boot:spring-boot-starter-test' implementation 'org.springframework.boot:spring-boot-starter-thymeleaf'
implementation 'org.springframework.boot:spring-boot-devtools'
implementation 'nz.net.ultraq.thymeleaf:thymeleaf-layout-dialect'
implementation 'org.webjars:bootstrap:5.1.3'
implementation 'org.webjars:jquery:3.6.0'
implementation 'org.webjars:font-awesome:6.1.0'
implementation 'org.springframework.boot:spring-boot-starter-data-jpa' implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
implementation 'com.h2database:h2:2.1.210' implementation 'com.h2database:h2:2.1.210'
implementation 'org.hibernate.validator:hibernate-validator' implementation 'org.hibernate.validator:hibernate-validator'
implementation 'org.springdoc:springdoc-openapi-ui:1.6.5' implementation 'org.springdoc:springdoc-openapi-ui:1.6.5'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
} }
tasks.named('test') { tasks.named('test') {

Binary file not shown.

@ -1 +1 @@
Subproject commit 62649c78e6740e7f779a68f9588c91a318d4c0b5 Subproject commit 2bc9e21c22b7fbd6679612d7e6fc1170964cc128

View File

@ -2,10 +2,17 @@ package com.webproglabs.lab1;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.CorsRegistry; import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.ViewControllerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration @Configuration
public class WebConfiguration implements WebMvcConfigurer { public class WebConfiguration implements WebMvcConfigurer {
public static final String REST_API = "/api";
@Override
public void addViewControllers(ViewControllerRegistry registry) {
WebMvcConfigurer.super.addViewControllers(registry);
registry.addViewController("rest-test");
}
@Override @Override
public void addCorsMappings(CorsRegistry registry) { public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**").allowedMethods("*"); registry.addMapping("/**").allowedMethods("*");

View File

@ -1,5 +1,6 @@
package com.webproglabs.lab1.lab34.controller; package com.webproglabs.lab1.lab34.controller;
import com.webproglabs.lab1.WebConfiguration;
import com.webproglabs.lab1.lab34.model.Comment; import com.webproglabs.lab1.lab34.model.Comment;
import com.webproglabs.lab1.lab34.model.Profile; import com.webproglabs.lab1.lab34.model.Profile;
import com.webproglabs.lab1.lab34.services.CommentService; import com.webproglabs.lab1.lab34.services.CommentService;
@ -8,7 +9,7 @@ import org.springframework.web.bind.annotation.*;
import java.util.List; import java.util.List;
@RestController @RestController
@RequestMapping("/comment") @RequestMapping(WebConfiguration.REST_API + "/comment")
public class CommentController { public class CommentController {
private final CommentService commentService; private final CommentService commentService;

View File

@ -1,5 +1,6 @@
package com.webproglabs.lab1.lab34.controller; package com.webproglabs.lab1.lab34.controller;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.webproglabs.lab1.lab34.model.Comment; import com.webproglabs.lab1.lab34.model.Comment;
public class CommentDto { public class CommentDto {
@ -11,6 +12,9 @@ public class CommentDto {
this.text = comment.getText(); this.text = comment.getText();
} }
public Long getId() {return id;} @JsonProperty(access = JsonProperty.Access.READ_ONLY)
public long getId() {
return id;
}
public String getText() {return text;} public String getText() {return text;}
} }

View File

@ -1,5 +1,6 @@
package com.webproglabs.lab1.lab34.controller; package com.webproglabs.lab1.lab34.controller;
import com.webproglabs.lab1.WebConfiguration;
import com.webproglabs.lab1.lab34.services.PostService; import com.webproglabs.lab1.lab34.services.PostService;
import org.springframework.web.bind.annotation.*; import org.springframework.web.bind.annotation.*;
@ -7,7 +8,7 @@ import java.util.ArrayList;
import java.util.List; import java.util.List;
@RestController @RestController
@RequestMapping("/post") @RequestMapping(WebConfiguration.REST_API + "/post")
public class PostController { public class PostController {
private final PostService postService; private final PostService postService;

View File

@ -1,5 +1,6 @@
package com.webproglabs.lab1.lab34.controller; package com.webproglabs.lab1.lab34.controller;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.webproglabs.lab1.lab34.model.Comment; import com.webproglabs.lab1.lab34.model.Comment;
import com.webproglabs.lab1.lab34.model.Post; import com.webproglabs.lab1.lab34.model.Post;
import com.webproglabs.lab1.lab34.model.Profile; import com.webproglabs.lab1.lab34.model.Profile;
@ -12,15 +13,22 @@ public class PostDto {
private String text; private String text;
private List<CommentDto> comments = new ArrayList<>(); private List<CommentDto> comments = new ArrayList<>();
private String authorLogin;
public PostDto(Post post){ public PostDto(Post post){
this.id = post.getId(); this.id = post.getId();
this.text = post.getText(); this.text = post.getText();
for(Comment comment: post.getComments()){ for(Comment comment: post.getComments()){
comments.add(new CommentDto(comment)); comments.add(new CommentDto(comment));
} }
this.authorLogin = post.getAuthor().getLogin();
} }
public Long getId() {return id;} @JsonProperty(access = JsonProperty.Access.READ_ONLY)
public long getId() {
return id;
}
public String getText() {return text;} public String getText() {return text;}
public List<CommentDto> getComments() {return comments;} public List<CommentDto> getComments() {return comments;}
public String getAuthor() {return authorLogin;}
} }

View File

@ -1,5 +1,6 @@
package com.webproglabs.lab1.lab34.controller; package com.webproglabs.lab1.lab34.controller;
import com.webproglabs.lab1.WebConfiguration;
import com.webproglabs.lab1.lab34.services.ProfileService; import com.webproglabs.lab1.lab34.services.ProfileService;
import org.springframework.web.bind.annotation.*; import org.springframework.web.bind.annotation.*;
@ -7,7 +8,7 @@ import java.util.ArrayList;
import java.util.List; import java.util.List;
@RestController @RestController
@RequestMapping("/profile") @RequestMapping(WebConfiguration.REST_API + "/profile")
public class ProfileController { public class ProfileController {
private final ProfileService profileService; private final ProfileService profileService;

View File

@ -1,5 +1,6 @@
package com.webproglabs.lab1.lab34.controller; package com.webproglabs.lab1.lab34.controller;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.webproglabs.lab1.lab34.model.Comment; import com.webproglabs.lab1.lab34.model.Comment;
import com.webproglabs.lab1.lab34.model.Post; import com.webproglabs.lab1.lab34.model.Post;
import com.webproglabs.lab1.lab34.model.Profile; import com.webproglabs.lab1.lab34.model.Profile;
@ -26,7 +27,10 @@ public class ProfileDto {
} }
} }
public Long getId() {return id;} @JsonProperty(access = JsonProperty.Access.READ_ONLY)
public long getId() {
return id;
}
public String getLogin() {return login;} public String getLogin() {return login;}
public String getPassword() {return password;} public String getPassword() {return password;}
public List<CommentDto> getComments() {return comments;} public List<CommentDto> getComments() {return comments;}

View File

@ -0,0 +1,75 @@
package com.webproglabs.lab1.lab34.controller.mvc_controllers;
import com.webproglabs.lab1.lab34.controller.PostDto;
import com.webproglabs.lab1.lab34.controller.ProfileDto;
import com.webproglabs.lab1.lab34.model.Post;
import com.webproglabs.lab1.lab34.services.PostService;
import com.webproglabs.lab1.lab34.services.ProfileService;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.*;
import java.util.ArrayList;
@Controller
@RequestMapping("/feed")
public class FeedMvcController {
private final ProfileService profileService;
private final PostService postService;
public FeedMvcController(ProfileService profileService, PostService postService) {
this.profileService = profileService;
this.postService = postService;
}
@GetMapping
public String getFeedPage(Model model) {
model.addAttribute("profiles", profileService.findAllUsers().stream().map(ProfileDto::new).toList());
return "feed";
}
@GetMapping(value = {"/{id}"})
public String getFeedPageAuthorized(@PathVariable(required = false) Long id, Model model) {
model.addAttribute("profiles", profileService.findAllUsers().stream().map(ProfileDto::new).toList());
model.addAttribute("posts", postService.findAllPosts().stream().map(PostDto::new).toList());
model.addAttribute("selectedProfile", new ProfileDto(profileService.findUser(id)));
return "feedPosts";
}
@GetMapping(value= {"/filter/{id}/"})
public String getFeedPageFiltered(@PathVariable(required = false) Long id, @RequestParam(value="searchField") String searchField, Model model) {
model.addAttribute("profiles", profileService.findAllUsers().stream().map(ProfileDto::new).toList());
model.addAttribute("posts", postService.findFilteredPosts(searchField).stream().map(PostDto::new).toList());
model.addAttribute("selectedProfile", new ProfileDto(profileService.findUser(id)));
return "feedPosts";
}
@PostMapping(value={"/post/{id}/"})
public String createPost(@PathVariable(required = false) Long id, @RequestParam(value="postInputField") String postInputField) {
postService.addPost(postInputField, new ArrayList<>(), id);
return "redirect:/feed/" + id.toString();
}
@PostMapping(value = {"/deletePost/{id}/{authorId}"})
public String deletePost(@PathVariable(required = false) Long id, @PathVariable(required = false) Long authorId) {
postService.deletePost(id);
return "redirect:/feed/" + authorId.toString();
}
@GetMapping(value = {"postModal/{id}/{authorId}"})
public String getPostEditModal(@PathVariable(required = false) Long id,@PathVariable(required = false) Long authorId, Model model) {
model.addAttribute("selectedPost", new PostDto(postService.findPost(id)));
model.addAttribute("profiles", profileService.findAllUsers().stream().map(ProfileDto::new).toList());
model.addAttribute("posts", postService.findAllPosts().stream().map(PostDto::new).toList());
model.addAttribute("selectedProfile", new ProfileDto(profileService.findUser(authorId)));
return "editPostModal";
}
@PostMapping(value = {"editPost/{id}/{authorId}/"})
public String editPost(@PathVariable(required = false) Long id, @PathVariable(required = false) Long authorId, @RequestParam(value="postEditField") String postEditField) {
postService.updatePost(id, postEditField);
return "redirect:/feed/" + authorId.toString();
}
}

View File

@ -0,0 +1,15 @@
package com.webproglabs.lab1.lab34.controller.mvc_controllers;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
@Controller
@RequestMapping("/")
public class ProfileMvcController {
@GetMapping
public String index() {
return "default";
}
}

View File

@ -53,7 +53,7 @@ public class PostService {
} }
@Transactional @Transactional
public Post addPost(String text, List<Comment> comments, Long authorId) { public Post addPost (String text, List<Comment> comments, Long authorId) {
if (!StringUtils.hasText(text)) { if (!StringUtils.hasText(text)) {
throw new IllegalArgumentException("Post data is null or empty"); throw new IllegalArgumentException("Post data is null or empty");
} }

View File

@ -0,0 +1,15 @@
.container-padding {
padding: 10px;
}
.margin-bottom {
margin-bottom: 10px;
}
.button-fixed {
min-width: 120px;
}
.button-sm {
padding: 1px;
}

View File

@ -0,0 +1,4 @@
<svg xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 448 512"><!--! Font Awesome Pro 6.1.1 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) Copyright 2022 Fonticons, Inc. -->
<path d="M448 48V384c-63.09 22.54-82.34 32-119.5 32c-62.82 0-86.6-32-149.3-32C158.6 384 142.6 387.6 128 392.2v-64C142.6 323.6 158.6 320 179.2 320c62.73 0 86.51 32 149.3 32C348.9 352 364.1 349 384 342.7v-208C364.1 141 348.9 144 328.5 144c-62.82 0-86.6-32-149.3-32C128.4 112 104.3 132.6 64 140.7v307.3C64 465.7 49.67 480 32 480S0 465.7 0 448V63.1C0 46.33 14.33 32 31.1 32S64 46.33 64 63.1V76.66C104.3 68.63 128.4 48 179.2 48c62.73 0 86.51 32 149.3 32C365.7 80 384.9 70.54 448 48z"/>
</svg>

After

Width:  |  Height:  |  Size: 727 B

View File

@ -0,0 +1,38 @@
<!DOCTYPE html>
<html lang="ru"
xmlns:th="http://www.thymeleaf.org"
xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout">
<head>
<meta charset="UTF-8"/>
<title>Лабораторная работа 5</title>
<meta name="viewport" content="width=device-width, initial-scale=1"/>
<link rel="icon" href="/favicon.svg">
<script type="text/javascript" src="/webjars/bootstrap/5.1.3/js/bootstrap.bundle.min.js"></script>
<script src="https://code.jquery.com/jquery-3.6.0.min.js" integrity="sha256-/xUj+3OJU5yExlq6GSYGSHk7tPXikynS7ogEvDej/m4=" crossorigin="anonymous"></script>
<link rel="stylesheet" href="/webjars/bootstrap/5.1.3/css/bootstrap.min.css"/>
<link rel="stylesheet" href="/webjars/font-awesome/6.1.0/css/all.min.css"/>
<link rel="stylesheet" href="/css/style.css"/>
</head>
<body>
<div>
<p class='text-center m-3 h3'> Лабораторная работа 5</p>
</div>
<div>
<p class='h4 text-center'>
<a href="/" class="text-decoration-none m-3">Профили</a>
<a href="/feed" class="text-decoration-none m-3">Лента</a>
</p>
</div>
<div >
<div layout:fragment="content"></div>
</div>
</body>
<script type="text/javascript">
window.onload = () => {
$('#postEdit').modal('show');
}
</script>
</html>

View File

@ -0,0 +1,37 @@
<!DOCTYPE html>
<html lang="en"
xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"
layout:decorate="~{feedPosts}" xmlns:th="http://www.w3.org/1999/xhtml">
<head>
</head>
<body>
<div layout:fragment="modalFeed">
<div class="modal fade" id="postEdit" tabindex="-1" role="dialog" aria-labelledby="postEditLabel" aria-hidden="true">
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="postEditLabel">Редактировать пост</h5>
</div>
<form th:action="@{/feed/editPost/{id}/{authorId}/{text} (id=${selectedPost.id}, authorId=${selectedProfile.id}, text=${postEditField} ) }" method="post" class="modal-body text-center">
<p>Текст поста:</p>
<input th:value="${postEditField}" id="postEditField" name="postEditField" type="text" class="mb-2">
<br>
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Закрыть</button>
<button type="submit" class="btn btn-primary" >Сохранить</button>
</form>
</div>
</div>
</div>
</div>
</body>
<script type="text/javascript">
window.onload = () => {
$('#postEdit').modal('show');
}
</script>
</html>

View File

@ -0,0 +1,36 @@
<!DOCTYPE html>
<html lang="en"
xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"
layout:decorate="~{default}">
<head>
</head>
<body>
<div layout:fragment="content">
<div class="text-center">
<div class="dropdown text-center mx-auto w-25 ">
<div class="text-end mt-3">
<button class="btn btn-secondary dropdown-toggle ms-3 mb-3 " type="button" data-bs-toggle="dropdown" aria-expanded="false">
Выбор пользователя
</button>
<ul class="dropdown-menu " >
<li th:each="profile: ${profiles}">
<a class="dropdown-item" th:href="@{/feed/{id}(id=${profile.id})}" th:text="${profile.login}">
</a>
</li>
</ul>
</div>
</div>
<div layout:fragment="contentFeed"></div>
</div>
</div>
</body>
<script type="text/javascript">
window.onload = () => {
$('#postEdit').modal('show');
}
</script>
</html>

View File

@ -0,0 +1,81 @@
<!DOCTYPE html>
<html lang="en"
xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"
layout:decorate="~{feed}" xmlns:th="http://www.w3.org/1999/xhtml">
<head>
</head>
<body>
<div layout:fragment="contentFeed">
<div class='h3 mb-3 mx-auto w-25'>
<p class="text-end" th:text="${selectedProfile.login}"></p>
</div>
<div class='h3 m-3 d-flex justify-content-between text-center mx-auto w-25'>
Лента
<button type='button' class="btn btn-primary ms-5 mb-3 " data-bs-toggle="modal" data-bs-target="#postCreate">
Добавить новый пост
</button>
</div>
<form th:action="@{/feed/filter/{id}/{text} (id=${selectedProfile.id}, text=${searchField}) }" method="get">
<input th:value="${searchField}" id="searchField" name="searchField" type="text" class="mb-2" style="width: 20%" placeholder="Поиск...">
<button type='submit' class="btn btn-primary mb-2" style="width: 5%">
Найти
</button>
</form>
<div th:each="post: ${posts}" class="text-center mx-auto w-25 mb-3">
<div class="border p-2">
<p th:text="${post.text}" class="h4 text-start"></p>
<div class="d-flex justify-content-between fst-italic">
<div>
Автор:
<a th:text="${post.getAuthor()}" class="text-start fst-italic" ></a>
</div>
<div th:if="${selectedProfile.getLogin() == post.getAuthor()}" class="d-flex justify-content-between fst-italic">
<form th:action="@{/feed/deletePost/{id}/{authorId} (id=${post.id}, authorId=${selectedProfile.id})}" method="post" >
<button type="submit" class="btn btn-danger me-1 mb-1 ms-2" >
<i class="fa fa-trash" aria-hidden="true"></i>
</button>
</form>
<form th:action="@{/feed/postModal/{id}/{authorId} (id=${post.id}, authorId=${selectedProfile.id})}" method="get">
<button type="submit" class="btn btn-warning mb-1" data-bs-toggle="modal" data-bs-target="#postEdit" >
<i class="fa fa-pencil" aria-hidden="true"></i>
</button>
</form>
</div>
</div>
</div>
</div>
<div class="modal fade" id="postCreate" tabindex="-1" role="dialog" aria-labelledby="postCreateLabel" aria-hidden="true">
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="postCreateLabel">Создать пост</h5>
</div>
<form th:action="@{/feed/post/{id}/{text} (id=${selectedProfile.id}, text=${postInputField}) }" method="post" class="modal-body text-center">
<p>Текст поста:</p>
<input th:value="${postInputField}" id="postInputField" name="postInputField" type="text" class="mb-2">
<br>
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Закрыть</button>
<button type="submit" class="btn btn-primary" >Сохранить</button>
</form>
</div>
</div>
</div>
<div layout:fragment="modalFeed"></div>
</div>
</body>
<script type="text/javascript">
window.onload = () => {
$('#postEdit').modal('show');
}
</script>
</html>