diff --git a/api/src/main/kotlin/com/nexters/bottles/api/bottle/facade/BottleFacade.kt b/api/src/main/kotlin/com/nexters/bottles/api/bottle/facade/BottleFacade.kt index 69791b3a..47071444 100644 --- a/api/src/main/kotlin/com/nexters/bottles/api/bottle/facade/BottleFacade.kt +++ b/api/src/main/kotlin/com/nexters/bottles/api/bottle/facade/BottleFacade.kt @@ -21,7 +21,6 @@ import com.nexters.bottles.api.bottle.facade.dto.PingPongLetter import com.nexters.bottles.api.bottle.facade.dto.PingPongListResponse import com.nexters.bottles.api.bottle.facade.dto.PingPongUserProfile import com.nexters.bottles.api.bottle.facade.dto.RegisterLetterRequest -import com.nexters.bottles.api.bottle.util.getLastActivatedAtInKorean import com.nexters.bottles.api.user.component.event.dto.UserApplicationEventDto import com.nexters.bottles.app.bottle.domain.Bottle import com.nexters.bottles.app.bottle.domain.Letter @@ -34,6 +33,7 @@ import com.nexters.bottles.app.bottle.service.QuestionCachingService import com.nexters.bottles.app.config.CacheType.Name.PING_PONG_BOTTLE import com.nexters.bottles.app.user.domain.User import com.nexters.bottles.app.user.domain.UserProfile +import com.nexters.bottles.app.user.service.BlockContactListService import com.nexters.bottles.app.user.service.UserReportService import com.nexters.bottles.app.user.service.UserService import org.springframework.beans.factory.annotation.Value @@ -52,6 +52,7 @@ class BottleFacade( private val bottleCachingService: BottleCachingService, private val questionCachingService: QuestionCachingService, private val applicationEventPublisher: ApplicationEventPublisher, + private val blockContactListService: BlockContactListService, @Value("\${matching.isActive}") private val isActiveMatching: Boolean, @@ -59,9 +60,13 @@ class BottleFacade( fun getNewBottles(userId: Long): BottleListResponse { val user = userService.findByIdAndNotDeleted(userId) + val blockUserIds = blockContactListService.findAllByUserId(userId).map{ it.userId }.toSet() // 내가 차단한 유저 + val blockedMeUserIds = blockContactListService.findAllByPhoneNumber(user.phoneNumber ?: throw IllegalStateException("핸드폰 번호를 등록해주세요")) + .map{ it.userId }.toSet() // 나를 차단한 유저 + if (isActiveMatching) { val matchingHour = 18 - bottleService.matchRandomBottle(user, matchingHour) + bottleService.matchRandomBottle(user, matchingHour, blockUserIds, blockedMeUserIds) ?.also { applicationEventPublisher.publishEvent( BottleMatchEventDto( @@ -73,14 +78,19 @@ class BottleFacade( } val bottles = bottleService.getNewBottles(user) val groupByStatus = bottles.groupBy { it.bottleStatus } - val blockedUserIds = userReportService.getReportRespondentList(userId) + val reportUserIds = userReportService.getReportRespondentList(userId) .map { it.respondentUserId } .toSet() - val randomBottles = groupByStatus[BottleStatus.RANDOM]?.map { toBottleDto(it, userId) } ?: emptyList() + val randomBottles = groupByStatus[BottleStatus.RANDOM] + ?.map { toBottleDto(it, userId) } + ?: emptyList() + val sentBottles = groupByStatus[BottleStatus.SENT] ?.map { toBottleDto(it, userId) } - ?.filter { it.userId !in blockedUserIds } + ?.filter { it.userId !in reportUserIds } + ?.filter { it.userId !in blockUserIds } + ?.filter { it.userId !in blockedMeUserIds } ?: emptyList() return BottleListResponse( @@ -172,13 +182,18 @@ class BottleFacade( val user = userService.findByIdAndNotDeleted(userId) val pingPongBottles = bottleCachingService.getPingPongBottles(userId) val groupByStatus = pingPongBottles.groupBy { it.pingPongStatus } - val blockedUserIds = userReportService.getReportRespondentList(userId) + val reportUserIds = userReportService.getReportRespondentList(userId) .map { it.respondentUserId } .toSet() + val blockUserIds = blockContactListService.findAllByUserId(userId).map{ it.userId }.toSet() // 내가 차단한 유저 + val blockMeUserIds = blockContactListService.findAllByPhoneNumber(user.phoneNumber ?: throw IllegalStateException("핸드폰 번호를 등록해주세요")) + .map{ it.userId }.toSet() // 나를 차단한 유저 val activeBottles = groupByStatus[PingPongStatus.ACTIVE] ?.map { toPingPongBottleDto(it, user) } - ?.filter { it.userId !in blockedUserIds } + ?.filter { it.userId !in reportUserIds } + ?.filter { it.userId !in blockUserIds} + ?.filter { it.userId !in blockMeUserIds } ?: emptyList() val doneBottles = (groupByStatus[PingPongStatus.STOPPED] @@ -187,7 +202,9 @@ class BottleFacade( groupByStatus[PingPongStatus.MATCHED].orEmpty() ) .map { toPingPongBottleDto(it, user) } - .filter { it.userId !in blockedUserIds } + .filter { it.userId !in reportUserIds } + .filter { it.userId !in blockUserIds} + .filter { it.userId !in blockMeUserIds } return PingPongListResponse(activeBottles = activeBottles, doneBottles = doneBottles) } diff --git a/api/src/main/kotlin/com/nexters/bottles/api/user/controller/UserController.kt b/api/src/main/kotlin/com/nexters/bottles/api/user/controller/UserController.kt index 3ffcf649..b9c88b0c 100644 --- a/api/src/main/kotlin/com/nexters/bottles/api/user/controller/UserController.kt +++ b/api/src/main/kotlin/com/nexters/bottles/api/user/controller/UserController.kt @@ -3,6 +3,7 @@ package com.nexters.bottles.api.user.controller import com.nexters.bottles.api.global.interceptor.AuthRequired import com.nexters.bottles.api.global.resolver.AuthUserId import com.nexters.bottles.api.user.facade.UserFacade +import com.nexters.bottles.api.user.facade.dto.BlockContactListRequest import com.nexters.bottles.api.user.facade.dto.ReportUserRequest import io.swagger.annotations.ApiOperation import org.springframework.web.bind.annotation.PostMapping @@ -22,4 +23,11 @@ class UserController( fun reportUser(@AuthUserId userId: Long, @RequestBody reportUserRequest: ReportUserRequest) { userFacade.reportUser(userId, reportUserRequest) } + + @ApiOperation("연락처 차단 목록 등록") + @PostMapping("/block/contact-list") + @AuthRequired + fun blockContactList(@AuthUserId userId: Long, @RequestBody blockContactListRequest: BlockContactListRequest) { + userFacade.blockContactList(userId, blockContactListRequest.blockContacts) + } } diff --git a/api/src/main/kotlin/com/nexters/bottles/api/user/facade/UserFacade.kt b/api/src/main/kotlin/com/nexters/bottles/api/user/facade/UserFacade.kt index f706a207..838a6435 100644 --- a/api/src/main/kotlin/com/nexters/bottles/api/user/facade/UserFacade.kt +++ b/api/src/main/kotlin/com/nexters/bottles/api/user/facade/UserFacade.kt @@ -1,13 +1,16 @@ package com.nexters.bottles.api.user.facade import com.nexters.bottles.api.user.facade.dto.ReportUserRequest +import com.nexters.bottles.app.user.domain.BlockContact import com.nexters.bottles.app.user.domain.UserReport +import com.nexters.bottles.app.user.service.BlockContactListService import com.nexters.bottles.app.user.service.UserReportService import org.springframework.stereotype.Component @Component class UserFacade( private val userReportService: UserReportService, + private val blockContactListService: BlockContactListService, ) { @@ -20,4 +23,14 @@ class UserFacade( ) ) } + + + fun blockContactList(userId: Long, blockContacts: Set) { + val savedBlockContacts = blockContactListService.findAllByUserId(userId = userId).map { it.phoneNumber }.toSet() + val newBlockContacts = blockContacts.minus(savedBlockContacts).map { BlockContact(userId = userId, phoneNumber = it) }.toList() + val deletedBlockContacts = savedBlockContacts.minus(blockContacts).map { BlockContact(userId = userId, phoneNumber = it) }.toList() + + blockContactListService.saveAll(newBlockContacts) + blockContactListService.deleteAll(deletedBlockContacts) + } } diff --git a/api/src/main/kotlin/com/nexters/bottles/api/user/facade/dto/BlockContactListRequest.kt b/api/src/main/kotlin/com/nexters/bottles/api/user/facade/dto/BlockContactListRequest.kt new file mode 100644 index 00000000..202e74bb --- /dev/null +++ b/api/src/main/kotlin/com/nexters/bottles/api/user/facade/dto/BlockContactListRequest.kt @@ -0,0 +1,6 @@ +package com.nexters.bottles.api.user.facade.dto + +data class BlockContactListRequest( + val blockContacts: Set = setOf(), +) { +} diff --git a/api/src/main/resources/sql/ddl/table_query.sql b/api/src/main/resources/sql/ddl/table_query.sql index 05b83117..e736939f 100644 --- a/api/src/main/resources/sql/ddl/table_query.sql +++ b/api/src/main/resources/sql/ddl/table_query.sql @@ -138,3 +138,16 @@ CREATE TABLE user_report updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, INDEX idx_reporter_user_id (reporter_user_id) ); + +CREATE TABLE block_contact_list +( + id BIGINT AUTO_INCREMENT PRIMARY KEY, + user_id BIGINT NOT NULL COMMENT '차단등록한 userId', + phone_number varchar(255) NOT NULL COMMENT '차단된 연락처', + created_at DATETIME DEFAULT CURRENT_TIMESTAMP, + updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + INDEX idx_reporter_user_id (user_id), + INDEX idx_phone_number (phone_number), + INDEX idx_created_at (created_at), + INDEX idx_updated_at (updated_at) +); diff --git a/app/src/main/kotlin/com/nexters/bottles/app/bottle/service/BottleService.kt b/app/src/main/kotlin/com/nexters/bottles/app/bottle/service/BottleService.kt index 4fa8b31f..9e65c598 100644 --- a/app/src/main/kotlin/com/nexters/bottles/app/bottle/service/BottleService.kt +++ b/app/src/main/kotlin/com/nexters/bottles/app/bottle/service/BottleService.kt @@ -154,7 +154,7 @@ class BottleService( } @Transactional - fun matchRandomBottle(user: User, matchingHour: Int): Bottle? { + fun matchRandomBottle(user: User, matchingHour: Int, blockUserIds: Set, blockedMeUserIds: Set): Bottle? { if (user.isNotRegisterProfile()) return null if (user.isMatchInactive()) return null @@ -162,6 +162,9 @@ class BottleService( if (user.lastRandomMatchedAt > matchingTime) return null val usersCanBeMatched = bottleMatchingRepository.findAllUserCanBeMatched(user.id, user.gender!!) + .filter { it.willMatchUserId !in blockUserIds } + .filter { it.willMatchUserId !in blockedMeUserIds } + if (usersCanBeMatched.isEmpty()) return null val matchingUserDto = findUserSameRegionOrRandom(usersCanBeMatched, user) diff --git a/app/src/main/kotlin/com/nexters/bottles/app/user/domain/BlockContact.kt b/app/src/main/kotlin/com/nexters/bottles/app/user/domain/BlockContact.kt new file mode 100644 index 00000000..9b511a9d --- /dev/null +++ b/app/src/main/kotlin/com/nexters/bottles/app/user/domain/BlockContact.kt @@ -0,0 +1,16 @@ +package com.nexters.bottles.app.user.domain + +import com.nexters.bottles.app.common.BaseEntity +import javax.persistence.* + +@Entity +class BlockContact( + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + val id: Long = 0L, + + val userId: Long, // 차단 등록한 유저 + + var phoneNumber: String, +) : BaseEntity() { +} diff --git a/app/src/main/kotlin/com/nexters/bottles/app/user/repository/BlockContactRepository.kt b/app/src/main/kotlin/com/nexters/bottles/app/user/repository/BlockContactRepository.kt new file mode 100644 index 00000000..6ad1732b --- /dev/null +++ b/app/src/main/kotlin/com/nexters/bottles/app/user/repository/BlockContactRepository.kt @@ -0,0 +1,11 @@ +package com.nexters.bottles.app.user.repository + +import com.nexters.bottles.app.user.domain.BlockContact +import org.springframework.data.jpa.repository.JpaRepository + +interface BlockContactRepository : JpaRepository { + + fun findAllByUserId(userId: Long): List + + fun findAllByPhoneNumber(phoneNumber: String): List +} diff --git a/app/src/main/kotlin/com/nexters/bottles/app/user/service/BlockContactListService.kt b/app/src/main/kotlin/com/nexters/bottles/app/user/service/BlockContactListService.kt new file mode 100644 index 00000000..e5feb8bf --- /dev/null +++ b/app/src/main/kotlin/com/nexters/bottles/app/user/service/BlockContactListService.kt @@ -0,0 +1,32 @@ +package com.nexters.bottles.app.user.service + +import com.nexters.bottles.app.user.domain.BlockContact +import com.nexters.bottles.app.user.repository.BlockContactRepository +import org.springframework.stereotype.Service +import org.springframework.transaction.annotation.Transactional + +@Service +class BlockContactListService( + private val blockContactListRepository: BlockContactRepository, +) { + + @Transactional(readOnly = true) + fun findAllByUserId(userId: Long): List { + return blockContactListRepository.findAllByUserId(userId) + } + + @Transactional + fun saveAll(newBlockContactList: List) { + blockContactListRepository.saveAll(newBlockContactList) + } + + @Transactional + fun deleteAll(newBlockContacts: List) { + blockContactListRepository.deleteAll(newBlockContacts) + } + + @Transactional(readOnly = true) + fun findAllByPhoneNumber(phoneNumber: String): List { + return blockContactListRepository.findAllByPhoneNumber(phoneNumber) + } +}