- Добавил возможность редактирования сообщений
- Улучшил внешний вид страниц. 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;
|
package net.sytes.kashey.sweater.controller;
|
||||||
|
|
||||||
|
import net.sytes.kashey.sweater.domain.Message;
|
||||||
import org.springframework.validation.BindingResult;
|
import org.springframework.validation.BindingResult;
|
||||||
import org.springframework.validation.FieldError;
|
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.Map;
|
||||||
|
import java.util.Objects;
|
||||||
|
import java.util.UUID;
|
||||||
import java.util.stream.Collector;
|
import java.util.stream.Collector;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
@@ -16,4 +22,17 @@ public class ControllerUtils {
|
|||||||
);
|
);
|
||||||
return bindingResult.getFieldErrors().stream().collect(collector);
|
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.bind.annotation.RequestParam;
|
||||||
import org.springframework.web.multipart.MultipartFile;
|
import org.springframework.web.multipart.MultipartFile;
|
||||||
|
|
||||||
import java.io.File;
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Objects;
|
|
||||||
import java.util.UUID;
|
|
||||||
|
|
||||||
@Controller
|
@Controller
|
||||||
public class MainController {
|
public class MainController {
|
||||||
@@ -64,18 +61,7 @@ public class MainController {
|
|||||||
model.mergeAttributes(errorsMap);
|
model.mergeAttributes(errorsMap);
|
||||||
model.addAttribute("message", message);
|
model.addAttribute("message", message);
|
||||||
} else {
|
} else {
|
||||||
|
ControllerUtils.saveFile(message, file, uploadPath);
|
||||||
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);
|
|
||||||
}
|
|
||||||
|
|
||||||
messageRepository.save(message);
|
messageRepository.save(message);
|
||||||
model.addAttribute("message", null);
|
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,11 +6,12 @@ import jakarta.validation.constraints.NotBlank;
|
|||||||
|
|
||||||
import java.io.Serial;
|
import java.io.Serial;
|
||||||
import java.io.Serializable;
|
import java.io.Serializable;
|
||||||
|
import java.util.Objects;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
@Entity
|
@Entity
|
||||||
@Table(name = "usr")
|
@Table(name = "usr")
|
||||||
public class User implements Serializable {
|
public class User implements Serializable {
|
||||||
@Serial
|
@Serial
|
||||||
private static final long serialVersionUID = 1L;
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
@@ -34,6 +35,22 @@ public class User implements Serializable {
|
|||||||
@Enumerated(EnumType.STRING)
|
@Enumerated(EnumType.STRING)
|
||||||
private Set<Role> roles;
|
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() {
|
public User() {
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -101,4 +118,12 @@ public class User implements Serializable {
|
|||||||
public void setActivationCode(String activationCode) {
|
public void setActivationCode(String activationCode) {
|
||||||
this.activationCode = 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() {
|
public String getEmail() {
|
||||||
return user.getEmail();
|
return user.getEmail();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Long getId() {
|
||||||
|
return user.getId();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
<#import "parts/common.ftl" as com>
|
<#import "parts/common.ftl" as com>
|
||||||
|
<#include "parts/security.ftl">
|
||||||
<@com.page>
|
<@com.page>
|
||||||
<h5>Hello, guest</h5>
|
<h5>Hello, <#if user??> ${name} <#else> guest </#if> </h5>
|
||||||
<div>
|
<div>
|
||||||
This is a simple version of Twitter
|
This is a simple version of Twitter
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -7,63 +7,6 @@
|
|||||||
<button type="submit" class="btn btn-primary ml-2">Search</button>
|
<button type="submit" class="btn btn-primary ml-2">Search</button>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
<#include "parts/messageEditor.ftl">
|
||||||
<a class="btn btn-primary" data-toggle="collapse" href="#collapseExample" role="button" aria-expanded="false"
|
<#include "parts/messageList.ftl">
|
||||||
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>
|
|
||||||
</@com.page>
|
</@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,10 +17,15 @@
|
|||||||
<li class="nav-item">
|
<li class="nav-item">
|
||||||
<a class="nav-link" aria-current="page" href="/main">Messages</a>
|
<a class="nav-link" aria-current="page" href="/main">Messages</a>
|
||||||
</li>
|
</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>
|
<#if isAdmin>
|
||||||
<li class="nav-item">
|
<li class="nav-item">
|
||||||
<a class="nav-link" aria-current="page" href="/users">Users</a>
|
<a class="nav-link" aria-current="page" href="/users">Users</a>
|
||||||
</li>
|
</li>
|
||||||
</#if>
|
</#if>
|
||||||
<#if user??>
|
<#if user??>
|
||||||
<li class="nav-item">
|
<li class="nav-item">
|
||||||
@@ -29,7 +34,7 @@
|
|||||||
</#if>
|
</#if>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
<div class="navbar-text mr-3" >${name}</div>
|
<div class="navbar-text mr-3">${name}</div>
|
||||||
<div><#if name != "unknown"> <@l.logout /> </#if></div>
|
<div><#if name != "unknown"> <@l.logout /> </#if></div>
|
||||||
</div>
|
</div>
|
||||||
</nav>
|
</nav>
|
||||||
@@ -7,10 +7,12 @@ known = SPRING_SECURITY_CONTEXT??
|
|||||||
user = SPRING_SECURITY_CONTEXT.authentication.principal
|
user = SPRING_SECURITY_CONTEXT.authentication.principal
|
||||||
name = user.getUsername()
|
name = user.getUsername()
|
||||||
isAdmin = user.isAdmin()
|
isAdmin = user.isAdmin()
|
||||||
|
currentUserId = user.getId()
|
||||||
>
|
>
|
||||||
<#else>
|
<#else>
|
||||||
<#assign
|
<#assign
|
||||||
name = "unknown"
|
name = "unknown"
|
||||||
isAdmin = false
|
isAdmin = false
|
||||||
|
currentUserId = -1
|
||||||
>
|
>
|
||||||
</#if>
|
</#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