mirror of
https://github.com/nagisa77/OpenIsle.git
synced 2026-05-11 21:27:31 +08:00
feat: add follow unsubscribe integration
This commit is contained in:
@@ -12,11 +12,19 @@
|
|||||||
<div class="profile-page-header-user-info">
|
<div class="profile-page-header-user-info">
|
||||||
<div class="profile-page-header-user-info-name">{{ user.username }}</div>
|
<div class="profile-page-header-user-info-name">{{ user.username }}</div>
|
||||||
<div class="profile-page-header-user-info-description">{{ user.introduction }}</div>
|
<div class="profile-page-header-user-info-description">{{ user.introduction }}</div>
|
||||||
<div class="profile-page-header-subscribe-button">
|
<div
|
||||||
|
v-if="!isMine && !subscribed"
|
||||||
|
class="profile-page-header-subscribe-button"
|
||||||
|
@click="subscribeUser"
|
||||||
|
>
|
||||||
<i class="fas fa-user-plus"></i>
|
<i class="fas fa-user-plus"></i>
|
||||||
关注
|
关注
|
||||||
</div>
|
</div>
|
||||||
<div class="profile-page-header-unsubscribe-button">
|
<div
|
||||||
|
v-if="!isMine && subscribed"
|
||||||
|
class="profile-page-header-unsubscribe-button"
|
||||||
|
@click="unsubscribeUser"
|
||||||
|
>
|
||||||
<i class="fas fa-user-minus"></i>
|
<i class="fas fa-user-minus"></i>
|
||||||
取消关注
|
取消关注
|
||||||
</div>
|
</div>
|
||||||
@@ -203,9 +211,10 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import { ref, onMounted, watch } from 'vue'
|
import { ref, computed, onMounted, watch } from 'vue'
|
||||||
import { useRoute } from 'vue-router'
|
import { useRoute } from 'vue-router'
|
||||||
import { API_BASE_URL } from '../main'
|
import { API_BASE_URL, toast } from '../main'
|
||||||
|
import { getToken, authState } from '../utils/auth'
|
||||||
import BaseTimeline from '../components/BaseTimeline.vue'
|
import BaseTimeline from '../components/BaseTimeline.vue'
|
||||||
import UserList from '../components/UserList.vue'
|
import UserList from '../components/UserList.vue'
|
||||||
import { stripMarkdown } from '../utils/markdown'
|
import { stripMarkdown } from '../utils/markdown'
|
||||||
@@ -226,19 +235,28 @@ export default {
|
|||||||
const timelineItems = ref([])
|
const timelineItems = ref([])
|
||||||
const followers = ref([])
|
const followers = ref([])
|
||||||
const followings = ref([])
|
const followings = ref([])
|
||||||
|
const subscribed = ref(false)
|
||||||
const isLoading = ref(true)
|
const isLoading = ref(true)
|
||||||
const tabLoading = ref(false)
|
const tabLoading = ref(false)
|
||||||
const selectedTab = ref('summary')
|
const selectedTab = ref('summary')
|
||||||
const followTab = ref('followers')
|
const followTab = ref('followers')
|
||||||
|
|
||||||
|
const isMine = computed(() => authState.username === username)
|
||||||
|
|
||||||
const formatDate = (d) => {
|
const formatDate = (d) => {
|
||||||
if (!d) return ''
|
if (!d) return ''
|
||||||
return TimeManager.format(d)
|
return TimeManager.format(d)
|
||||||
}
|
}
|
||||||
|
|
||||||
const fetchUser = async () => {
|
const fetchUser = async () => {
|
||||||
const res = await fetch(`${API_BASE_URL}/api/users/${username}`)
|
const token = getToken()
|
||||||
if (res.ok) user.value = await res.json()
|
const headers = token ? { Authorization: `Bearer ${token}` } : {}
|
||||||
|
const res = await fetch(`${API_BASE_URL}/api/users/${username}`, { headers })
|
||||||
|
if (res.ok) {
|
||||||
|
const data = await res.json()
|
||||||
|
user.value = data
|
||||||
|
subscribed.value = !!data.subscribed
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const fetchSummary = async () => {
|
const fetchSummary = async () => {
|
||||||
@@ -303,6 +321,42 @@ export default {
|
|||||||
tabLoading.value = false
|
tabLoading.value = false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const subscribeUser = async () => {
|
||||||
|
const token = getToken()
|
||||||
|
if (!token) {
|
||||||
|
toast.error('请先登录')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const res = await fetch(`${API_BASE_URL}/api/subscriptions/users/${username}`, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: { Authorization: `Bearer ${token}` }
|
||||||
|
})
|
||||||
|
if (res.ok) {
|
||||||
|
subscribed.value = true
|
||||||
|
toast.success('已关注')
|
||||||
|
} else {
|
||||||
|
toast.error('操作失败')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const unsubscribeUser = async () => {
|
||||||
|
const token = getToken()
|
||||||
|
if (!token) {
|
||||||
|
toast.error('请先登录')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const res = await fetch(`${API_BASE_URL}/api/subscriptions/users/${username}`, {
|
||||||
|
method: 'DELETE',
|
||||||
|
headers: { Authorization: `Bearer ${token}` }
|
||||||
|
})
|
||||||
|
if (res.ok) {
|
||||||
|
subscribed.value = false
|
||||||
|
toast.success('已取消关注')
|
||||||
|
} else {
|
||||||
|
toast.error('操作失败')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const init = async () => {
|
const init = async () => {
|
||||||
try {
|
try {
|
||||||
await fetchUser()
|
await fetchUser()
|
||||||
@@ -330,6 +384,8 @@ export default {
|
|||||||
timelineItems,
|
timelineItems,
|
||||||
followers,
|
followers,
|
||||||
followings,
|
followings,
|
||||||
|
subscribed,
|
||||||
|
isMine,
|
||||||
isLoading,
|
isLoading,
|
||||||
tabLoading,
|
tabLoading,
|
||||||
selectedTab,
|
selectedTab,
|
||||||
@@ -338,7 +394,9 @@ export default {
|
|||||||
stripMarkdown,
|
stripMarkdown,
|
||||||
loadTimeline,
|
loadTimeline,
|
||||||
loadFollow,
|
loadFollow,
|
||||||
loadSummary
|
loadSummary,
|
||||||
|
subscribeUser,
|
||||||
|
unsubscribeUser
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -42,7 +42,7 @@ public class UserController {
|
|||||||
@GetMapping("/me")
|
@GetMapping("/me")
|
||||||
public ResponseEntity<UserDto> me(Authentication auth) {
|
public ResponseEntity<UserDto> me(Authentication auth) {
|
||||||
User user = userService.findByUsername(auth.getName()).orElseThrow();
|
User user = userService.findByUsername(auth.getName()).orElseThrow();
|
||||||
return ResponseEntity.ok(toDto(user));
|
return ResponseEntity.ok(toDto(user, auth));
|
||||||
}
|
}
|
||||||
|
|
||||||
@PostMapping("/me/avatar")
|
@PostMapping("/me/avatar")
|
||||||
@@ -68,13 +68,14 @@ public class UserController {
|
|||||||
public ResponseEntity<UserDto> updateProfile(@RequestBody UpdateProfileDto dto,
|
public ResponseEntity<UserDto> updateProfile(@RequestBody UpdateProfileDto dto,
|
||||||
Authentication auth) {
|
Authentication auth) {
|
||||||
User user = userService.updateProfile(auth.getName(), dto.getUsername(), dto.getIntroduction());
|
User user = userService.updateProfile(auth.getName(), dto.getUsername(), dto.getIntroduction());
|
||||||
return ResponseEntity.ok(toDto(user));
|
return ResponseEntity.ok(toDto(user, auth));
|
||||||
}
|
}
|
||||||
|
|
||||||
@GetMapping("/{identifier}")
|
@GetMapping("/{identifier}")
|
||||||
public ResponseEntity<UserDto> getUser(@PathVariable("identifier") String identifier) {
|
public ResponseEntity<UserDto> getUser(@PathVariable("identifier") String identifier,
|
||||||
|
Authentication auth) {
|
||||||
User user = userService.findByIdentifier(identifier).orElseThrow();
|
User user = userService.findByIdentifier(identifier).orElseThrow();
|
||||||
return ResponseEntity.ok(toDto(user));
|
return ResponseEntity.ok(toDto(user, auth));
|
||||||
}
|
}
|
||||||
|
|
||||||
@GetMapping("/{identifier}/posts")
|
@GetMapping("/{identifier}/posts")
|
||||||
@@ -138,7 +139,8 @@ public class UserController {
|
|||||||
@GetMapping("/{identifier}/all")
|
@GetMapping("/{identifier}/all")
|
||||||
public ResponseEntity<UserAggregateDto> userAggregate(@PathVariable("identifier") String identifier,
|
public ResponseEntity<UserAggregateDto> userAggregate(@PathVariable("identifier") String identifier,
|
||||||
@RequestParam(value = "postsLimit", required = false) Integer postsLimit,
|
@RequestParam(value = "postsLimit", required = false) Integer postsLimit,
|
||||||
@RequestParam(value = "repliesLimit", required = false) Integer repliesLimit) {
|
@RequestParam(value = "repliesLimit", required = false) Integer repliesLimit,
|
||||||
|
Authentication auth) {
|
||||||
User user = userService.findByIdentifier(identifier).orElseThrow();
|
User user = userService.findByIdentifier(identifier).orElseThrow();
|
||||||
int pLimit = postsLimit != null ? postsLimit : defaultPostsLimit;
|
int pLimit = postsLimit != null ? postsLimit : defaultPostsLimit;
|
||||||
int rLimit = repliesLimit != null ? repliesLimit : defaultRepliesLimit;
|
int rLimit = repliesLimit != null ? repliesLimit : defaultRepliesLimit;
|
||||||
@@ -149,13 +151,13 @@ public class UserController {
|
|||||||
.map(this::toCommentInfoDto)
|
.map(this::toCommentInfoDto)
|
||||||
.collect(java.util.stream.Collectors.toList());
|
.collect(java.util.stream.Collectors.toList());
|
||||||
UserAggregateDto dto = new UserAggregateDto();
|
UserAggregateDto dto = new UserAggregateDto();
|
||||||
dto.setUser(toDto(user));
|
dto.setUser(toDto(user, auth));
|
||||||
dto.setPosts(posts);
|
dto.setPosts(posts);
|
||||||
dto.setReplies(replies);
|
dto.setReplies(replies);
|
||||||
return ResponseEntity.ok(dto);
|
return ResponseEntity.ok(dto);
|
||||||
}
|
}
|
||||||
|
|
||||||
private UserDto toDto(User user) {
|
private UserDto toDto(User user, Authentication viewer) {
|
||||||
UserDto dto = new UserDto();
|
UserDto dto = new UserDto();
|
||||||
dto.setId(user.getId());
|
dto.setId(user.getId());
|
||||||
dto.setUsername(user.getUsername());
|
dto.setUsername(user.getUsername());
|
||||||
@@ -168,9 +170,18 @@ public class UserController {
|
|||||||
dto.setCreatedAt(user.getCreatedAt());
|
dto.setCreatedAt(user.getCreatedAt());
|
||||||
dto.setLastPostTime(postService.getLastPostTime(user.getUsername()));
|
dto.setLastPostTime(postService.getLastPostTime(user.getUsername()));
|
||||||
dto.setTotalViews(postService.getTotalViews(user.getUsername()));
|
dto.setTotalViews(postService.getTotalViews(user.getUsername()));
|
||||||
|
if (viewer != null) {
|
||||||
|
dto.setSubscribed(subscriptionService.isSubscribed(viewer.getName(), user.getUsername()));
|
||||||
|
} else {
|
||||||
|
dto.setSubscribed(false);
|
||||||
|
}
|
||||||
return dto;
|
return dto;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private UserDto toDto(User user) {
|
||||||
|
return toDto(user, null);
|
||||||
|
}
|
||||||
|
|
||||||
private PostMetaDto toMetaDto(com.openisle.model.Post post) {
|
private PostMetaDto toMetaDto(com.openisle.model.Post post) {
|
||||||
PostMetaDto dto = new PostMetaDto();
|
PostMetaDto dto = new PostMetaDto();
|
||||||
dto.setId(post.getId());
|
dto.setId(post.getId());
|
||||||
@@ -219,6 +230,7 @@ public class UserController {
|
|||||||
private java.time.LocalDateTime createdAt;
|
private java.time.LocalDateTime createdAt;
|
||||||
private java.time.LocalDateTime lastPostTime;
|
private java.time.LocalDateTime lastPostTime;
|
||||||
private long totalViews;
|
private long totalViews;
|
||||||
|
private boolean subscribed;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Data
|
@Data
|
||||||
|
|||||||
@@ -99,4 +99,13 @@ public class SubscriptionService {
|
|||||||
User user = userRepo.findByUsername(username).orElseThrow();
|
User user = userRepo.findByUsername(username).orElseThrow();
|
||||||
return userSubRepo.countBySubscriber(user);
|
return userSubRepo.countBySubscriber(user);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean isSubscribed(String subscriberName, String targetName) {
|
||||||
|
if (subscriberName == null || targetName == null || subscriberName.equals(targetName)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
User subscriber = userRepo.findByUsername(subscriberName).orElseThrow();
|
||||||
|
User target = userRepo.findByUsername(targetName).orElseThrow();
|
||||||
|
return userSubRepo.findBySubscriberAndTarget(subscriber, target).isPresent();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user