- Добавил возможность редактирования сообщений
- Улучшил внешний вид страниц. https://www.youtube.com/watch?v=-gdDklUu03k&list=PLU2ftbIeotGpAYRP9Iv2KLIwK36-o_qYk&index=20
This commit is contained in:
@@ -1,9 +1,15 @@
|
||||
package net.sytes.kashey.sweater.controller;
|
||||
|
||||
import net.sytes.kashey.sweater.domain.Message;
|
||||
import org.springframework.validation.BindingResult;
|
||||
import org.springframework.validation.FieldError;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.UUID;
|
||||
import java.util.stream.Collector;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@@ -16,4 +22,17 @@ public class ControllerUtils {
|
||||
);
|
||||
return bindingResult.getFieldErrors().stream().collect(collector);
|
||||
}
|
||||
|
||||
static void saveFile(Message message, MultipartFile file, String uploadPath) throws IOException {
|
||||
if (file != null && !Objects.requireNonNull(file.getOriginalFilename()).isEmpty()) {
|
||||
File uploadDir = new File(uploadPath); // Директория на сервере, куда помещаем пришедший файл
|
||||
if (!uploadDir.exists()) {
|
||||
uploadDir.mkdirs();
|
||||
}
|
||||
String uuidFile = UUID.randomUUID().toString();
|
||||
String resultFilename = uuidFile + "." + file.getOriginalFilename(); // случайный уид + имя файла с клиента
|
||||
file.transferTo(new File(uploadPath + "/" + resultFilename)); // помещаем переименованный файл с клиента в директорию сервера
|
||||
message.setFilename(resultFilename);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -14,11 +14,8 @@ import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.UUID;
|
||||
|
||||
@Controller
|
||||
public class MainController {
|
||||
@@ -64,18 +61,7 @@ public class MainController {
|
||||
model.mergeAttributes(errorsMap);
|
||||
model.addAttribute("message", message);
|
||||
} else {
|
||||
|
||||
if (file != null && !Objects.requireNonNull(file.getOriginalFilename()).isEmpty()) {
|
||||
File uploadDir = new File(uploadPath); // Директория на сервере, куда помещаем пришедший файл
|
||||
if (!uploadDir.exists()) {
|
||||
uploadDir.mkdirs();
|
||||
}
|
||||
String uuidFile = UUID.randomUUID().toString();
|
||||
String resultFilename = uuidFile + "." + file.getOriginalFilename(); // случайный уид + имя файла с клиента
|
||||
file.transferTo(new File(uploadPath + "/" + resultFilename)); // помещаем переименованный файл с клиента в директорию сервера
|
||||
message.setFilename(resultFilename);
|
||||
}
|
||||
|
||||
ControllerUtils.saveFile(message, file, uploadPath);
|
||||
messageRepository.save(message);
|
||||
model.addAttribute("message", null);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,69 @@
|
||||
package net.sytes.kashey.sweater.controller;
|
||||
|
||||
|
||||
import net.sytes.kashey.sweater.domain.Message;
|
||||
import net.sytes.kashey.sweater.domain.User;
|
||||
import net.sytes.kashey.sweater.repository.MessageRepository;
|
||||
import net.sytes.kashey.sweater.security.CustomUserDetails;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.security.core.annotation.AuthenticationPrincipal;
|
||||
import org.springframework.stereotype.Controller;
|
||||
import org.springframework.ui.Model;
|
||||
import org.springframework.util.ObjectUtils;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
@Controller
|
||||
@RequestMapping("/")
|
||||
public class UserMessagesController {
|
||||
|
||||
private final MessageRepository messageRepository;
|
||||
|
||||
@Value("${upload.path}")
|
||||
private String uploadPath;
|
||||
|
||||
public UserMessagesController(MessageRepository messageRepository) {
|
||||
|
||||
this.messageRepository = messageRepository;
|
||||
}
|
||||
|
||||
@GetMapping("/user-messages/{user}")
|
||||
public String getUserMessages(@PathVariable User user,
|
||||
@RequestParam(required = false) Message message,
|
||||
@AuthenticationPrincipal CustomUserDetails customUserDetails,
|
||||
Model model) {
|
||||
|
||||
User currentUser = customUserDetails.getUser();
|
||||
model.addAttribute("isCurrentUser", user.equals(currentUser));
|
||||
model.addAttribute("messages", user.getMessages());
|
||||
model.addAttribute("message", message);
|
||||
return "user_messages";
|
||||
}
|
||||
|
||||
@PostMapping("/user-messages/{userId}")
|
||||
public String getUserMessages(@AuthenticationPrincipal CustomUserDetails customUserDetails,
|
||||
@PathVariable(name = "userId") Long userId,
|
||||
@RequestParam(required = false) Message message,
|
||||
@RequestParam String text,
|
||||
@RequestParam String tag,
|
||||
@RequestParam("file") MultipartFile file
|
||||
) throws IOException {
|
||||
|
||||
User currentUser = customUserDetails.getUser();
|
||||
if (message.getAuthor().equals(currentUser)) {
|
||||
if (!ObjectUtils.isEmpty(text)) {
|
||||
message.setText(text);
|
||||
}
|
||||
|
||||
if (!ObjectUtils.isEmpty(tag)) {
|
||||
message.setTag(tag);
|
||||
}
|
||||
|
||||
ControllerUtils.saveFile(message, file, uploadPath);
|
||||
messageRepository.save(message);
|
||||
}
|
||||
return "redirect:/user-messages/" + userId;
|
||||
}
|
||||
}
|
||||
@@ -6,6 +6,7 @@ import jakarta.validation.constraints.NotBlank;
|
||||
|
||||
import java.io.Serial;
|
||||
import java.io.Serializable;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
|
||||
@Entity
|
||||
@@ -34,6 +35,22 @@ public class User implements Serializable {
|
||||
@Enumerated(EnumType.STRING)
|
||||
private Set<Role> roles;
|
||||
|
||||
@OneToMany(mappedBy = "author", cascade = CascadeType.ALL, fetch = FetchType.LAZY)
|
||||
private Set<Message> messages;
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (o == null || getClass() != o.getClass()) return false;
|
||||
User user = (User) o;
|
||||
return Objects.equals(id, user.id);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(id);
|
||||
}
|
||||
|
||||
public User() {
|
||||
}
|
||||
|
||||
@@ -101,4 +118,12 @@ public class User implements Serializable {
|
||||
public void setActivationCode(String activationCode) {
|
||||
this.activationCode = activationCode;
|
||||
}
|
||||
|
||||
public Set<Message> getMessages() {
|
||||
return messages;
|
||||
}
|
||||
|
||||
public void setMessages(Set<Message> messages) {
|
||||
this.messages = messages;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -65,4 +65,8 @@ public class CustomUserDetails implements UserDetails {
|
||||
public String getEmail() {
|
||||
return user.getEmail();
|
||||
}
|
||||
|
||||
public Long getId() {
|
||||
return user.getId();
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
<#import "parts/common.ftl" as com>
|
||||
<#include "parts/security.ftl">
|
||||
<@com.page>
|
||||
<h5>Hello, guest</h5>
|
||||
<h5>Hello, <#if user??> ${name} <#else> guest </#if> </h5>
|
||||
<div>
|
||||
This is a simple version of Twitter
|
||||
</div>
|
||||
|
||||
@@ -7,63 +7,6 @@
|
||||
<button type="submit" class="btn btn-primary ml-2">Search</button>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<a class="btn btn-primary" data-toggle="collapse" href="#collapseExample" role="button" aria-expanded="false"
|
||||
aria-controls="collapseExample">
|
||||
Click to add message
|
||||
</a>
|
||||
<div class="collapse <#if message??>show</#if>" id="collapseExample">
|
||||
<div class="form-group mt-3">
|
||||
<form method="post" enctype="multipart/form-data">
|
||||
<div class="form-group">
|
||||
<input type="text" class="form-control ${(textError??)?string('is-invalid', '')}"
|
||||
value="<#if message??>${message.text}</#if>" name="text" placeholder="Text" />
|
||||
<#if textError??>
|
||||
<div class="invalid-feedback">
|
||||
${textError}
|
||||
</div>
|
||||
</#if>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<input type="text" class="form-control ${(tagError??)?string('is-invalid', '')}"
|
||||
value="<#if message??>${message.tag}</#if>" name="tag" placeholder="Tag">
|
||||
<#if tagError??>
|
||||
<div class="invalid-feedback">
|
||||
${tagError}
|
||||
</div>
|
||||
</#if>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<div class="custom-file">
|
||||
<input type="file" name="file" id="customFile">
|
||||
<label class="custom-file-label" for="customFile">Choose file</label>
|
||||
</div>
|
||||
</div>
|
||||
<input type="hidden" name="_csrf" value="${_csrf.token}"/>
|
||||
<div class="form-group">
|
||||
<button type="submit" class="btn btn-primary">Add message</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-columns">
|
||||
<#list messages as message>
|
||||
<div>
|
||||
<div>
|
||||
<#if message.filename??>
|
||||
<img src="/img/${message.filename}" class="card-img-top">
|
||||
</#if>
|
||||
</div>
|
||||
<div class="m-2">
|
||||
<span>${message.text}</span>
|
||||
<i>${message.tag}</i>
|
||||
</div>
|
||||
<div class="card-footer">
|
||||
${message.authorName}
|
||||
</div>
|
||||
</div>
|
||||
<#else>
|
||||
No messages found
|
||||
</#list>
|
||||
</div>
|
||||
<#include "parts/messageEditor.ftl">
|
||||
<#include "parts/messageList.ftl">
|
||||
</@com.page>
|
||||
39
src/main/resources/templates/parts/messageEditor.ftl
Normal file
39
src/main/resources/templates/parts/messageEditor.ftl
Normal file
@@ -0,0 +1,39 @@
|
||||
<a class="btn btn-primary mb-3" data-toggle="collapse" href="#collapseExample" role="button" aria-expanded="false"
|
||||
aria-controls="collapseExample">
|
||||
Message editor
|
||||
</a>
|
||||
<div class="collapse <#if message??>show</#if>" id="collapseExample">
|
||||
<div class="form-group mt-3">
|
||||
<form method="post" enctype="multipart/form-data">
|
||||
<div class="form-group">
|
||||
<input type="text" class="form-control ${(textError??)?string('is-invalid', '')}"
|
||||
value="<#if message??>${message.text}</#if>" name="text" placeholder="Text"/>
|
||||
<#if textError??>
|
||||
<div class="invalid-feedback">
|
||||
${textError}
|
||||
</div>
|
||||
</#if>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<input type="text" class="form-control ${(tagError??)?string('is-invalid', '')}"
|
||||
value="<#if message??>${message.tag}</#if>" name="tag" placeholder="Tag">
|
||||
<#if tagError??>
|
||||
<div class="invalid-feedback">
|
||||
${tagError}
|
||||
</div>
|
||||
</#if>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<div class="custom-file">
|
||||
<input type="file" name="file" id="customFile">
|
||||
<label class="custom-file-label" for="customFile">Choose file</label>
|
||||
</div>
|
||||
</div>
|
||||
<input type="hidden" name="_csrf" value="${_csrf.token}"/>
|
||||
<input type="hidden" name="message-id" value=<#if message??>${message.id}</#if>/>
|
||||
<div class="form-group">
|
||||
<button type="submit" class="btn btn-primary">Save message</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
25
src/main/resources/templates/parts/messageList.ftl
Normal file
25
src/main/resources/templates/parts/messageList.ftl
Normal file
@@ -0,0 +1,25 @@
|
||||
<#include "security.ftl">
|
||||
|
||||
<div class="row row-cols-1 row-cols-md-3 g-4">
|
||||
<#list messages as message>
|
||||
<div class="col">
|
||||
<div class="card h-100"> <!-- h-100 делает карточки одинаковой высоты -->
|
||||
<#if message.filename??>
|
||||
<img src="/img/${message.filename}" class="card-img-top">
|
||||
</#if>
|
||||
<div class="card-body">
|
||||
<span>${message.text}</span><br>
|
||||
<i>#${message.tag}</i>
|
||||
</div>
|
||||
<div class="card-footer d-flex justify-content-between">
|
||||
<a href="/user-messages/${message.author.id}"> ${message.authorName} </a>
|
||||
<#if message.author.id == currentUserId>
|
||||
<a class="btn btn-primary btn-sm" href="/user-messages/${message.author.id}?message=${message.id}">Edit</a>
|
||||
</#if>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<#else>
|
||||
<p>No messages found</p>
|
||||
</#list>
|
||||
</div>
|
||||
@@ -17,6 +17,11 @@
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" aria-current="page" href="/main">Messages</a>
|
||||
</li>
|
||||
<#if user??>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" aria-current="page" href="/user-messages/${currentUserId}">My messages</a>
|
||||
</li>
|
||||
</#if>
|
||||
<#if isAdmin>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" aria-current="page" href="/users">Users</a>
|
||||
|
||||
@@ -7,10 +7,12 @@ known = SPRING_SECURITY_CONTEXT??
|
||||
user = SPRING_SECURITY_CONTEXT.authentication.principal
|
||||
name = user.getUsername()
|
||||
isAdmin = user.isAdmin()
|
||||
currentUserId = user.getId()
|
||||
>
|
||||
<#else>
|
||||
<#assign
|
||||
name = "unknown"
|
||||
isAdmin = false
|
||||
currentUserId = -1
|
||||
>
|
||||
</#if>
|
||||
7
src/main/resources/templates/user_messages.ftl
Normal file
7
src/main/resources/templates/user_messages.ftl
Normal file
@@ -0,0 +1,7 @@
|
||||
<#import "parts/common.ftl" as com>
|
||||
<@com.page>
|
||||
<#if isCurrentUser>
|
||||
<#include "parts/messageEditor.ftl">
|
||||
</#if>
|
||||
<#include "parts/messageList.ftl">
|
||||
</@com.page>
|
||||
Reference in New Issue
Block a user