From f759bf31d61840b9ab16a29653307a485ec5d57d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=9D=B8=EC=A4=80?= Date: Tue, 10 Sep 2024 23:29:51 +0900 Subject: [PATCH] =?UTF-8?q?[Feature/400]=20=EC=95=8C=EB=A6=BC=20=EC=84=A4?= =?UTF-8?q?=EC=A0=95=20=EB=B0=8F=20=EC=A1=B0=ED=9A=8C=20API=20=EA=B5=AC?= =?UTF-8?q?=ED=98=84=20(#405)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: 알림 설정 및 조회 API 구현 * feat: 주석 추가 * feat: 응답 형태 변경 * feat: 웹훅 수정 --- .github/workflows/pull-request-workflow.yml | 46 ++++++++++++++----- .../api/user/controller/UserController.kt | 17 +++++++ .../bottles/api/user/facade/UserFacade.kt | 14 ++++++ .../api/user/facade/dto/AlimyOnOffRequest.kt | 9 ++++ .../api/user/facade/dto/AlimyResponse.kt | 9 ++++ .../main/resources/sql/ddl/table_query.sql | 12 +++++ .../bottles/app/user/domain/UserAlimy.kt | 22 +++++++++ .../bottles/app/user/domain/enum/AlimyType.kt | 9 ++++ .../user/repository/UserAlimyRepository.kt | 12 +++++ .../app/user/service/UserAlimyService.kt | 30 ++++++++++++ 10 files changed, 168 insertions(+), 12 deletions(-) create mode 100644 api/src/main/kotlin/com/nexters/bottles/api/user/facade/dto/AlimyOnOffRequest.kt create mode 100644 api/src/main/kotlin/com/nexters/bottles/api/user/facade/dto/AlimyResponse.kt create mode 100644 app/src/main/kotlin/com/nexters/bottles/app/user/domain/UserAlimy.kt create mode 100644 app/src/main/kotlin/com/nexters/bottles/app/user/domain/enum/AlimyType.kt create mode 100644 app/src/main/kotlin/com/nexters/bottles/app/user/repository/UserAlimyRepository.kt create mode 100644 app/src/main/kotlin/com/nexters/bottles/app/user/service/UserAlimyService.kt diff --git a/.github/workflows/pull-request-workflow.yml b/.github/workflows/pull-request-workflow.yml index 52713424..83e44c54 100644 --- a/.github/workflows/pull-request-workflow.yml +++ b/.github/workflows/pull-request-workflow.yml @@ -42,22 +42,44 @@ jobs: env: DATA: | { - "content": "<@&${{ secrets.DISCORD_ROLE_ID }}>", - "embeds": [ + "blocks": [ { - "author": { - "name": ${{ toJson(github.event.sender.login) }}, - "url": "https://github.com/${{ github.event.sender.login }}", - "icon_url": ${{ toJson(github.event.sender.avatar_url) }} - }, - "title": ${{ toJson(github.event.pull_request.title) }}, - "description": ${{ toJson(github.event.pull_request.body) }}, - "url": ${{ toJson(github.event.pull_request.html_url) }}, - "color": 65280 + "type": "section", + "text": { + "type": "mrkdwn", + "text": "*서버 PR* :bell: <@U07L8AX9B4N><@U07L87A3WKY>" + } + }, + { + "type": "section", + "fields": [ + { + "type": "mrkdwn", + "text": "*Author:*\n" + }, + { + "type": "mrkdwn", + "text": "*Title:*\n${{ github.event.pull_request.title }}" + } + ] + }, + { + "type": "section", + "text": { + "type": "mrkdwn", + "text": "*Description:*\n${{ github.event.pull_request.body }}" + } + }, + { + "type": "section", + "text": { + "type": "mrkdwn", + "text": "*Pull Request URL:*\n<${{ github.event.pull_request.html_url }}|View PR>" + } } ] } run: | curl -X POST -H 'Content-type: application/json' \ -d "$DATA" \ - ${{ secrets.DISCORD_WEBHOOK_URL }} + ${{ secrets.SLACK_WEBHOOK_URL }} 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 b9c88b0c..f9f55be4 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,9 +3,12 @@ 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.AlimyOnOffRequest +import com.nexters.bottles.api.user.facade.dto.AlimyResponse 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.GetMapping import org.springframework.web.bind.annotation.PostMapping import org.springframework.web.bind.annotation.RequestBody import org.springframework.web.bind.annotation.RequestMapping @@ -30,4 +33,18 @@ class UserController( fun blockContactList(@AuthUserId userId: Long, @RequestBody blockContactListRequest: BlockContactListRequest) { userFacade.blockContactList(userId, blockContactListRequest.blockContacts) } + + @ApiOperation("알림 설정") + @PostMapping("/alimy") + @AuthRequired + fun turnOnOffAlimy(@AuthUserId userId: Long, @RequestBody alimyOnOffRequest: AlimyOnOffRequest) { + userFacade.turnOnOffAlimy(userId, alimyOnOffRequest) + } + + @ApiOperation("알림 설정 조회") + @GetMapping("/alimy") + @AuthRequired + fun turnOnOffAlimy(@AuthUserId userId: Long): List { + return userFacade.getAlimy(userId) + } } 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 838a6435..8a50a5ff 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,9 +1,13 @@ package com.nexters.bottles.api.user.facade +import com.nexters.bottles.api.user.facade.dto.AlimyOnOffRequest +import com.nexters.bottles.api.user.facade.dto.AlimyResponse import com.nexters.bottles.api.user.facade.dto.ReportUserRequest import com.nexters.bottles.app.user.domain.BlockContact +import com.nexters.bottles.app.user.domain.UserAlimy import com.nexters.bottles.app.user.domain.UserReport import com.nexters.bottles.app.user.service.BlockContactListService +import com.nexters.bottles.app.user.service.UserAlimyService import com.nexters.bottles.app.user.service.UserReportService import org.springframework.stereotype.Component @@ -11,6 +15,7 @@ import org.springframework.stereotype.Component class UserFacade( private val userReportService: UserReportService, private val blockContactListService: BlockContactListService, + private val alimyService: UserAlimyService, ) { @@ -33,4 +38,13 @@ class UserFacade( blockContactListService.saveAll(newBlockContacts) blockContactListService.deleteAll(deletedBlockContacts) } + + fun turnOnOffAlimy(userId: Long, alimyOnOffRequest: AlimyOnOffRequest) { + alimyService.turnOnOffAlimy(userId, alimyOnOffRequest.alimyType, alimyOnOffRequest.enabled) + } + + fun getAlimy(userId: Long): List { + return alimyService.findAlimies(userId) + .map { AlimyResponse(it.alimyType, it.enabled) } + } } diff --git a/api/src/main/kotlin/com/nexters/bottles/api/user/facade/dto/AlimyOnOffRequest.kt b/api/src/main/kotlin/com/nexters/bottles/api/user/facade/dto/AlimyOnOffRequest.kt new file mode 100644 index 00000000..e6484657 --- /dev/null +++ b/api/src/main/kotlin/com/nexters/bottles/api/user/facade/dto/AlimyOnOffRequest.kt @@ -0,0 +1,9 @@ +package com.nexters.bottles.api.user.facade.dto + +import com.nexters.bottles.app.user.domain.enum.AlimyType + +data class AlimyOnOffRequest( + val alimyType: AlimyType, + val enabled: Boolean, +) { +} diff --git a/api/src/main/kotlin/com/nexters/bottles/api/user/facade/dto/AlimyResponse.kt b/api/src/main/kotlin/com/nexters/bottles/api/user/facade/dto/AlimyResponse.kt new file mode 100644 index 00000000..4c1eb06e --- /dev/null +++ b/api/src/main/kotlin/com/nexters/bottles/api/user/facade/dto/AlimyResponse.kt @@ -0,0 +1,9 @@ +package com.nexters.bottles.api.user.facade.dto + +import com.nexters.bottles.app.user.domain.enum.AlimyType + +data class AlimyResponse( + val alimyType: AlimyType, + val enabled: Boolean, +) { +} diff --git a/api/src/main/resources/sql/ddl/table_query.sql b/api/src/main/resources/sql/ddl/table_query.sql index 0136ecb2..7d4297d9 100644 --- a/api/src/main/resources/sql/ddl/table_query.sql +++ b/api/src/main/resources/sql/ddl/table_query.sql @@ -151,3 +151,15 @@ CREATE TABLE block_contact INDEX idx_created_at (created_at), INDEX idx_updated_at (updated_at) ); + +CREATE TABLE user_alimy +( + id INT AUTO_INCREMENT PRIMARY KEY, + user_id INT NOT NULL, + alimy_type VARCHAR(100) NOT NULL comment 'DAILY_RANDOM, RECEIVE_LIKE, PINGPONG, MARKETING', + enabled BOOLEAN NOT NULL DEFAULT TRUE, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP + + UNIQUE KEY unique_user_alimy (user_id, alimy_type) +); diff --git a/app/src/main/kotlin/com/nexters/bottles/app/user/domain/UserAlimy.kt b/app/src/main/kotlin/com/nexters/bottles/app/user/domain/UserAlimy.kt new file mode 100644 index 00000000..26f5f8b3 --- /dev/null +++ b/app/src/main/kotlin/com/nexters/bottles/app/user/domain/UserAlimy.kt @@ -0,0 +1,22 @@ +package com.nexters.bottles.app.user.domain + +import com.nexters.bottles.app.common.BaseEntity +import com.nexters.bottles.app.user.domain.enum.AlimyType +import org.springframework.data.relational.core.mapping.Table +import javax.persistence.* + +@Entity +data class UserAlimy( + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + val id: Long = 0L, + + val userId: Long, + + @Enumerated(EnumType.STRING) + val alimyType: AlimyType, + + val enabled: Boolean = true, +): BaseEntity() { +} diff --git a/app/src/main/kotlin/com/nexters/bottles/app/user/domain/enum/AlimyType.kt b/app/src/main/kotlin/com/nexters/bottles/app/user/domain/enum/AlimyType.kt new file mode 100644 index 00000000..2c535aa4 --- /dev/null +++ b/app/src/main/kotlin/com/nexters/bottles/app/user/domain/enum/AlimyType.kt @@ -0,0 +1,9 @@ +package com.nexters.bottles.app.user.domain.enum + +enum class AlimyType { + DAILY_RANDOM, // 떠다니는 보틀 알림 + RECEIVE_LIKE, // 호감 도착 알림 + PINGPONG, // 대화 알림 + MARKETING, // 마케팅 수신 동의 + ; +} diff --git a/app/src/main/kotlin/com/nexters/bottles/app/user/repository/UserAlimyRepository.kt b/app/src/main/kotlin/com/nexters/bottles/app/user/repository/UserAlimyRepository.kt new file mode 100644 index 00000000..60b144f3 --- /dev/null +++ b/app/src/main/kotlin/com/nexters/bottles/app/user/repository/UserAlimyRepository.kt @@ -0,0 +1,12 @@ +package com.nexters.bottles.app.user.repository + +import com.nexters.bottles.app.user.domain.UserAlimy +import com.nexters.bottles.app.user.domain.enum.AlimyType +import org.springframework.data.jpa.repository.JpaRepository + +interface UserAlimyRepository: JpaRepository { + + fun findByUserIdAndAlimyType(userId: Long, alimyType: AlimyType): UserAlimy? + + fun findAllByUserId(userId: Long): List +} diff --git a/app/src/main/kotlin/com/nexters/bottles/app/user/service/UserAlimyService.kt b/app/src/main/kotlin/com/nexters/bottles/app/user/service/UserAlimyService.kt new file mode 100644 index 00000000..b1df65fd --- /dev/null +++ b/app/src/main/kotlin/com/nexters/bottles/app/user/service/UserAlimyService.kt @@ -0,0 +1,30 @@ +package com.nexters.bottles.app.user.service + +import com.nexters.bottles.app.user.domain.UserAlimy +import com.nexters.bottles.app.user.domain.enum.AlimyType +import com.nexters.bottles.app.user.repository.UserAlimyRepository +import org.springframework.stereotype.Service +import org.springframework.transaction.annotation.Transactional + +@Service +class UserAlimyService( + private val userAlimyRepository: UserAlimyRepository, +) { + @Transactional + fun turnOnOffAlimy(userId: Long, alimyType: AlimyType, enabled: Boolean) { + userAlimyRepository.findByUserIdAndAlimyType(userId, alimyType)?.let { + userAlimyRepository.save( + UserAlimy(userId = userId, alimyType = alimyType, enabled = enabled) + ) + } ?: run { + userAlimyRepository.save( + UserAlimy(userId = userId, alimyType = alimyType, enabled = enabled) + ) + } + } + + @Transactional(readOnly = true) + fun findAlimies(userId: Long): List { + return userAlimyRepository.findAllByUserId(userId) + } +}