Profile page, friend requests

This commit is contained in:
Michael Wain 2024-02-27 03:53:21 +03:00
parent fbcaa886c1
commit 4a67bac3d9
10 changed files with 177 additions and 6 deletions

View File

@ -169,6 +169,15 @@ public class APIController {
return ResponseEntity.ok().build();
}
@PostMapping("/api/v1/friends/follow")
public ResponseEntity<String> followFriend( @RequestParam("userId") Long friendId ) {
if( userService.findById(friendId) == null ) return ResponseEntity.badRequest().build();
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
Long userId = userService.findByUsername(((org.springframework.security.core.userdetails.User) auth.getPrincipal()).getUsername()).getId();
friendService.followUser(userId, friendId);
return ResponseEntity.ok().build();
}
@PostMapping("/async/notify/get/")
@ResponseBody
public DeferredResult<LongPollResult> getNotify(@RequestParam("last_chat_id") Long last_chat_id,

View File

@ -2,6 +2,7 @@ package com.alterdekim.game.controller;
import com.alterdekim.game.dto.AuthApiObject;
import com.alterdekim.game.dto.FriendPageResult;
import com.alterdekim.game.dto.SelectOption;
import com.alterdekim.game.entities.Image;
import com.alterdekim.game.entities.User;
import com.alterdekim.game.repository.ImageRepository;
@ -16,10 +17,15 @@ import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.multipart.MultipartFile;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
@Slf4j
@Controller
public class StaticController {
@ -65,9 +71,30 @@ public class StaticController {
return "friends";
}
@GetMapping("/profile/{id}")
public String profilePage(@PathVariable("id") Long id, Model model) {
AuthenticationUtil.authProfile(model, userService);
User u = userService.findById(id);
model.addAttribute("page_user", new FriendPageResult("", "background-image: url(\"/image/store/"+u.getAvatarId()+"\");", u.getUsername(), u.getId(), u.getDisplayName()));
return "profile";
}
@GetMapping("/settings")
public String settingsPage(Model model) {
Long userId = AuthenticationUtil.authProfile(model, userService).getId();
User u = AuthenticationUtil.authProfile(model, userService);
List<SelectOption> options = new ArrayList<>();
options.add(new SelectOption("she/her", true));
options.add(new SelectOption("he/him", false));
options.add(new SelectOption("they/them", false));
options.add(new SelectOption("idc", false));
String p = u.getPronouns();
options = options.stream().map(o -> {
if(p.equals(o.getText())) {
return new SelectOption(o.getText(), true);
}
return new SelectOption(o.getText(), false);
}).collect(Collectors.toList());
model.addAttribute("options", options);
return "settings";
}

View File

@ -0,0 +1,11 @@
package com.alterdekim.game.dto;
import lombok.AllArgsConstructor;
import lombok.Getter;
@AllArgsConstructor
@Getter
public class SelectOption {
private String text;
private Boolean selected;
}

View File

@ -20,4 +20,12 @@ public interface FriendRepository extends JpaRepository<FriendStatus, Long> {
@Modifying
@Query(value = "DELETE FROM FriendStatus f WHERE ((f.firstUserId = :userId AND f.secondUserId = :friendId) OR (f.firstUserId = :friendId AND f.secondUserId = :userId)) AND f.status = 2")
void removeFriend(@Param("userId") Long userId, @Param("friendId") Long friendId);
@Query(value = "SELECT f FROM FriendStatus f WHERE ((f.firstUserId = :userId AND f.secondUserId = :friendId) OR (f.firstUserId = :friendId AND f.secondUserId = :userId)) AND f.status = 1")
FriendStatus getFollow(@Param("userId") Long userId, @Param("friendId") Long friendId);
@Transactional
@Modifying
@Query(value = "UPDATE FriendStatus f SET f.status = 2 WHERE ((f.firstUserId = :userId AND f.secondUserId = :friendId) OR (f.firstUserId = :friendId AND f.secondUserId = :userId)) AND f.status = 1")
void setFriend(@Param("userId") Long userId, @Param("friendId") Long friendId);
}

View File

@ -1,5 +1,6 @@
package com.alterdekim.game.service;
import com.alterdekim.game.entities.FriendStatus;
import com.alterdekim.game.repository.FriendRepository;
import lombok.AllArgsConstructor;
import org.springframework.stereotype.Service;
@ -18,4 +19,12 @@ public class FriendServiceImpl {
public void removeFriend(Long userId, Long friendId) {
repository.removeFriend(userId, friendId);
}
public void followUser(Long userId, Long friendId) {
if( repository.getFollow(userId, friendId) == null ) {
repository.save(new FriendStatus(userId, friendId, 1));
return;
}
repository.setFriend(userId, friendId);
}
}

View File

@ -18,7 +18,7 @@ public class AuthenticationUtil {
model.addAttribute("profile", new FriendPageResult("/profile/" + u.getId(), "/image/store/"+u.getAvatarId(), u.getUsername(), u.getId(), u.getDisplayName()));
return u;
} catch (Exception e) {
log.error(e.getMessage(), e);
// log.error(e.getMessage(), e);
}
}
return null;

View File

@ -0,0 +1,58 @@
.profile-info-avatar {
display: flex;
justify-content: center;
align-items: center;
position: relative;
border-radius: 100%;
width: 15rem;
height: 15rem;
background: center/cover no-repeat;
}
.profile-info-nick {
font-size: 45px;
font-weight: 300;
}
.profile-top-info {
display: flex;
}
.profile-stat {
display: flex;
flex-direction: column;
justify-content: center;
}
.profile-top-stat-list {
display: flex;
flex-flow: row nowrap;
margin-left: 25px;
width: 600px;
height: 61px;
}
._val {
font-size: 35px;
font-weight: 300;
line-height: 26px;
}
._key {
margin-top: 8px;
margin-right: 10px;
line-height: 10px;
opacity: .5;
}
.profile-top-actions {
display: flex;
flex-direction: column;
width: 200px;
max-height: 60px;
}
.profile-second-row {
display: flex;
margin-top: 15px;
}

View File

@ -0,0 +1,12 @@
function followUser(obj) {
let uid = $(obj).attr('data-profile-id');
$.ajax({
method: "POST",
url: "/api/v1/friends/follow",
data: {
userId: uid
}
}).done(function() {
window.location.reload();
});
}

View File

@ -0,0 +1,40 @@
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.w3.org/1999/xhtml" xmlns:sec="http://www.thymeleaf.org/extras/spring-security">
<head>
<th:block th:insert="~{fragments/head}"></th:block>
<link rel="stylesheet" href="/static/css/profile.css"/>
</head>
<body>
<th:block th:insert="~{fragments/navbar}"></th:block>
<div class="container mt-5 min-vh-100" style="max-width: 1000px">
<div class="profile-top-info">
<div class="profile-info-avatar" th:style="${page_user.avatar}"></div>
<span class="profile-info-nick" th:text="${page_user.displayName}"></span>
</div>
<div class="profile-second-row">
<div class="profile-top-actions">
<button class="btn btn-primary" onClick="followUser(this)" th:data-profile-id="${page_user.id}">Follow</button>
</div>
<div class="profile-top-stat-list">
<div class="profile-stat">
<div class="_val">5</div>
<div class="_key">Friends</div>
</div>
<div class="profile-stat">
<div class="_val">5</div>
<div class="_key">Friends</div>
</div>
<div class="profile-stat">
<div class="_val">5</div>
<div class="_key">Friends</div>
</div>
</div>
</div>
</div>
<th:block th:insert="~{fragments/footer}"></th:block>
<th:block th:insert="~{fragments/essentials}"></th:block>
<script src="/static/javascript/profile.js"></script>
</body>
</html>

View File

@ -32,10 +32,7 @@
<div class="mb-3">
<label for="pronouns-select" class="form-label">Pronouns</label>
<select class="form-select" id="pronouns-select" aria-label="">
<option value="1" selected>she/her</option>
<option value="2">he/him</option>
<option value="3">they/them</option>
<option value="4">idc</option>
<option th:each="option : ${options}" th:value="${option.text}" th:text="${option.text}" th:selected="${option.selected}"></option>
</select>
</div>
<div class="mb-3">