Skip to content
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

Merged
merged 7 commits into from
Aug 21, 2024
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -598,6 +598,27 @@ paths:
required:
- placeAccessibilityComment

/reportAccessibility:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

기획에 대해 잘 모르는데, reportPlaceAccessibility랑 reportBuildingAccessibility가 구분되지 않아도 괜찮나요?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

일단 지금 앱 구현에서는 따로 구분되어 있는거 같지 않아서 Place 로 합쳐놨는데 한번 여쭤볼게요!

post:
summary: 등록된 접근성 정보가 올바르지 않은 경우 신고한다.
requestBody:
required: true
content:
application/json:
schema:
type: object
properties:
placeId:
type: string
description: 신고할 장소의 아이디
reason:
type: string
description: 신고 사유
required:
- placeId
responses:
'204': { }

/searchExternalAccessibilities:
post:
summary: 외부 접근성 정보가 등록된 장소를 검색한다.
Expand Down
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 {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

notification이 꽤나 여러가지 의미를 가지는 것 같아서, 걍 SlackService가 더 나을지도? ㅋㅋ

Copy link
Member Author

Choose a reason for hiding this comment

The 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
Expand Up @@ -9,8 +9,10 @@ dependencies {
implementation(projects.boundedContext.challenge.domain)
implementation(projects.boundedContext.challenge.application)
implementation(projects.boundedContext.challenge.infra)
implementation(projects.crossCuttingConcern.infra.network)
implementation(projects.crossCuttingConcern.infra.persistenceModel)
implementation("org.springframework.boot:spring-boot-starter-web")
implementation("org.springframework:spring-webflux")
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-jdk8")

integrationTestImplementation(projects.crossCuttingConcern.test.springIt)
Expand Down
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>(
Copy link
Contributor

Choose a reason for hiding this comment

The 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
Expand Up @@ -55,3 +55,8 @@ scc:
nhn-cloud:
url-shortening:
appKey: test

slack:
token: test
channel:
reportAccessibility: "#scc-accessibility-report-test"
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
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package club.staircrusher.testing.spring_it.mock
import club.staircrusher.accessibility.application.port.`in`.image.ImageProcessor
import club.staircrusher.accessibility.application.port.`in`.image.ThumbnailGenerator
import club.staircrusher.accessibility.application.port.out.DetectFacesService
import club.staircrusher.accessibility.application.port.out.NotificationService
import club.staircrusher.accessibility.application.port.out.file_management.FileManagementService
import club.staircrusher.place.application.port.out.web.MapsService
import club.staircrusher.quest.application.port.out.web.UrlShorteningService
Expand Down Expand Up @@ -78,4 +79,10 @@ open class SccSpringItMockConfiguration {
open fun mockImageProcessor(): ImageProcessor {
return MockImageProcessor()
}

@Bean
@Primary
open fun mockNotificationService(): NotificationService {
return MockNotificationService()
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -61,3 +61,8 @@ scc:
nhn-cloud:
url-shortening:
appKey: test

slack:
token: test
channel:
reportAccessibility: "#scc-accessibility-report-test"
Original file line number Diff line number Diff line change
Expand Up @@ -79,3 +79,8 @@ scc:
nhn-cloud:
url-shortening:
app-key: test

slack:
token: test
channel:
reportAccessibility: "#scc-accessibility-report-test"
3 changes: 3 additions & 0 deletions infra/helm/scc-server/files/dev/application.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -57,3 +57,6 @@ scc:
thumbnailBucketName: scc-dev-accessibility-thumbnails
cloudfront:
domain: d3vmj65l82owxs.cloudfront.net
slack:
channel:
reportAccessibility: "#scc-accessibility-report-test"
6 changes: 4 additions & 2 deletions infra/helm/scc-server/files/dev/secret.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ scc:
stibee:
apiKey: ENC[AES256_GCM,data:J7nuh5eVfUjlMsdatLzq38yJdzYPON+EQi79zzmJGF7UJOkwsTIi5Yb7oS6LhZLA5bKiti/S2j6sA94XeFz61suBQh9WzSxDMWTjWa+uTEVRQfZHg5ej6niikjIjI3XmbKt7LZoSlk1f6xPY7Cmn5IvPnfR8cX1zHcqv4jTuhAY=,iv:JHSJdMNKqY+NnKKMFJE6CjrW9OxKof7UhdwvkjxzZBM=,tag:mIN0EhQpDSs8O3KQ96aNuw==,type:str]
listId: ENC[AES256_GCM,data:RLAcAPT6,iv:FB7g7rdL075AcOeAgVTLm2PjC7ZDV5UZbEFaFxyjNms=,tag:E3wyrVGVNrgldouE+kwQQQ==,type:int]
slack:
token: ENC[AES256_GCM,data:KbZqJNS9+sThrnTRWPbhbKIMsCeFF+VLWirYoIIQ+Ym6zl+JPQs+a4hBbsYZVLIunke/HXY2MTdZ,iv:jaqEhzE/JHA7Q4JPmrQX+nNRZqsA6ejGl4ylc9mriCk=,tag:JVO6v/CM+Yn1U+pHebtclQ==,type:str]
sops:
kms:
- arn: arn:aws:kms:ap-northeast-2:291889421067:alias/sops
Expand All @@ -33,8 +35,8 @@ sops:
azure_kv: []
hc_vault: []
age: []
lastmodified: "2024-07-06T09:38:45Z"
mac: ENC[AES256_GCM,data:cxYVWD/x6qu6v7gXyiClC/P/ZtjD94IPDntcJtJouGpqp2PkTxJLAPkaIR0Y3H5PtagUImh3j3jnfh3wS1IUc07vLMxjnGjEyHQhJ0h6NjF9Sk2g+VTdgmcnLz5Tet80ggRCMybYBUYycIrpsCqsv7gTd9P76KbyPOEKGfHsjMw=,iv:Qdz8mzTxvYJJLxqX3i68iARd7Ii7LGOLBaOdQxYNCKw=,tag:vY80U66gOuihmxdBnUDWWw==,type:str]
lastmodified: "2024-08-16T07:04:49Z"
mac: ENC[AES256_GCM,data:ZLtZOwpnCscjQXhgbC8qjSX7tn7aeVtSLddDTRdoxmhh/gqkQG8BfWglg3TUns6EjWzSarO9UKBcTyJMl6NnybYJZDKj2/pQOu+uMEIR9L4cWs68XeGW/2NcMESF1J4wyzSRQcglIbWLyxYHb3mONkru5JU5FAlaXNfEviB6bAk=,iv:9o/48iHoxMIwTXZHMZpb3HKd8jk/yQoKfZ5nQfh7cu4=,tag:0EKCMcRwmyIwr767VdXvXQ==,type:str]
pgp: []
unencrypted_suffix: _unencrypted
version: 3.9.0
3 changes: 3 additions & 0 deletions infra/helm/scc-server/files/prod/application.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -57,3 +57,6 @@ scc:
thumbnailBucketName: scc-prod-accessibility-thumbnails
cloudfront:
domain: d1whorck6z6h62.cloudfront.net
slack:
channel:
reportAccessibility: "#scc-accessibility-report"
6 changes: 4 additions & 2 deletions infra/helm/scc-server/files/prod/secret.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ scc:
stibee:
apiKey: ENC[AES256_GCM,data:WoiOf+6w+BimLkHrF/btjAs/KRZbt6yvCGIOgQT3gJRmazZPbtODQW24Az8lyWbeT64TpNOVNITEgachKH9gWr6yWp5HZHUovuhLwiA0BZLEsHWnT1e3WauVrJ6mVduXfcOnHXUEvxjwd1NOjSNhfRxN0q/GsNF8iSNBXNZh2tQ=,iv:fxCqqrUcM6UPDqZ7YUMFqaDpdSOM2nAQceapYSbhGSQ=,tag:2iLWnm2LBU8rS60QRT3x1A==,type:str]
listId: ENC[AES256_GCM,data:xgzjVc5o,iv:akBZmf528GTUY6oURVwC1Ad7XzlDMSBZF+s4Jfod6Hw=,tag:6FRNWGdX8kVspTmEGng1TQ==,type:int]
slack:
token: ENC[AES256_GCM,data:PWdCpUaPcucWFx0rbYBy4SUtYBt8iTyBkb0K8E5eu0CcL9RVLEUGkJd/yEZtmmYxb1j9LfFk5pcb,iv:WDu8DwVk+n7zw6iPouSZyjOupM2hcgZcEsY9py78FSY=,tag:dBTWX9gUV7PZNfc0h3Glmg==,type:str]
sops:
kms:
- arn: arn:aws:kms:ap-northeast-2:291889421067:alias/sops
Expand All @@ -33,8 +35,8 @@ sops:
azure_kv: []
hc_vault: []
age: []
lastmodified: "2024-07-06T09:39:20Z"
mac: ENC[AES256_GCM,data:vUfwMOegtvbEfHCm9un0pgjVPuYXDzN8s7a81Qauz9sGX+H35i6ct+rm5ao/Xt9t44cF4mP00hwSAOQi1FJcQM11V0h1VAXPHC9EgEnQCeVLh0xyOpOZHsz5zs5rjypE0xH07qK5pEYV7PJ0ZFBDpg7/h9P6FG9+/1HHvw45PTI=,iv:k8O+MGg2aMLo82xPk4jKLHP8otmA+Sl9xDX991gy7O8=,tag:9wu3oMiyYuZCgM7wHUxjHg==,type:str]
lastmodified: "2024-08-16T07:05:29Z"
mac: ENC[AES256_GCM,data:PEUSra0aE1ARwLTMynChDSRWtCkfsG+TRbUDUr7xi/JBlDh5scXO6MCgOornPDOmD5MhasKfyA1uTotArwvHZeiMeIIx5CcN94M4BHvj/U+RbdF7pyPMKGWLknVjWHAMQHX6iQoB/cTQjHhwaDZ/wMv/jxbYwlpOcdGswXCtzJU=,iv:QAWwGC6Hrp0TvkS/BywbCbeamxEPbI3SNpWa3pPZ7MM=,tag:3Vn3OanCrfV7ANXCojqgkQ==,type:str]
pgp: []
unencrypted_suffix: _unencrypted
version: 3.9.0
Loading