- Добавил возможность редактирования сообщений

- Улучшил внешний вид страниц.

https://www.youtube.com/watch?v=-gdDklUu03k&list=PLU2ftbIeotGpAYRP9Iv2KLIwK36-o_qYk&index=20
This commit is contained in:
Leonid
2025-02-20 11:08:31 +03:00
parent fe4932fff5
commit 7b7378e30b
12 changed files with 205 additions and 80 deletions

View File

@@ -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);
}
}
}

View File

@@ -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);
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -65,4 +65,8 @@ public class CustomUserDetails implements UserDetails {
public String getEmail() {
return user.getEmail();
}
public Long getId() {
return user.getId();
}
}

View File

@@ -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>

View File

@@ -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>

View 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>

View 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>

View File

@@ -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>

View File

@@ -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>

View 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>