- Добавил возможность подписаться на пользователя и отписаться от него.
- Добавил вывод списка подписчиков/подписок. https://www.youtube.com/watch?v=JpF0MwdJzO4&list=PLU2ftbIeotGpAYRP9Iv2KLIwK36-o_qYk&index=19
This commit is contained in:
@@ -68,4 +68,38 @@ public class UserController {
|
|||||||
customUserDetailsService.updateProfile(customUserDetails.getUser(), password, email);
|
customUserDetailsService.updateProfile(customUserDetails.getUser(), password, email);
|
||||||
return "redirect:/users/profile";
|
return "redirect:/users/profile";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@GetMapping("/{type}/{user}/list")
|
||||||
|
public String getSubscribtions(@PathVariable User user,
|
||||||
|
@PathVariable String type,
|
||||||
|
Model model) {
|
||||||
|
model.addAttribute("userChannel", user);
|
||||||
|
model.addAttribute("type", type);
|
||||||
|
|
||||||
|
if ("subscribers".equals(type)) {
|
||||||
|
model.addAttribute("users", user.getSubscribers());
|
||||||
|
} else if ("subscriptions".equals(type)) {
|
||||||
|
model.addAttribute("users", user.getSubscribtions());
|
||||||
|
}
|
||||||
|
|
||||||
|
return "subscriptions";
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping("/subscribe/{user}")
|
||||||
|
public String subscribe(@AuthenticationPrincipal CustomUserDetails customUserDetails,
|
||||||
|
@PathVariable User user) {
|
||||||
|
|
||||||
|
User currentUser = customUserDetails.getUser();
|
||||||
|
customUserDetailsService.subscribe(currentUser, user);
|
||||||
|
return "redirect:/user-messages/" + user.getId();
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping("/unsubscribe/{user}")
|
||||||
|
public String unsubscribe(@AuthenticationPrincipal CustomUserDetails customUserDetails,
|
||||||
|
@PathVariable User user) {
|
||||||
|
|
||||||
|
User currentUser = customUserDetails.getUser();
|
||||||
|
customUserDetailsService.unsubscribe(currentUser, user);
|
||||||
|
return "redirect:/user-messages/" + user.getId();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -39,20 +39,25 @@ public class UserMessagesController {
|
|||||||
model.addAttribute("isCurrentUser", user.equals(currentUser));
|
model.addAttribute("isCurrentUser", user.equals(currentUser));
|
||||||
model.addAttribute("messages", user.getMessages());
|
model.addAttribute("messages", user.getMessages());
|
||||||
model.addAttribute("message", message);
|
model.addAttribute("message", message);
|
||||||
|
model.addAttribute("userChannel", user);
|
||||||
|
model.addAttribute("subscribersCount", user.getSubscribers().size());
|
||||||
|
model.addAttribute("subscriptionsCount", user.getSubscribtions().size());
|
||||||
|
model.addAttribute("isSubscriber", user.getSubscribers().contains(currentUser));
|
||||||
|
|
||||||
return "user_messages";
|
return "user_messages";
|
||||||
}
|
}
|
||||||
|
|
||||||
@PostMapping("/user-messages/{userId}")
|
@PostMapping("/user-messages/{userId}")
|
||||||
public String getUserMessages(@AuthenticationPrincipal CustomUserDetails customUserDetails,
|
public String getUserMessage(@AuthenticationPrincipal CustomUserDetails customUserDetails,
|
||||||
@PathVariable(name = "userId") Long userId,
|
@PathVariable(name = "userId") Long userId,
|
||||||
@RequestParam(required = false) Message message,
|
@ModelAttribute Message message,
|
||||||
@RequestParam String text,
|
@RequestParam String text,
|
||||||
@RequestParam String tag,
|
@RequestParam String tag,
|
||||||
@RequestParam("file") MultipartFile file
|
@RequestParam("file") MultipartFile file
|
||||||
) throws IOException {
|
) throws IOException {
|
||||||
|
|
||||||
User currentUser = customUserDetails.getUser();
|
message.setAuthor(customUserDetails.getUser());
|
||||||
if (message.getAuthor().equals(currentUser)) {
|
|
||||||
if (!ObjectUtils.isEmpty(text)) {
|
if (!ObjectUtils.isEmpty(text)) {
|
||||||
message.setText(text);
|
message.setText(text);
|
||||||
}
|
}
|
||||||
@@ -63,7 +68,6 @@ public class UserMessagesController {
|
|||||||
|
|
||||||
ControllerUtils.saveFile(message, file, uploadPath);
|
ControllerUtils.saveFile(message, file, uploadPath);
|
||||||
messageRepository.save(message);
|
messageRepository.save(message);
|
||||||
}
|
|
||||||
return "redirect:/user-messages/" + userId;
|
return "redirect:/user-messages/" + userId;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -6,6 +6,7 @@ import jakarta.validation.constraints.NotBlank;
|
|||||||
|
|
||||||
import java.io.Serial;
|
import java.io.Serial;
|
||||||
import java.io.Serializable;
|
import java.io.Serializable;
|
||||||
|
import java.util.HashSet;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
@@ -38,6 +39,15 @@ public class User implements Serializable {
|
|||||||
@OneToMany(mappedBy = "author", cascade = CascadeType.ALL, fetch = FetchType.LAZY)
|
@OneToMany(mappedBy = "author", cascade = CascadeType.ALL, fetch = FetchType.LAZY)
|
||||||
private Set<Message> messages;
|
private Set<Message> messages;
|
||||||
|
|
||||||
|
@ManyToMany
|
||||||
|
@JoinTable(name = "user_subscriptions", joinColumns = { @JoinColumn(name = "channel_id")},
|
||||||
|
inverseJoinColumns = { @JoinColumn(name = "subscriber_id")})
|
||||||
|
private Set<User> subscribers = new HashSet<>();
|
||||||
|
|
||||||
|
@ManyToMany
|
||||||
|
@JoinTable(name = "user_subscriptions", joinColumns = { @JoinColumn(name = "subscriber_id")},
|
||||||
|
inverseJoinColumns = { @JoinColumn(name = "channel_id")})
|
||||||
|
private Set<User> subscribtions = new HashSet<>();
|
||||||
@Override
|
@Override
|
||||||
public boolean equals(Object o) {
|
public boolean equals(Object o) {
|
||||||
if (this == o) return true;
|
if (this == o) return true;
|
||||||
@@ -126,4 +136,20 @@ public class User implements Serializable {
|
|||||||
public void setMessages(Set<Message> messages) {
|
public void setMessages(Set<Message> messages) {
|
||||||
this.messages = messages;
|
this.messages = messages;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Set<User> getSubscribers() {
|
||||||
|
return subscribers;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setSubscribers(Set<User> subscribers) {
|
||||||
|
this.subscribers = subscribers;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Set<User> getSubscribtions() {
|
||||||
|
return subscribtions;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setSubscribtions(Set<User> subscribtions) {
|
||||||
|
this.subscribtions = subscribtions;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -66,6 +66,7 @@ public class CustomUserDetailsService implements UserDetailsService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void sendMessage(User user) {
|
private void sendMessage(User user) {
|
||||||
|
|
||||||
if (!ObjectUtils.isEmpty(user.getEmail())) {
|
if (!ObjectUtils.isEmpty(user.getEmail())) {
|
||||||
|
|
||||||
String message = String.format(
|
String message = String.format(
|
||||||
@@ -133,6 +134,19 @@ public class CustomUserDetailsService implements UserDetailsService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public List<User> findAll() {
|
public List<User> findAll() {
|
||||||
|
|
||||||
return userRepository.findAll();
|
return userRepository.findAll();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void subscribe(User currentUser, User user) {
|
||||||
|
|
||||||
|
user.getSubscribers().add(currentUser);
|
||||||
|
userRepository.save(user);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void unsubscribe(User currentUser, User user) {
|
||||||
|
|
||||||
|
user.getSubscribers().remove(currentUser);
|
||||||
|
userRepository.save(user);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,8 @@
|
|||||||
|
create table if not exists user_subscriptions
|
||||||
|
(
|
||||||
|
channel_id bigint not null references usr,
|
||||||
|
subscriber_id bigint not null references usr,
|
||||||
|
primary key (channel_id, subscriber_id),
|
||||||
|
FOREIGN KEY (channel_id) REFERENCES usr(id),
|
||||||
|
FOREIGN KEY (subscriber_id) REFERENCES usr(id)
|
||||||
|
) engine = InnoDB;
|
||||||
@@ -1,3 +1,4 @@
|
|||||||
|
<#include "security.ftl">
|
||||||
<#macro login path isRegistrationForm=false>
|
<#macro login path isRegistrationForm=false>
|
||||||
<form action="${path}" method="post" xmlns="http://www.w3.org/1999/html">
|
<form action="${path}" method="post" xmlns="http://www.w3.org/1999/html">
|
||||||
<div class="row mb-3">
|
<div class="row mb-3">
|
||||||
@@ -74,6 +75,6 @@
|
|||||||
<#macro logout>
|
<#macro logout>
|
||||||
<form action="/logout" method="post">
|
<form action="/logout" method="post">
|
||||||
<input type="hidden" name="_csrf" value="${_csrf.token}"/>
|
<input type="hidden" name="_csrf" value="${_csrf.token}"/>
|
||||||
<button type="submit" class="btn btn-primary">Sign Out</button>
|
<button class="btn btn-primary" type="submit"><#if user??>Sign Out<#else>Log in</#if></button>
|
||||||
</form>
|
</form>
|
||||||
</#macro>
|
</#macro>
|
||||||
@@ -1,4 +1,6 @@
|
|||||||
<a class="btn btn-primary mb-3" data-toggle="collapse" href="#collapseExample" role="button" aria-expanded="false"
|
<#include "security.ftl">
|
||||||
|
|
||||||
|
<a class="btn btn-primary mb-2" data-toggle="collapse" href="#collapseExample" role="button" aria-expanded="false"
|
||||||
aria-controls="collapseExample">
|
aria-controls="collapseExample">
|
||||||
Message editor
|
Message editor
|
||||||
</a>
|
</a>
|
||||||
@@ -30,7 +32,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<input type="hidden" name="_csrf" value="${_csrf.token}"/>
|
<input type="hidden" name="_csrf" value="${_csrf.token}"/>
|
||||||
<input type="hidden" name="message-id" value=<#if message??>${message.id}</#if>/>
|
<input type="hidden" name="id" value="<#if message??>${message.id}</#if>" />
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<button type="submit" class="btn btn-primary">Save message</button>
|
<button type="submit" class="btn btn-primary">Save message</button>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -14,10 +14,10 @@
|
|||||||
<li class="nav-item">
|
<li class="nav-item">
|
||||||
<a class="nav-link" aria-current="page" href="/">Home</a>
|
<a class="nav-link" aria-current="page" href="/">Home</a>
|
||||||
</li>
|
</li>
|
||||||
|
<#if user??>
|
||||||
<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">
|
<li class="nav-item">
|
||||||
<a class="nav-link" aria-current="page" href="/user-messages/${currentUserId}">My messages</a>
|
<a class="nav-link" aria-current="page" href="/user-messages/${currentUserId}">My messages</a>
|
||||||
</li>
|
</li>
|
||||||
@@ -34,7 +34,7 @@
|
|||||||
</#if>
|
</#if>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
<div class="navbar-text mr-3">${name}</div>
|
<div class="navbar-text mr-3"><#if user??>${name}<#else>Please, login</#if></div>
|
||||||
<div><#if name != "unknown"> <@l.logout /> </#if></div>
|
<@l.logout />
|
||||||
</div>
|
</div>
|
||||||
</nav>
|
</nav>
|
||||||
12
src/main/resources/templates/subscriptions.ftl
Normal file
12
src/main/resources/templates/subscriptions.ftl
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
<#import "parts/common.ftl" as com>
|
||||||
|
<@com.page>
|
||||||
|
<h3>${userChannel.username}</h3>
|
||||||
|
<div>${type}</div>
|
||||||
|
<ul class="list-group">
|
||||||
|
<#list users as user>
|
||||||
|
<li class="list-group-item">
|
||||||
|
<a href="/user-messages/${user.id}">${user.getUsername()}</a>
|
||||||
|
</li>
|
||||||
|
</#list>
|
||||||
|
</ul>
|
||||||
|
</@com.page>
|
||||||
@@ -1,5 +1,39 @@
|
|||||||
<#import "parts/common.ftl" as com>
|
<#import "parts/common.ftl" as com>
|
||||||
<@com.page>
|
<@com.page>
|
||||||
|
<h3>${userChannel.username}</h3>
|
||||||
|
<#if !isCurrentUser>
|
||||||
|
<div class="form-group">
|
||||||
|
<#if isSubscriber>
|
||||||
|
<a class="btn btn-info" href="/users/unsubscribe/${userChannel.id}">Unsubscibe</a>
|
||||||
|
<#else>
|
||||||
|
<a class="btn btn-info" href="/users/subscribe/${userChannel.id}">Subscribe</a>
|
||||||
|
</#if>
|
||||||
|
</div>
|
||||||
|
</#if>
|
||||||
|
<div class="container my-2">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col">
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-body">
|
||||||
|
<div class="card-title">Subscriptions</div>
|
||||||
|
<h3 class="card-text">
|
||||||
|
<a href="/users/subscriptions/${userChannel.id}/list">${subscriptionsCount}</a>
|
||||||
|
</h3>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col">
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-body">
|
||||||
|
<div class="card-title">Subscribers</div>
|
||||||
|
<h3 class="card-text">
|
||||||
|
<a href="/users/subscribers/${userChannel.id}/list">${subscribersCount}</a>
|
||||||
|
</h3>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<#if isCurrentUser>
|
<#if isCurrentUser>
|
||||||
<#include "parts/messageEditor.ftl">
|
<#include "parts/messageEditor.ftl">
|
||||||
</#if>
|
</#if>
|
||||||
|
|||||||
Reference in New Issue
Block a user