Merge pull request 'feat: add user statistics functionality with DTOs and service methods' (#46) from feat/user-stats into main
Reviewed-on: #46
This commit is contained in:
commit
890421ec9c
3
.vscode/settings.json
vendored
Normal file
3
.vscode/settings.json
vendored
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
{
|
||||||
|
"java.compile.nullAnalysis.mode": "automatic"
|
||||||
|
}
|
||||||
@ -0,0 +1,29 @@
|
|||||||
|
package com.magamochi.userinteraction.controller;
|
||||||
|
|
||||||
|
import com.magamochi.common.model.dto.DefaultResponseDTO;
|
||||||
|
import com.magamochi.userinteraction.model.dto.UserStatisticsDTO;
|
||||||
|
import com.magamochi.userinteraction.service.UserStatisticsService;
|
||||||
|
import io.swagger.v3.oas.annotations.Operation;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import org.springframework.web.bind.annotation.CrossOrigin;
|
||||||
|
import org.springframework.web.bind.annotation.GetMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RequestMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
|
|
||||||
|
@RestController
|
||||||
|
@RequestMapping("/user/statistics")
|
||||||
|
@CrossOrigin(origins = "*")
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class UserStatisticsController {
|
||||||
|
private final UserStatisticsService userStatisticsService;
|
||||||
|
|
||||||
|
@Operation(
|
||||||
|
summary = "Get user statistics",
|
||||||
|
description = "Get statistics for the logged in user.",
|
||||||
|
tags = {"User Statistics"},
|
||||||
|
operationId = "getUserStatistics")
|
||||||
|
@GetMapping
|
||||||
|
public DefaultResponseDTO<UserStatisticsDTO> getUserStatistics() {
|
||||||
|
return DefaultResponseDTO.ok(userStatisticsService.getUserStatistics());
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,8 @@
|
|||||||
|
package com.magamochi.userinteraction.model.dto;
|
||||||
|
|
||||||
|
import java.time.Instant;
|
||||||
|
import lombok.Builder;
|
||||||
|
|
||||||
|
@Builder
|
||||||
|
public record UserRecentActivityDTO(
|
||||||
|
String mangaTitle, String contentTitle, Instant readAt, Long mangaId) {}
|
||||||
@ -0,0 +1,13 @@
|
|||||||
|
package com.magamochi.userinteraction.model.dto;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import lombok.Builder;
|
||||||
|
|
||||||
|
@Builder
|
||||||
|
public record UserStatisticsDTO(
|
||||||
|
long favoriteMangaCount,
|
||||||
|
long mangaReadingCount,
|
||||||
|
long chaptersReadCount,
|
||||||
|
UserRecentActivityDTO lastReadContent,
|
||||||
|
boolean hasReadingActivity,
|
||||||
|
List<UserRecentActivityDTO> recentActivities) {}
|
||||||
@ -13,4 +13,6 @@ public interface UserFavoriteMangaRepository extends JpaRepository<UserFavoriteM
|
|||||||
Optional<UserFavoriteManga> findByUserAndManga(User user, Manga manga);
|
Optional<UserFavoriteManga> findByUserAndManga(User user, Manga manga);
|
||||||
|
|
||||||
Set<UserFavoriteManga> findByUser(User user);
|
Set<UserFavoriteManga> findByUser(User user);
|
||||||
|
|
||||||
|
long countByUser(User user);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -3,11 +3,25 @@ package com.magamochi.userinteraction.model.repository;
|
|||||||
import com.magamochi.content.model.entity.MangaContent;
|
import com.magamochi.content.model.entity.MangaContent;
|
||||||
import com.magamochi.user.model.entity.User;
|
import com.magamochi.user.model.entity.User;
|
||||||
import com.magamochi.userinteraction.model.entity.UserMangaContentRead;
|
import com.magamochi.userinteraction.model.entity.UserMangaContentRead;
|
||||||
|
import java.util.List;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
import org.springframework.data.jpa.repository.JpaRepository;
|
import org.springframework.data.jpa.repository.JpaRepository;
|
||||||
|
import org.springframework.data.jpa.repository.Query;
|
||||||
|
import org.springframework.data.repository.query.Param;
|
||||||
|
|
||||||
public interface UserMangaContentReadRepository extends JpaRepository<UserMangaContentRead, Long> {
|
public interface UserMangaContentReadRepository extends JpaRepository<UserMangaContentRead, Long> {
|
||||||
boolean existsByUserAndMangaContent(User user, MangaContent mangaContent);
|
boolean existsByUserAndMangaContent(User user, MangaContent mangaContent);
|
||||||
|
|
||||||
Optional<UserMangaContentRead> findByUserAndMangaContent(User user, MangaContent mangaContent);
|
Optional<UserMangaContentRead> findByUserAndMangaContent(User user, MangaContent mangaContent);
|
||||||
|
|
||||||
|
long countByUser(User user);
|
||||||
|
|
||||||
|
@Query(
|
||||||
|
"SELECT COUNT(DISTINCT mcp.manga) FROM UserMangaContentRead umcr "
|
||||||
|
+ "JOIN umcr.mangaContent mc "
|
||||||
|
+ "JOIN mc.mangaContentProvider mcp "
|
||||||
|
+ "WHERE umcr.user = :user")
|
||||||
|
long countDistinctMangaByUser(@Param("user") User user);
|
||||||
|
|
||||||
|
List<UserMangaContentRead> findTop10ByUserOrderByCreatedAtDesc(User user);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -0,0 +1,58 @@
|
|||||||
|
package com.magamochi.userinteraction.service;
|
||||||
|
|
||||||
|
import com.magamochi.user.service.UserService;
|
||||||
|
import com.magamochi.userinteraction.model.dto.UserRecentActivityDTO;
|
||||||
|
import com.magamochi.userinteraction.model.dto.UserStatisticsDTO;
|
||||||
|
import com.magamochi.userinteraction.model.entity.UserMangaContentRead;
|
||||||
|
import com.magamochi.userinteraction.model.repository.UserFavoriteMangaRepository;
|
||||||
|
import com.magamochi.userinteraction.model.repository.UserMangaContentReadRepository;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
|
@Service
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class UserStatisticsService {
|
||||||
|
private final UserService userService;
|
||||||
|
private final UserFavoriteMangaRepository userFavoriteMangaRepository;
|
||||||
|
private final UserMangaContentReadRepository userMangaContentReadRepository;
|
||||||
|
|
||||||
|
public UserStatisticsDTO getUserStatistics() {
|
||||||
|
var user = userService.getLoggedUserThrowIfNotFound();
|
||||||
|
|
||||||
|
var favoriteMangaCount = userFavoriteMangaRepository.countByUser(user);
|
||||||
|
var mangaReadingCount = userMangaContentReadRepository.countDistinctMangaByUser(user);
|
||||||
|
var chaptersReadCount = userMangaContentReadRepository.countByUser(user);
|
||||||
|
|
||||||
|
var recentReads = userMangaContentReadRepository.findTop10ByUserOrderByCreatedAtDesc(user);
|
||||||
|
|
||||||
|
UserRecentActivityDTO lastReadContent = null;
|
||||||
|
if (!recentReads.isEmpty()) {
|
||||||
|
lastReadContent = mapToRecentActivityDTO(recentReads.getFirst());
|
||||||
|
}
|
||||||
|
|
||||||
|
var recentActivities =
|
||||||
|
recentReads.stream().map(this::mapToRecentActivityDTO).collect(Collectors.toList());
|
||||||
|
|
||||||
|
return UserStatisticsDTO.builder()
|
||||||
|
.favoriteMangaCount(favoriteMangaCount)
|
||||||
|
.mangaReadingCount(mangaReadingCount)
|
||||||
|
.chaptersReadCount(chaptersReadCount)
|
||||||
|
.lastReadContent(lastReadContent)
|
||||||
|
.hasReadingActivity(!recentReads.isEmpty())
|
||||||
|
.recentActivities(recentActivities)
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
private UserRecentActivityDTO mapToRecentActivityDTO(UserMangaContentRead read) {
|
||||||
|
var mangaContent = read.getMangaContent();
|
||||||
|
var manga = mangaContent.getMangaContentProvider().getManga();
|
||||||
|
|
||||||
|
return UserRecentActivityDTO.builder()
|
||||||
|
.mangaTitle(manga.getTitle())
|
||||||
|
.contentTitle(mangaContent.getTitle())
|
||||||
|
.readAt(read.getCreatedAt())
|
||||||
|
.mangaId(manga.getId())
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user