-
Notifications
You must be signed in to change notification settings - Fork 1
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
장소 신고하기 API 구현 #370
장소 신고하기 API 구현 #370
Changes from 2 commits
1676c4b
3919ead
270632d
5912fb2
fd6129f
3aaa5cd
601620c
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,34 @@ | ||
package club.staircrusher.accessibility.application.port.`in` | ||
|
||
import club.staircrusher.accessibility.application.port.out.NotificationService | ||
import club.staircrusher.place.application.port.`in`.PlaceApplicationService | ||
import club.staircrusher.stdlib.di.annotation.Component | ||
import club.staircrusher.stdlib.persistence.TransactionManager | ||
import org.springframework.beans.factory.annotation.Value | ||
|
||
@Component | ||
class ReportAccessibilityUseCase( | ||
private val transactionManager: TransactionManager, | ||
private val notificationService: NotificationService, | ||
private val placeApplicationService: PlaceApplicationService, | ||
@Value("\${scc.slack.channel.reportAccessibility:#scc-accessibility-report}") val accessibilityReportChannel: String, | ||
) { | ||
fun handle(placeId: String, userId: String, reason: String?) { | ||
val place = transactionManager.doInTransaction { | ||
placeApplicationService.findPlace(placeId) | ||
} | ||
|
||
val content = """ | ||
접근성 정보에 대한 신고가 접수되었습니다. | ||
|신고자: $userId | ||
|장소명: ${place?.name} | ||
|주소: ${place?.address} | ||
|신고 사유: ${reason ?: "사유 없음"} | ||
""".trimIndent() | ||
|
||
notificationService.send( | ||
accessibilityReportChannel, | ||
content | ||
) | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
package club.staircrusher.accessibility.application.port.out | ||
|
||
// TODO: push 기능 생기면 그거랑 합치기 | ||
interface NotificationService { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. notification이 꽤나 여러가지 의미를 가지는 것 같아서, 걍 SlackService가 더 나을지도? ㅋㅋ There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||
fun send(recipient: String, content: String) | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,36 @@ | ||
package club.staircrusher.accesssibility.infra.adapter.`in`.controller | ||
|
||
import club.staircrusher.accessibility.application.port.out.NotificationService | ||
import club.staircrusher.accesssibility.infra.adapter.`in`.controller.base.AccessibilityITBase | ||
import club.staircrusher.api.spec.dto.ReportAccessibilityPostRequest | ||
import org.junit.jupiter.api.Test | ||
import org.mockito.kotlin.any | ||
import org.mockito.kotlin.eq | ||
import org.mockito.kotlin.times | ||
import org.mockito.kotlin.verify | ||
import org.springframework.boot.test.mock.mockito.MockBean | ||
|
||
class ReportAccessibilityTest : AccessibilityITBase() { | ||
@MockBean | ||
lateinit var notificationService: NotificationService | ||
|
||
@Test | ||
fun `신고하기를 누를 경우 메세지가 전송된다`() { | ||
val accessibilityResult = registerAccessibility() | ||
val place = accessibilityResult.place | ||
val user = accessibilityResult.user | ||
|
||
val params = ReportAccessibilityPostRequest(placeId = place.id, reason = "아이유") | ||
mvc | ||
.sccRequest("/reportAccessibility", params, user) | ||
.andExpect { | ||
status { isNoContent() } | ||
} | ||
.apply { | ||
verify(notificationService, times(1)).send( | ||
recipient = eq("#scc-accessibility-report-test"), | ||
any(), | ||
) | ||
} | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
package club.staircrusher.accessibility.infra.adapter.`in`.controller | ||
|
||
import club.staircrusher.accessibility.application.port.`in`.ReportAccessibilityUseCase | ||
import club.staircrusher.api.spec.dto.ReportAccessibilityPostRequest | ||
import club.staircrusher.spring_web.security.app.SccAppAuthentication | ||
import org.springframework.http.ResponseEntity | ||
import org.springframework.web.bind.annotation.PostMapping | ||
import org.springframework.web.bind.annotation.RequestBody | ||
import org.springframework.web.bind.annotation.RestController | ||
|
||
@RestController | ||
class ReportAccessibilityController( | ||
private val reportAccessibilityUseCase: ReportAccessibilityUseCase, | ||
) { | ||
@PostMapping("/reportAccessibility") | ||
fun reportAccessibility( | ||
@RequestBody request: ReportAccessibilityPostRequest, | ||
authentication: SccAppAuthentication, | ||
): ResponseEntity<Unit> { | ||
reportAccessibilityUseCase.handle( | ||
request.placeId, | ||
authentication.principal, | ||
request.reason, | ||
) | ||
return ResponseEntity | ||
.noContent() | ||
.build() | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
package club.staircrusher.accessibility.infra.adapter.out | ||
|
||
import org.springframework.boot.context.properties.ConfigurationProperties | ||
|
||
@ConfigurationProperties("scc.slack") | ||
data class SlackNotificationProperties( | ||
val token: String | ||
) |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,80 @@ | ||
package club.staircrusher.accessibility.infra.adapter.out | ||
|
||
import club.staircrusher.accessibility.application.port.out.NotificationService | ||
import club.staircrusher.infra.network.createExternalApiService | ||
import club.staircrusher.stdlib.di.annotation.Component | ||
import mu.KotlinLogging | ||
import org.springframework.http.HttpHeaders | ||
import org.springframework.web.bind.annotation.RequestBody | ||
import org.springframework.web.service.annotation.PostExchange | ||
|
||
@Component | ||
class SlackNotificationService( | ||
properties: SlackNotificationProperties | ||
) : NotificationService { | ||
private val logger = KotlinLogging.logger {} | ||
private val slackService = createExternalApiService<SlackService>( | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 오 감사합니다 ㅋㅋ |
||
baseUrl = "https://slack.com", | ||
defaultHeadersBlock = { it.add(HttpHeaders.AUTHORIZATION, "Bearer ${properties.token}") }, | ||
defaultErrorHandler = { response -> | ||
response | ||
.bodyToMono(String::class.java) | ||
.map { RuntimeException(it) } | ||
.onErrorResume { response.createException() } | ||
}, | ||
) | ||
|
||
override fun send(recipient: String, content: String) { | ||
val response = slackService.postMessage( | ||
SlackService.PostMessageRequest( | ||
channel = recipient, | ||
text = content, | ||
attachments = emptyList(), | ||
) | ||
) | ||
|
||
if (!response.ok) { | ||
logger.error("Failed to send message: $content; response: $response") | ||
} | ||
} | ||
|
||
private interface SlackService { | ||
@PostExchange( | ||
url = "/api/chat.postMessage", | ||
accept = ["application/json"], | ||
contentType = "application/json" | ||
) | ||
fun postMessage( | ||
@RequestBody requestBody: PostMessageRequest, | ||
): PostMessageResponse | ||
|
||
/** | ||
* https://api.slack.com/methods/chat.postMessage | ||
*/ | ||
data class PostMessageRequest( | ||
val channel: String, | ||
val text: String, | ||
val attachments: List<Attachment>, | ||
) { | ||
data class Attachment( | ||
val text: String, | ||
val actions: List<Action>, | ||
) { | ||
data class Action( | ||
val type: String = "button", | ||
val text: String, | ||
val url: String, | ||
) | ||
} | ||
} | ||
|
||
data class PostMessageResponse( | ||
val ok: Boolean, | ||
val channel: String, | ||
// Slack 메세지가 전송된 timestamp | ||
// channel 과 조합해서 id 처럼 쓰인다 | ||
val ts: String, | ||
val error: String? = null, | ||
) | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
package club.staircrusher.testing.spring_it.mock | ||
|
||
import club.staircrusher.accessibility.application.port.out.NotificationService | ||
|
||
class MockNotificationService : NotificationService { | ||
override fun send(recipient: String, content: String) { | ||
return | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
기획에 대해 잘 모르는데, reportPlaceAccessibility랑 reportBuildingAccessibility가 구분되지 않아도 괜찮나요?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
일단 지금 앱 구현에서는 따로 구분되어 있는거 같지 않아서 Place 로 합쳐놨는데 한번 여쭤볼게요!