- Добавил возможность подписаться на пользователя и отписаться от него.

- Добавил вывод списка подписчиков/подписок.

https://www.youtube.com/watch?v=JpF0MwdJzO4&list=PLU2ftbIeotGpAYRP9Iv2KLIwK36-o_qYk&index=19
This commit is contained in:
Leonid
2025-02-22 14:05:57 +03:00
parent 7b7378e30b
commit 6e4e358dee
10 changed files with 158 additions and 23 deletions

View File

@@ -68,4 +68,38 @@ public class UserController {
customUserDetailsService.updateProfile(customUserDetails.getUser(), password, email);
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();
}
}

View File

@@ -39,20 +39,25 @@ public class UserMessagesController {
model.addAttribute("isCurrentUser", user.equals(currentUser));
model.addAttribute("messages", user.getMessages());
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";
}
@PostMapping("/user-messages/{userId}")
public String getUserMessages(@AuthenticationPrincipal CustomUserDetails customUserDetails,
public String getUserMessage(@AuthenticationPrincipal CustomUserDetails customUserDetails,
@PathVariable(name = "userId") Long userId,
@RequestParam(required = false) Message message,
@ModelAttribute Message message,
@RequestParam String text,
@RequestParam String tag,
@RequestParam("file") MultipartFile file
) throws IOException {
User currentUser = customUserDetails.getUser();
if (message.getAuthor().equals(currentUser)) {
message.setAuthor(customUserDetails.getUser());
if (!ObjectUtils.isEmpty(text)) {
message.setText(text);
}
@@ -63,7 +68,6 @@ public class UserMessagesController {
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.HashSet;
import java.util.Objects;
import java.util.Set;
@@ -38,6 +39,15 @@ public class User implements Serializable {
@OneToMany(mappedBy = "author", cascade = CascadeType.ALL, fetch = FetchType.LAZY)
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
public boolean equals(Object o) {
if (this == o) return true;
@@ -126,4 +136,20 @@ public class User implements Serializable {
public void setMessages(Set<Message> 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;
}
}

View File

@@ -66,6 +66,7 @@ public class CustomUserDetailsService implements UserDetailsService {
}
private void sendMessage(User user) {
if (!ObjectUtils.isEmpty(user.getEmail())) {
String message = String.format(
@@ -133,6 +134,19 @@ public class CustomUserDetailsService implements UserDetailsService {
}
public List<User> 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);
}
}

View File

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

View File

@@ -1,3 +1,4 @@
<#include "security.ftl">
<#macro login path isRegistrationForm=false>
<form action="${path}" method="post" xmlns="http://www.w3.org/1999/html">
<div class="row mb-3">
@@ -74,6 +75,6 @@
<#macro logout>
<form action="/logout" method="post">
<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>
</#macro>

View File

@@ -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">
Message editor
</a>
@@ -30,7 +32,7 @@
</div>
</div>
<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">
<button type="submit" class="btn btn-primary">Save message</button>
</div>

View File

@@ -14,10 +14,10 @@
<li class="nav-item">
<a class="nav-link" aria-current="page" href="/">Home</a>
</li>
<#if user??>
<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>
@@ -34,7 +34,7 @@
</#if>
</ul>
</div>
<div class="navbar-text mr-3">${name}</div>
<div><#if name != "unknown"> <@l.logout /> </#if></div>
<div class="navbar-text mr-3"><#if user??>${name}<#else>Please, login</#if></div>
<@l.logout />
</div>
</nav>

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

View File

@@ -1,5 +1,39 @@
<#import "parts/common.ftl" as com>
<@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>
<#include "parts/messageEditor.ftl">
</#if>