@@ -14,18 +14,14 @@ import com.krylov.refound.enums.PostStatus;
import com.krylov.refound.enums.PostType ;
import com.krylov.refound.enums.PostType ;
import com.krylov.refound.exception.ApiException ;
import com.krylov.refound.exception.ApiException ;
import com.krylov.refound.mapper.PostMapper ;
import com.krylov.refound.mapper.PostMapper ;
import com.krylov.refound.repository.FavoriteRepository ;
import com.krylov.refound.repository.ImageRepository ;
import com.krylov.refound.repository.ImageRepository ;
import com.krylov.refound.repository.PostRepository ;
import com.krylov.refound.repository.PostRepository ;
import com.krylov.refound.util.PostSpecification ;
import com.krylov.refound.util.PostSpecification ;
import java.time.LocalDateTime ;
import java.time.LocalDateTime ;
import java.util.Arrays ;
import java.util.Arrays ;
import java.util.EnumSet ;
import java.util.EnumSet ;
import java.util.HashSet ;
import java.util.List ;
import java.util.List ;
import java.util.Map ;
import java.util.Set ;
import java.util.Set ;
import java.util.stream.Collectors ;
import lombok.RequiredArgsConstructor ;
import lombok.RequiredArgsConstructor ;
import lombok.extern.slf4j.Slf4j ;
import lombok.extern.slf4j.Slf4j ;
import org.springframework.data.domain.Page ;
import org.springframework.data.domain.Page ;
@@ -50,7 +46,6 @@ public class PostService {
private final ImageRepository imageRepository ;
private final ImageRepository imageRepository ;
private final GeocodingService geocodingService ;
private final GeocodingService geocodingService ;
private final UserService userService ;
private final UserService userService ;
private final FavoriteRepository favoriteRepository ;
private final FileStorageService fileStorageService ;
private final FileStorageService fileStorageService ;
private final StatisticsService statisticsService ;
private final StatisticsService statisticsService ;
private final ObjectMapper objectMapper ;
private final ObjectMapper objectMapper ;
@@ -84,21 +79,13 @@ public class PostService {
}
}
public Page < PostResponse > getFeed ( String search , Pageable pageable ) {
public Page < PostResponse > getFeed ( String search , Pageable pageable ) {
Specification < Post > spec = PostSpecification . hasSearch ( search ) . and ( ( root , query , cb ) - > root . get ( " status " ) . in ( FEED_VISIBLE_STATUSES ) ) ;
Specification < Post > spec = PostSpecification . hasSearch ( search )
. and ( ( root , query , cb ) - >
root . get ( " status " ) . in ( FEED_VISIBLE_STATUSES ) ) ;
Page < Post > postsPage = postRepository . findAll ( spec , pageable ) ;
Page < Post > postsPage = postRepository . findAll ( spec , pageable ) ;
List < Long > postIds = postsPage . getContent ( ) . stream ( )
return postsPage . map ( post - > toResponse ( post , true ) ) ;
. map ( Post : : getId )
. toList ( ) ;
if ( postIds . isEmpty ( ) ) {
return Page . empty ( pageable ) ;
}
Map < Long , Long > likeCounts = countLikes ( postIds ) ;
Set < Long > userFavoriteIds = currentUserFavoriteIds ( postIds ) ;
return postsPage . map ( post - > toResponse ( post , likeCounts , userFavoriteIds , true ) ) ;
}
}
/**
/**
@@ -115,15 +102,8 @@ public class PostService {
return List . of ( ) ;
return List . of ( ) ;
}
}
List < Long > postIds = posts . stream ( )
. map ( Post : : getId )
. toList ( ) ;
Map < Long , Long > likeCounts = countLikes ( postIds ) ;
Set < Long > userFavoriteIds = currentUserFavoriteIds ( postIds ) ;
return posts . stream ( )
return posts . stream ( )
. map ( post - > toResponse ( post , likeCounts , userFavoriteIds , false ) )
. map ( post - > toResponse ( post , false ) )
. toList ( ) ;
. toList ( ) ;
}
}
@@ -203,9 +183,6 @@ public class PostService {
Post post = postRepository . findById ( id )
Post post = postRepository . findById ( id )
. orElseThrow ( ( ) - > new ApiException ( ErrorCode . NOT_FOUND , " Post not found " , HttpStatus . NOT_FOUND ) ) ;
. orElseThrow ( ( ) - > new ApiException ( ErrorCode . NOT_FOUND , " Post not found " , HttpStatus . NOT_FOUND ) ) ;
// Сначала избранное — иначе DataIntegrityViolationException (FK) при удалении поста
favoriteRepository . deleteByPostIn ( List . of ( post ) ) ;
// Без этого файлы останутся в MinIO мусором после удаления объявления
// Без этого файлы останутся в MinIO мусором после удаления объявления
post . getImages ( ) . forEach ( img - > deleteFileQuietly ( img . getUrl ( ) ) ) ;
post . getImages ( ) . forEach ( img - > deleteFileQuietly ( img . getUrl ( ) ) ) ;
@@ -234,13 +211,7 @@ public class PostService {
post . setStatus ( newStatus ) ;
post . setStatus ( newStatus ) ;
Post updated = postRepository . save ( post ) ;
Post updated = postRepository . save ( post ) ;
log . info ( " Статус поста id={} изменён: {} -> {} " , id , previousStatus , newStatus ) ;
log . info ( " Статус поста id={} изменён: {} -> {} " , id , previousStatus , newStatus ) ;
return mapper . toResponse ( updated ) ;
List < Long > postIds = List . of ( id ) ;
PostResponse response = mapper . toResponse ( updated ) ;
response . setLikeCount ( countLikes ( postIds ) . getOrDefault ( id , 0L ) ) ;
response . setFavorite ( currentUserFavoriteIds ( postIds ) . contains ( id ) ) ;
return response ;
}
}
public List < PostResponse > searchByCity ( String city ) {
public List < PostResponse > searchByCity ( String city ) {
@@ -256,15 +227,8 @@ public class PostService {
return List . of ( ) ;
return List . of ( ) ;
}
}
List < Long > postIds = posts . stream ( )
. map ( Post : : getId )
. toList ( ) ;
Map < Long , Long > likeCounts = countLikes ( postIds ) ;
Set < Long > userFavoriteIds = currentUserFavoriteIds ( postIds ) ;
return posts . stream ( )
return posts . stream ( )
. map ( post - > toResponse ( post , likeCounts , userFavoriteIds , true ) )
. map ( post - > toResponse ( post , true ) )
. toList ( ) ;
. toList ( ) ;
}
}
@@ -328,45 +292,8 @@ public class PostService {
}
}
}
}
public Map < Long , Long > countLikes ( Li st< Long > postIds ) {
public PostResponse toResponse ( Po st post , boolean includeUserRole ) {
if ( postIds . isEmpty ( ) ) {
return Map . of ( ) ;
}
return favoriteRepository . countLikesByPostIds ( postIds ) . stream ( )
. collect ( Collectors . toMap (
row - > ( Long ) row [ 0 ] ,
row - > ( Long ) row [ 1 ]
) ) ;
}
/**
* Возвращает id постов, лайкнутых текущим пользователем, либо пустой набор,
* если пользователь не авторизован.
*/
public Set < Long > currentUserFavoriteIds ( List < Long > postIds ) {
if ( postIds . isEmpty ( ) ) {
return Set . of ( ) ;
}
try {
User currentUser = userService . getCurrentUser ( ) ;
return new HashSet < > ( favoriteRepository . findFavoritePostIds ( currentUser . getId ( ) , postIds ) ) ;
} catch ( ApiException e ) {
if ( e . getErrorCode ( ) = = ErrorCode . UNAUTHORIZED ) {
// Ожидаемо для анонимных пользователей — не считаем ошибкой
log . debug ( " Избранное не подгружено: пользователь не авторизован " ) ;
} else {
// Подозрительный случай: в SecurityContext есть аутентификация с логином,
// но пользователя с таким логином нет в БД (например, удалён после выдачи токена)
log . warn ( " Избранное не подгружено: {} " , e . getMessage ( ) ) ;
}
return Set . of ( ) ;
}
}
public PostResponse toResponse ( Post post , Map < Long , Long > likeCounts , Set < Long > userFavoriteIds , boolean includeUserRole ) {
PostResponse response = mapper . toResponse ( post ) ;
PostResponse response = mapper . toResponse ( post ) ;
response . setLikeCount ( likeCounts . getOrDefault ( post . getId ( ) , 0L ) ) ;
response . setFavorite ( userFavoriteIds . contains ( post . getId ( ) ) ) ;
response . setReward ( post . getIsReward ( ) ) ;
response . setReward ( post . getIsReward ( ) ) ;
if ( includeUserRole ) {
if ( includeUserRole ) {
response . setUserRole ( post . getUser ( ) . getRole ( ) ) ;
response . setUserRole ( post . getUser ( ) . getRole ( ) ) ;
@@ -389,10 +316,6 @@ public class PostService {
Post post = postRepository . findById ( postId )
Post post = postRepository . findById ( postId )
. orElseThrow ( ( ) - > new ApiException ( ErrorCode . NOT_FOUND , " Post not found " , HttpStatus . NOT_FOUND ) ) ;
. orElseThrow ( ( ) - > new ApiException ( ErrorCode . NOT_FOUND , " Post not found " , HttpStatus . NOT_FOUND ) ) ;
List < Long > postIds = List . of ( postId ) ;
return toResponse ( post , true ) ;
Map < Long , Long > likeCounts = countLikes ( postIds ) ;
Set < Long > userFavoriteIds = currentUserFavoriteIds ( postIds ) ;
return toResponse ( post , likeCounts , userFavoriteIds , true ) ;
}
}
}
}