Compare commits
372 Commits
test
...
11b9c349d1
| Author | SHA1 | Date | |
|---|---|---|---|
| 11b9c349d1 | |||
| ef9f8d65e1 | |||
| 299f2100e9 | |||
| fd5c794480 | |||
| 68197de095 | |||
| 587f3d6b58 | |||
| 9b6167d46d | |||
| 008ee3b4e5 | |||
| 3a57ad23bb | |||
| 729552335a | |||
| 02ae507c87 | |||
| 5818abf69d | |||
| ee403915f0 | |||
| 1a660088de | |||
| 5196c80ca8 | |||
| c9c09c2998 | |||
| 3ea33c4c7b | |||
| 451a1aa4f2 | |||
| 90555fd34f | |||
| 0dc430b098 | |||
| 1f2103c7fa | |||
| 062c17c51e | |||
| de169b79a1 | |||
| aa24de0a5a | |||
| e5937d573a | |||
| 6da86e12bd | |||
| 9049022a74 | |||
| 7b6f3a7a5f | |||
| 53e9678efa | |||
| e4f547fa92 | |||
| b69756ef81 | |||
| 1a3a9149a2 | |||
| ce120a6d5d | |||
| 08b5fd23ab | |||
| eb18e2d009 | |||
| a27852ed44 | |||
| c7925c1706 | |||
| be59bd7e89 | |||
| 51ce143fc2 | |||
| 89eb11f808 | |||
| 30d89987a4 | |||
| 7959d3e5ed | |||
| 1e29573ef7 | |||
| cc2f533dc6 | |||
| 32b0c19f9d | |||
| 9af2d768e8 | |||
| 5677824cde | |||
| e8f1bc09f9 | |||
| d1a936d55b | |||
| dc97eaa835 | |||
| dcbe57806c | |||
| b14438cc15 | |||
| b27d3bd5c6 | |||
| 03ebc9cfe9 | |||
| 24841b9850 | |||
| d35a3d1a8c | |||
| 60c4e0b528 | |||
| 84f33d1bc2 | |||
| c4e1709b99 | |||
| e7a5fd5819 | |||
| 4bde03643c | |||
| 1bc52b56af | |||
| 9c33fd93f7 | |||
| 3c087bc275 | |||
| 8ad13c289e | |||
| 7577f48a09 | |||
| 0251906964 | |||
| 2723a5f134 | |||
| c3c60605fd | |||
| 238f704b22 | |||
| 5639d8ac8e | |||
| 9aac591591 | |||
| ffa8e5aebb | |||
| cbbfe014cc | |||
| 83028f7817 | |||
| 70d1795557 | |||
| 8c6c681424 | |||
| 50bc9f4ff3 | |||
| f00ea03fad | |||
| f22e7b9ad1 | |||
| c7ec95f4bb | |||
| 229e7a8ccc | |||
| 3c616474ff | |||
| 56eb6b3ce3 | |||
| 545836d43c | |||
| 219f83dec0 | |||
| a76a841238 | |||
| c26680de84 | |||
| 8fffad9d3a | |||
| f4f0f203a2 | |||
| b7196f5a0c | |||
| 5d33a18890 | |||
| 96186a1a50 | |||
| bc8bc479d1 | |||
| 47595b1291 | |||
| 01a88964df | |||
| 3a2b77379f | |||
| dc4e5f75cd | |||
| d0178d551c | |||
| 827333108d | |||
| 587b90bd27 | |||
| 4dc20c5e90 | |||
| ac25782f2b | |||
| 20437d56e7 | |||
| f0b412828a | |||
| 367faac5c3 | |||
| 84deaaa970 | |||
| a2b39466c2 | |||
| 03586c4005 | |||
| 6ea69e1510 | |||
| 553c6dc539 | |||
| 6cc22f5b6d | |||
| 9103d67cc1 | |||
| 25083fb0e4 | |||
| d2dc045255 | |||
| b8621dfbb0 | |||
| 93633940dd | |||
| b6f5325351 | |||
| 7c32c08f1f | |||
| 1d268da08d | |||
| 797666ae0d | |||
| dcf470997e | |||
| 0974d1dbf8 | |||
| 12a35db6cd | |||
| 9abbb05ad8 | |||
| 1ecaf69b0b | |||
| e334d1e5d9 | |||
| b735e861d0 | |||
| 4eb433d372 | |||
| 2416ae61f3 | |||
| 01fb336985 | |||
| b6af88a732 | |||
| 58a2a17d6d | |||
| 79f5a0f520 | |||
| 7f6c0f7f04 | |||
| f658df4dca | |||
| 9d43b8e23a | |||
| 4270aef79b | |||
| 1c0dc82d44 | |||
| c1e325aadf | |||
| cec87da69d | |||
| f68f24cb2c | |||
| ed094347fc | |||
| b8afdffbe1 | |||
| f6ba79f31c | |||
| 5f3b1663d2 | |||
| 66e786b4bb | |||
| f671114574 | |||
| ce37060d94 | |||
| 7d19a4d184 | |||
| 22f28a2f8a | |||
| ceef9ca979 | |||
| efe8f4f939 | |||
| ba692a1195 | |||
| d732bad042 | |||
| 4c935c3bee | |||
| c160dd791f | |||
| 23cd1b4601 | |||
| 031fc8ba1b | |||
| c6853289ad | |||
| 2497bb69bc | |||
| a58a67e0a2 | |||
| 4315fe12a5 | |||
| 42f10a8899 | |||
| 1e4b47f989 | |||
| ff255dbfae | |||
| dbe9b72feb | |||
| 95a714b391 | |||
| 28f58c7f56 | |||
| 8bd46d8f21 | |||
| e1bb8e54ed | |||
| 1de705b063 | |||
| f6926ad356 | |||
| 2cdbbb1b37 | |||
| 4dce8c8f03 | |||
| 97a5bace6f | |||
| d4d51ec48f | |||
| fb91398462 | |||
| 105dadd798 | |||
| 2abf2837d3 | |||
| 422aa67af6 | |||
| 7fffab6985 | |||
| 5a4be3d2c1 | |||
| f39a7681db | |||
| c60a7580ba | |||
| 97edb56edc | |||
| 6ebca8d22b | |||
| 95371ad934 | |||
| 2c176825fd | |||
| fae7de48d3 | |||
| b8230646a2 | |||
| 43279541dd | |||
| b4791977c1 | |||
| ef917ecc25 | |||
| a93faad951 | |||
| fd001d24d3 | |||
| 7aa5884797 | |||
| 5b237a1547 | |||
| 2e37990d87 | |||
| dd07d724a8 | |||
| 03ce8618e7 | |||
| db1a7a7fd6 | |||
| 36a82d7f53 | |||
| 3a34401113 | |||
| 9927268330 | |||
| c45c97e29d | |||
| c64a315226 | |||
| a4cafca6ab | |||
| 46284a0660 | |||
| 05df86e15a | |||
| 8b433027e2 | |||
| 5bd4ff7610 | |||
| d693c397ea | |||
| 1d8d1ec9a5 | |||
| 5e491f11ee | |||
| 7cedea06ac | |||
| 2e5f750e50 | |||
| 20289cad10 | |||
| e0d64c31c7 | |||
| 8c1b95dc97 | |||
| fb5641343e | |||
| 87765941eb | |||
| 1809862c16 | |||
| 300f784f7d | |||
| 67a045eae6 | |||
| 2a79903a28 | |||
| d3222ce083 | |||
| 406a421742 | |||
| 10bf728faf | |||
| 607617747c | |||
| f0a69eb1a2 | |||
| 6b307a6e17 | |||
| 08d08a934a | |||
| c500c12668 | |||
| 62060adeba | |||
| b2fc75edb8 | |||
| a999dd2085 | |||
| 49f95ab100 | |||
| 1a84d5b30c | |||
| 3b65050632 | |||
| d0df31674c | |||
| 1fe88402e2 | |||
| 67097696e6 | |||
| 8e7e77067a | |||
| 9899390b61 | |||
| 80c476a908 | |||
| 59da1d6e49 | |||
| 5aef7dac33 | |||
| faf7aa06b6 | |||
| 38ef6e5583 | |||
| c0b15b5d94 | |||
| 2cfc067ea1 | |||
| a91db4f956 | |||
| 8a09780a02 | |||
| 45e8ec6505 | |||
| 4554b85914 | |||
| 8aa79c4a9c | |||
| c8d3210b57 | |||
| 2282a49563 | |||
| b82fdfb2c8 | |||
| 2d17eac199 | |||
| e482bc3aad | |||
| ec022b74d1 | |||
| dc42c09ce3 | |||
| 046a34d2a4 | |||
| 9ff6ec1888 | |||
| d2950106ec | |||
| 962f800d2e | |||
| 962107e507 | |||
| 039bd11963 | |||
| 5c250ea4ae | |||
| e3405bcec6 | |||
| 0fd1c2235f | |||
| b20c29b022 | |||
| 12d5dcd298 | |||
| 2c305dc6c6 | |||
| 62f76f7433 | |||
| 858ce524f9 | |||
| 3795fb4a40 | |||
| 0c01aeec50 | |||
| 892206744d | |||
| 9e2c1474db | |||
| 16328f73d9 | |||
| e0d4f53cf4 | |||
| e09a59c5b4 | |||
| 049e654535 | |||
| c927dc4ecd | |||
| fe4ecd0ad8 | |||
| 78d476fe80 | |||
| a11c8465d5 | |||
| 366304a9b7 | |||
| 4356663688 | |||
| 26b55e6fcf | |||
| 0d743f7204 | |||
| 6cbe113b3e | |||
| 6409b69d6c | |||
| c5164c76fc | |||
| baade8e138 | |||
| b848d6b4e0 | |||
| d8139d2ab0 | |||
| e96d8f7469 | |||
| 2acffd8afc | |||
| 3c8e72073c | |||
| 724d7a9d9b | |||
| 2da3b0db78 | |||
| 685ad7afaf | |||
| 264cf75964 | |||
| c773dbc7b5 | |||
| 37cbc64f52 | |||
| cb1dde17bb | |||
| c29988acf4 | |||
| eadbf56dae | |||
| 4b3b455135 | |||
| e6ac177396 | |||
| 3d0e29003f | |||
| 78b9b00f77 | |||
| 0ee7faa551 | |||
| e5fdced681 | |||
| afb99fef64 | |||
| 7dfaa36024 | |||
| 0496f665aa | |||
| 0d19e1be74 | |||
| 4aff0111aa | |||
| 63b3ba2bb2 | |||
| 7444b41f60 | |||
| 8e90dbc8b6 | |||
| 9f70722521 | |||
| 52fae596fa | |||
| ccb67957bc | |||
| fb82538d0d | |||
| 72ee39612e | |||
| 51fd5408dc | |||
| 3fae40fbef | |||
| 0745890af0 | |||
| 4abe1730a7 | |||
| 626f0e6989 | |||
| 9f42d9d173 | |||
| f90a93c4bc | |||
| 8000ad6c6a | |||
| 1f1f1bea1a | |||
| d95460c7cd | |||
| a3d93d4b08 | |||
| 07a92af982 | |||
| f4618877d4 | |||
| 2b914fd222 | |||
| 109e42a5a3 | |||
| fa515ad39c | |||
| f09673a795 | |||
| f71536c614 | |||
| 7bdddc7ae8 | |||
| aa8926a624 | |||
| be71e59be2 | |||
| 4d7753378f | |||
| 60257c4ef4 | |||
| 1e0b79bf62 | |||
| 6883434d0d | |||
| eda2193e64 | |||
| 99bf829c88 | |||
| 5feafe1b48 | |||
| c9292b7d04 | |||
| ae7e1a91c1 | |||
| 3e1887e0d1 | |||
| 474646db47 | |||
| 56f7b6c449 | |||
| 76b2b5f7e3 | |||
| e918d809eb | |||
| 7af059e543 | |||
| 897726e1ec | |||
| 8b98a2dd07 | |||
| cca75420f0 | |||
| 86c627ed1d | |||
| d55514e3a7 |
@@ -41,8 +41,6 @@ dependencies {
|
||||
runtimeOnly("io.jsonwebtoken:jjwt-impl:0.11.5")
|
||||
runtimeOnly("io.jsonwebtoken:jjwt-jackson:0.11.5")
|
||||
|
||||
implementation("com.nimbusds:nimbus-jose-jwt:9.37.3")
|
||||
|
||||
// querydsl (추가 설정)
|
||||
implementation("com.querydsl:querydsl-jpa:$querydslVersion")
|
||||
kapt("com.querydsl:querydsl-apt:$querydslVersion:jpa")
|
||||
|
||||
@@ -7,6 +7,8 @@ import kr.co.vividnext.sodalive.aws.s3.S3Uploader
|
||||
import kr.co.vividnext.sodalive.common.SodaException
|
||||
import kr.co.vividnext.sodalive.fcm.FcmEvent
|
||||
import kr.co.vividnext.sodalive.fcm.FcmEventType
|
||||
import kr.co.vividnext.sodalive.i18n.LangContext
|
||||
import kr.co.vividnext.sodalive.i18n.SodaMessageSource
|
||||
import kr.co.vividnext.sodalive.utils.generateFileName
|
||||
import org.springframework.beans.factory.annotation.Value
|
||||
import org.springframework.context.ApplicationEventPublisher
|
||||
@@ -22,6 +24,8 @@ class AdminAuditionService(
|
||||
private val repository: AdminAuditionRepository,
|
||||
private val roleRepository: AdminAuditionRoleRepository,
|
||||
private val applicationEventPublisher: ApplicationEventPublisher,
|
||||
private val langContext: LangContext,
|
||||
private val messageSource: SodaMessageSource,
|
||||
|
||||
@Value("\${cloud.aws.s3.bucket}")
|
||||
private val bucket: String
|
||||
@@ -88,12 +92,14 @@ class AdminAuditionService(
|
||||
}
|
||||
|
||||
if (request.status != null && request.status == AuditionStatus.IN_PROGRESS && audition.isActive) {
|
||||
val title = messageSource.getMessage("admin.audition.fcm.title.new", langContext.lang).orEmpty()
|
||||
val messageTemplate = messageSource.getMessage("admin.audition.fcm.message.new", langContext.lang).orEmpty()
|
||||
val message = String.format(messageTemplate, audition.title)
|
||||
applicationEventPublisher.publishEvent(
|
||||
FcmEvent(
|
||||
type = FcmEventType.IN_PROGRESS_AUDITION,
|
||||
titleKey = "admin.audition.fcm.title.new",
|
||||
messageKey = "admin.audition.fcm.message.new",
|
||||
args = listOf(audition.title),
|
||||
title = title,
|
||||
message = message,
|
||||
isAuth = audition.isAdult,
|
||||
auditionId = audition.id ?: -1
|
||||
)
|
||||
|
||||
@@ -148,7 +148,6 @@ class AdminChatCharacterController(
|
||||
runCatching { CharacterType.valueOf(it) }
|
||||
.getOrDefault(CharacterType.Character)
|
||||
} ?: CharacterType.Character,
|
||||
region = request.region,
|
||||
tags = request.tags,
|
||||
values = request.values,
|
||||
hobbies = request.hobbies,
|
||||
@@ -204,7 +203,6 @@ class AdminChatCharacterController(
|
||||
body["name"] = request.name
|
||||
body["systemPrompt"] = request.systemPrompt
|
||||
body["description"] = request.description
|
||||
body["region"] = request.region
|
||||
request.age?.let { body["age"] = it }
|
||||
request.gender?.let { body["gender"] = it }
|
||||
request.mbti?.let { body["mbti"] = it }
|
||||
|
||||
@@ -20,7 +20,6 @@ data class ChatCharacterDetailResponse(
|
||||
val speechPattern: String?,
|
||||
val speechStyle: String?,
|
||||
val appearance: String?,
|
||||
val region: String,
|
||||
val isActive: Boolean,
|
||||
val tags: List<String>,
|
||||
val hobbies: List<String>,
|
||||
@@ -68,7 +67,6 @@ data class ChatCharacterDetailResponse(
|
||||
speechPattern = chatCharacter.speechPattern,
|
||||
speechStyle = chatCharacter.speechStyle,
|
||||
appearance = chatCharacter.appearance,
|
||||
region = chatCharacter.region,
|
||||
isActive = chatCharacter.isActive,
|
||||
tags = chatCharacter.tagMappings.map { it.tag.tag },
|
||||
hobbies = chatCharacter.hobbyMappings.map { it.hobby.hobby },
|
||||
|
||||
@@ -38,7 +38,6 @@ data class ChatCharacterRegisterRequest(
|
||||
@JsonProperty("speechPattern") val speechPattern: String?,
|
||||
@JsonProperty("speechStyle") val speechStyle: String?,
|
||||
@JsonProperty("appearance") val appearance: String?,
|
||||
@JsonProperty("region") val region: String = "KR",
|
||||
@JsonProperty("originalTitle") val originalTitle: String? = null,
|
||||
@JsonProperty("originalLink") val originalLink: String? = null,
|
||||
@JsonProperty("originalWorkId") val originalWorkId: Long? = null,
|
||||
|
||||
@@ -14,7 +14,6 @@ data class ChatCharacterListResponse(
|
||||
val mbti: String?,
|
||||
val speechStyle: String?,
|
||||
val speechPattern: String?,
|
||||
val region: String,
|
||||
val tags: List<String>,
|
||||
val createdAt: String?,
|
||||
val updatedAt: String?
|
||||
@@ -49,7 +48,6 @@ data class ChatCharacterListResponse(
|
||||
mbti = chatCharacter.mbti,
|
||||
speechStyle = chatCharacter.speechStyle,
|
||||
speechPattern = chatCharacter.speechPattern,
|
||||
region = chatCharacter.region,
|
||||
tags = chatCharacter.tagMappings.map { it.tag.tag },
|
||||
createdAt = createdAtStr,
|
||||
updatedAt = updatedAtStr
|
||||
|
||||
@@ -2,15 +2,13 @@ package kr.co.vividnext.sodalive.admin.content
|
||||
|
||||
import kr.co.vividnext.sodalive.common.ApiResponse
|
||||
import org.springframework.data.domain.Pageable
|
||||
import org.springframework.http.MediaType
|
||||
import org.springframework.security.access.prepost.PreAuthorize
|
||||
import org.springframework.web.bind.annotation.GetMapping
|
||||
import org.springframework.web.bind.annotation.PutMapping
|
||||
import org.springframework.web.bind.annotation.RequestBody
|
||||
import org.springframework.web.bind.annotation.RequestMapping
|
||||
import org.springframework.web.bind.annotation.RequestParam
|
||||
import org.springframework.web.bind.annotation.RequestPart
|
||||
import org.springframework.web.bind.annotation.RestController
|
||||
import org.springframework.web.multipart.MultipartFile
|
||||
|
||||
@RestController
|
||||
@PreAuthorize("hasRole('ADMIN')")
|
||||
@@ -40,11 +38,10 @@ class AdminContentController(private val service: AdminContentService) {
|
||||
)
|
||||
)
|
||||
|
||||
@PutMapping(consumes = [MediaType.MULTIPART_FORM_DATA_VALUE])
|
||||
@PutMapping
|
||||
fun modifyAudioContent(
|
||||
@RequestPart("request") requestString: String,
|
||||
@RequestPart("coverImage", required = false) coverImage: MultipartFile? = null
|
||||
) = ApiResponse.ok(service.updateAudioContent(coverImage, requestString))
|
||||
@RequestBody request: UpdateAdminContentRequest
|
||||
) = ApiResponse.ok(service.updateAudioContent(request))
|
||||
|
||||
@GetMapping("/main/tab")
|
||||
fun getContentMainTabList() = ApiResponse.ok(service.getContentMainTabList())
|
||||
|
||||
@@ -1,21 +1,15 @@
|
||||
package kr.co.vividnext.sodalive.admin.content
|
||||
|
||||
import com.amazonaws.services.s3.model.ObjectMetadata
|
||||
import com.fasterxml.jackson.databind.ObjectMapper
|
||||
import kr.co.vividnext.sodalive.admin.content.curation.AdminContentCurationRepository
|
||||
import kr.co.vividnext.sodalive.admin.content.tab.AdminContentMainTabRepository
|
||||
import kr.co.vividnext.sodalive.admin.content.theme.AdminContentThemeRepository
|
||||
import kr.co.vividnext.sodalive.aws.cloudfront.AudioContentCloudFront
|
||||
import kr.co.vividnext.sodalive.aws.s3.S3Uploader
|
||||
import kr.co.vividnext.sodalive.common.SodaException
|
||||
import kr.co.vividnext.sodalive.content.main.tab.GetContentMainTabItem
|
||||
import kr.co.vividnext.sodalive.utils.generateFileName
|
||||
import org.springframework.beans.factory.annotation.Value
|
||||
import org.springframework.data.domain.Pageable
|
||||
import org.springframework.data.repository.findByIdOrNull
|
||||
import org.springframework.stereotype.Service
|
||||
import org.springframework.transaction.annotation.Transactional
|
||||
import org.springframework.web.multipart.MultipartFile
|
||||
|
||||
@Service
|
||||
class AdminContentService(
|
||||
@@ -23,11 +17,7 @@ class AdminContentService(
|
||||
private val themeRepository: AdminContentThemeRepository,
|
||||
private val audioContentCloudFront: AudioContentCloudFront,
|
||||
private val curationRepository: AdminContentCurationRepository,
|
||||
private val contentMainTabRepository: AdminContentMainTabRepository,
|
||||
private val objectMapper: ObjectMapper,
|
||||
private val s3Uploader: S3Uploader,
|
||||
@Value("\${cloud.aws.s3.bucket}")
|
||||
private val bucket: String
|
||||
private val contentMainTabRepository: AdminContentMainTabRepository
|
||||
) {
|
||||
fun getAudioContentList(status: ContentReleaseStatus, pageable: Pageable): GetAdminContentListResponse {
|
||||
val totalCount = repository.getAudioContentTotalCount(status = status)
|
||||
@@ -92,25 +82,12 @@ class AdminContentService(
|
||||
}
|
||||
|
||||
@Transactional
|
||||
fun updateAudioContent(coverImage: MultipartFile?, requestString: String) {
|
||||
val request = objectMapper.readValue(requestString, UpdateAdminContentRequest::class.java)
|
||||
fun updateAudioContent(request: UpdateAdminContentRequest) {
|
||||
val audioContent = repository.findByIdOrNull(id = request.id)
|
||||
?: throw SodaException(messageKey = "admin.content.not_found")
|
||||
|
||||
if (coverImage != null) {
|
||||
val metadata = ObjectMetadata()
|
||||
metadata.contentLength = coverImage.size
|
||||
|
||||
val fileName = generateFileName(prefix = "${request.id}-cover")
|
||||
val imagePath = s3Uploader.upload(
|
||||
inputStream = coverImage.inputStream,
|
||||
bucket = bucket,
|
||||
filePath = "audio_content_cover/${request.id}/$fileName",
|
||||
metadata = metadata
|
||||
)
|
||||
audioContent.coverImage = imagePath
|
||||
} else if (request.isDefaultCoverImage) {
|
||||
audioContent.coverImage = "profile/default_profile.png"
|
||||
if (request.isDefaultCoverImage) {
|
||||
audioContent.coverImage = "`profile/default_profile.png`"
|
||||
}
|
||||
|
||||
if (request.isActive != null) {
|
||||
|
||||
@@ -321,17 +321,20 @@ class AdminLiveService(
|
||||
}
|
||||
|
||||
// 예약현황 취소
|
||||
val pushTokens = memberRepository.getPushTokenFromReservationList(room.id!!)
|
||||
val pushTokenListMap = memberRepository.getPushTokenFromReservationList(room.id!!)
|
||||
reservationRepository.cancelReservation(roomId = room.id!!)
|
||||
|
||||
// 라이브 취소 푸시 발송
|
||||
val cancelMessageTemplate = messageSource
|
||||
.getMessage("live.room.fcm.message.canceled", langContext.lang)
|
||||
.orEmpty()
|
||||
val cancelMessage = String.format(cancelMessageTemplate, room.title)
|
||||
applicationEventPublisher.publishEvent(
|
||||
FcmEvent(
|
||||
type = FcmEventType.CANCEL_LIVE,
|
||||
title = room.member!!.nickname,
|
||||
messageKey = "live.room.fcm.message.canceled",
|
||||
args = listOf(room.title),
|
||||
pushTokens = pushTokens
|
||||
message = cancelMessage,
|
||||
recipientsMap = pushTokenListMap
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
@@ -108,7 +108,6 @@ class AdminMemberService(
|
||||
MemberProvider.KAKAO -> messageSource.getMessage("member.provider.kakao", langContext.lang).orEmpty()
|
||||
MemberProvider.GOOGLE -> messageSource.getMessage("member.provider.google", langContext.lang).orEmpty()
|
||||
MemberProvider.APPLE -> messageSource.getMessage("member.provider.apple", langContext.lang).orEmpty()
|
||||
MemberProvider.LINE -> messageSource.getMessage("member.provider.line", langContext.lang).orEmpty()
|
||||
}
|
||||
|
||||
val signUpDate = it.createdAt!!
|
||||
@@ -127,7 +126,7 @@ class AdminMemberService(
|
||||
|
||||
GetAdminMemberListResponseItem(
|
||||
id = it.id!!,
|
||||
email = it.email ?: "",
|
||||
email = it.email,
|
||||
nickname = it.nickname,
|
||||
profileUrl = if (it.profileImage != null) {
|
||||
"$cloudFrontHost/${it.profileImage}"
|
||||
@@ -161,7 +160,6 @@ class AdminMemberService(
|
||||
val member = repository.findByIdAndActive(memberId = request.memberId)
|
||||
?: throw SodaException(messageKey = "admin.member.reset_password_invalid")
|
||||
|
||||
val email = member.email ?: throw SodaException(message = "이메일이 없는 계정은 비밀번호 재설정이 불가능합니다.")
|
||||
member.password = passwordEncoder.encode(email.split("@")[0])
|
||||
member.password = passwordEncoder.encode(member.email.split("@")[0])
|
||||
}
|
||||
}
|
||||
|
||||
@@ -68,32 +68,6 @@ class AdminMemberStatisticsRepository(private val queryFactory: JPAQueryFactory)
|
||||
.size
|
||||
}
|
||||
|
||||
fun getTotalSignUpAppleCount(startDate: LocalDateTime, endDate: LocalDateTime): Int {
|
||||
return queryFactory
|
||||
.select(member.id)
|
||||
.from(member)
|
||||
.where(
|
||||
member.createdAt.goe(startDate),
|
||||
member.createdAt.loe(endDate),
|
||||
member.provider.eq(MemberProvider.APPLE)
|
||||
)
|
||||
.fetch()
|
||||
.size
|
||||
}
|
||||
|
||||
fun getTotalSignUpLineCount(startDate: LocalDateTime, endDate: LocalDateTime): Int {
|
||||
return queryFactory
|
||||
.select(member.id)
|
||||
.from(member)
|
||||
.where(
|
||||
member.createdAt.goe(startDate),
|
||||
member.createdAt.loe(endDate),
|
||||
member.provider.eq(MemberProvider.LINE)
|
||||
)
|
||||
.fetch()
|
||||
.size
|
||||
}
|
||||
|
||||
fun getTotalAuthCount(startDate: LocalDateTime, endDate: LocalDateTime): Int {
|
||||
return queryFactory
|
||||
.select(auth.id)
|
||||
@@ -215,44 +189,6 @@ class AdminMemberStatisticsRepository(private val queryFactory: JPAQueryFactory)
|
||||
.fetch()
|
||||
}
|
||||
|
||||
fun getSignUpAppleCountInRange(startDate: LocalDateTime, endDate: LocalDateTime): List<DateAndMemberCount> {
|
||||
return queryFactory
|
||||
.select(
|
||||
QDateAndMemberCount(
|
||||
getFormattedDate(member.createdAt),
|
||||
member.id.countDistinct().castToNum(Int::class.java)
|
||||
)
|
||||
)
|
||||
.from(member)
|
||||
.where(
|
||||
member.createdAt.goe(startDate),
|
||||
member.createdAt.loe(endDate),
|
||||
member.provider.eq(MemberProvider.APPLE)
|
||||
)
|
||||
.groupBy(getFormattedDate(member.createdAt))
|
||||
.orderBy(getFormattedDate(member.createdAt).desc())
|
||||
.fetch()
|
||||
}
|
||||
|
||||
fun getSignUpLineCountInRange(startDate: LocalDateTime, endDate: LocalDateTime): List<DateAndMemberCount> {
|
||||
return queryFactory
|
||||
.select(
|
||||
QDateAndMemberCount(
|
||||
getFormattedDate(member.createdAt),
|
||||
member.id.countDistinct().castToNum(Int::class.java)
|
||||
)
|
||||
)
|
||||
.from(member)
|
||||
.where(
|
||||
member.createdAt.goe(startDate),
|
||||
member.createdAt.loe(endDate),
|
||||
member.provider.eq(MemberProvider.LINE)
|
||||
)
|
||||
.groupBy(getFormattedDate(member.createdAt))
|
||||
.orderBy(getFormattedDate(member.createdAt).desc())
|
||||
.fetch()
|
||||
}
|
||||
|
||||
fun getAuthCountInRange(startDate: LocalDateTime, endDate: LocalDateTime): List<DateAndMemberCount> {
|
||||
return queryFactory
|
||||
.select(
|
||||
|
||||
@@ -58,14 +58,6 @@ class AdminMemberStatisticsService(private val repository: AdminMemberStatistics
|
||||
startDate = startDateTime,
|
||||
endDate = endDateTime
|
||||
)
|
||||
val totalSignUpAppleCount = repository.getTotalSignUpAppleCount(
|
||||
startDate = startDateTime,
|
||||
endDate = endDateTime
|
||||
)
|
||||
val totalSignUpLineCount = repository.getTotalSignUpLineCount(
|
||||
startDate = startDateTime,
|
||||
endDate = endDateTime
|
||||
)
|
||||
val totalAuthCount = repository.getTotalAuthCount(startDate = startDateTime, endDate = endDateTime)
|
||||
val totalSignOutCount = repository.getTotalSignOutCount(startDate = startDateTime, endDate = endDateTime)
|
||||
val totalPaymentMemberCount = repository.getPaymentMemberCount(startDate = startDateTime, endDate = endDateTime)
|
||||
@@ -100,16 +92,6 @@ class AdminMemberStatisticsService(private val repository: AdminMemberStatistics
|
||||
endDate = endDateTime
|
||||
).associateBy({ it.date }, { it.memberCount })
|
||||
|
||||
val signUpAppleCountInRange = repository.getSignUpAppleCountInRange(
|
||||
startDate = startDateTime,
|
||||
endDate = endDateTime
|
||||
).associateBy({ it.date }, { it.memberCount })
|
||||
|
||||
val signUpLineCountInRange = repository.getSignUpLineCountInRange(
|
||||
startDate = startDateTime,
|
||||
endDate = endDateTime
|
||||
).associateBy({ it.date }, { it.memberCount })
|
||||
|
||||
val authCountInRange = repository.getAuthCountInRange(
|
||||
startDate = startDateTime,
|
||||
endDate = endDateTime
|
||||
@@ -139,8 +121,6 @@ class AdminMemberStatisticsService(private val repository: AdminMemberStatistics
|
||||
signUpEmailCount = signUpEmailCountInRange[date] ?: 0,
|
||||
signUpKakaoCount = signUpKakaoCountInRange[date] ?: 0,
|
||||
signUpGoogleCount = signUpGoogleCountInRange[date] ?: 0,
|
||||
signUpAppleCount = signUpAppleCountInRange[date] ?: 0,
|
||||
signUpLineCount = signUpLineCountInRange[date] ?: 0,
|
||||
signOutCount = signOutCountInRange[date] ?: 0,
|
||||
paymentMemberCount = paymentMemberCountInRangeMap[date] ?: 0
|
||||
)
|
||||
@@ -154,8 +134,6 @@ class AdminMemberStatisticsService(private val repository: AdminMemberStatistics
|
||||
totalSignUpEmailCount = totalSignUpEmailCount,
|
||||
totalSignUpKakaoCount = totalSignUpKakaoCount,
|
||||
totalSignUpGoogleCount = totalSignUpGoogleCount,
|
||||
totalSignUpAppleCount = totalSignUpAppleCount,
|
||||
totalSignUpLineCount = totalSignUpLineCount,
|
||||
totalSignOutCount = totalSignOutCount,
|
||||
totalPaymentMemberCount = totalPaymentMemberCount,
|
||||
items = items
|
||||
|
||||
@@ -7,8 +7,6 @@ data class GetMemberStatisticsResponse(
|
||||
val totalSignUpEmailCount: Int,
|
||||
val totalSignUpKakaoCount: Int,
|
||||
val totalSignUpGoogleCount: Int,
|
||||
val totalSignUpAppleCount: Int,
|
||||
val totalSignUpLineCount: Int,
|
||||
val totalSignOutCount: Int,
|
||||
val totalPaymentMemberCount: Int,
|
||||
val items: List<GetMemberStatisticsItem>
|
||||
@@ -21,8 +19,6 @@ data class GetMemberStatisticsItem(
|
||||
val signUpEmailCount: Int,
|
||||
val signUpKakaoCount: Int,
|
||||
val signUpGoogleCount: Int,
|
||||
val signUpAppleCount: Int,
|
||||
val signUpLineCount: Int,
|
||||
val signOutCount: Int,
|
||||
val paymentMemberCount: Int
|
||||
)
|
||||
|
||||
@@ -34,14 +34,15 @@ class RtcTokenBuilder {
|
||||
appId: String,
|
||||
appCertificate: String,
|
||||
channelName: String,
|
||||
uid: String,
|
||||
uid: Int,
|
||||
privilegeTs: Int
|
||||
): String {
|
||||
val account = if (uid == 0) "" else uid.toString()
|
||||
return buildTokenWithUserAccount(
|
||||
appId,
|
||||
appCertificate,
|
||||
channelName,
|
||||
uid,
|
||||
account,
|
||||
privilegeTs
|
||||
)
|
||||
}
|
||||
|
||||
@@ -106,8 +106,7 @@ class HomeService(
|
||||
|
||||
val latestContentThemeList = contentThemeService.getActiveThemeOfContent(
|
||||
isAdult = isAdult,
|
||||
contentType = contentType,
|
||||
excludeThemes = listOf("다시듣기")
|
||||
contentType = contentType
|
||||
)
|
||||
|
||||
val latestContentList = contentService.getLatestContentByTheme(
|
||||
@@ -317,9 +316,8 @@ class HomeService(
|
||||
val themeList = if (theme.isBlank()) {
|
||||
contentThemeService.getActiveThemeOfContent(
|
||||
isAdult = isAdult,
|
||||
isFree = false,
|
||||
contentType = contentType,
|
||||
excludeThemes = listOf("다시듣기")
|
||||
isFree = true,
|
||||
contentType = contentType
|
||||
)
|
||||
} else {
|
||||
listOf(theme)
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package kr.co.vividnext.sodalive.can
|
||||
|
||||
import kr.co.vividnext.sodalive.common.ApiResponse
|
||||
import kr.co.vividnext.sodalive.common.GeoCountry
|
||||
import kr.co.vividnext.sodalive.common.SodaException
|
||||
import kr.co.vividnext.sodalive.member.Member
|
||||
import org.springframework.data.domain.Pageable
|
||||
@@ -9,16 +10,15 @@ import org.springframework.web.bind.annotation.GetMapping
|
||||
import org.springframework.web.bind.annotation.RequestMapping
|
||||
import org.springframework.web.bind.annotation.RequestParam
|
||||
import org.springframework.web.bind.annotation.RestController
|
||||
import javax.servlet.http.HttpServletRequest
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/can")
|
||||
class CanController(private val service: CanService) {
|
||||
@GetMapping
|
||||
fun getCans(
|
||||
@AuthenticationPrincipal(expression = "#this == 'anonymousUser' ? null : member") member: Member?
|
||||
): ApiResponse<List<CanResponse>> {
|
||||
val isNotSelectedCurrency = member != null && member.id == 2L
|
||||
return ApiResponse.ok(service.getCans(isNotSelectedCurrency = isNotSelectedCurrency))
|
||||
fun getCans(request: HttpServletRequest): ApiResponse<List<CanResponse>> {
|
||||
val geoCountry = request.getAttribute("geoCountry") as? GeoCountry ?: GeoCountry.OTHER
|
||||
return ApiResponse.ok(service.getCans(geoCountry))
|
||||
}
|
||||
|
||||
@GetMapping("/status")
|
||||
|
||||
@@ -23,7 +23,7 @@ import org.springframework.stereotype.Repository
|
||||
interface CanRepository : JpaRepository<Can, Long>, CanQueryRepository
|
||||
|
||||
interface CanQueryRepository {
|
||||
fun findAllByStatusAndCurrency(status: CanStatus, currency: String?): List<CanResponse>
|
||||
fun findAllByStatusAndCurrency(status: CanStatus, currency: String): List<CanResponse>
|
||||
fun getCanUseStatus(member: Member, pageable: Pageable): List<UseCan>
|
||||
fun getCanChargeStatus(member: Member, pageable: Pageable, container: String): List<Charge>
|
||||
fun isExistPaidLiveRoom(memberId: Long, roomId: Long): UseCan?
|
||||
@@ -32,13 +32,7 @@ interface CanQueryRepository {
|
||||
|
||||
@Repository
|
||||
class CanQueryRepositoryImpl(private val queryFactory: JPAQueryFactory) : CanQueryRepository {
|
||||
override fun findAllByStatusAndCurrency(status: CanStatus, currency: String?): List<CanResponse> {
|
||||
var where = can1.status.eq(status)
|
||||
|
||||
if (currency != null) {
|
||||
where = where.and(can1.currency.eq(currency))
|
||||
}
|
||||
|
||||
override fun findAllByStatusAndCurrency(status: CanStatus, currency: String): List<CanResponse> {
|
||||
return queryFactory
|
||||
.select(
|
||||
QCanResponse(
|
||||
@@ -52,7 +46,10 @@ class CanQueryRepositoryImpl(private val queryFactory: JPAQueryFactory) : CanQue
|
||||
)
|
||||
)
|
||||
.from(can1)
|
||||
.where(where)
|
||||
.where(
|
||||
can1.status.eq(status),
|
||||
can1.currency.eq(currency)
|
||||
)
|
||||
.orderBy(can1.can.asc())
|
||||
.fetch()
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@ package kr.co.vividnext.sodalive.can
|
||||
import kr.co.vividnext.sodalive.can.charge.ChargeStatus
|
||||
import kr.co.vividnext.sodalive.can.payment.PaymentGateway
|
||||
import kr.co.vividnext.sodalive.can.use.CanUsage
|
||||
import kr.co.vividnext.sodalive.common.CountryContext
|
||||
import kr.co.vividnext.sodalive.common.GeoCountry
|
||||
import kr.co.vividnext.sodalive.member.Member
|
||||
import org.springframework.data.domain.Pageable
|
||||
import org.springframework.stereotype.Service
|
||||
@@ -11,18 +11,11 @@ import java.time.ZoneId
|
||||
import java.time.format.DateTimeFormatter
|
||||
|
||||
@Service
|
||||
class CanService(
|
||||
private val repository: CanRepository,
|
||||
private val countryContext: CountryContext
|
||||
) {
|
||||
fun getCans(isNotSelectedCurrency: Boolean): List<CanResponse> {
|
||||
val currency = if (isNotSelectedCurrency) {
|
||||
null
|
||||
} else {
|
||||
when (countryContext.countryCode) {
|
||||
"KR" -> "KRW"
|
||||
else -> "USD"
|
||||
}
|
||||
class CanService(private val repository: CanRepository) {
|
||||
fun getCans(geoCountry: GeoCountry): List<CanResponse> {
|
||||
val currency = when (geoCountry) {
|
||||
GeoCountry.KR -> "KRW"
|
||||
else -> "USD"
|
||||
}
|
||||
return repository.findAllByStatusAndCurrency(status = CanStatus.SALE, currency = currency)
|
||||
}
|
||||
|
||||
@@ -9,6 +9,8 @@ import kr.co.vividnext.sodalive.can.payment.PaymentStatus
|
||||
import kr.co.vividnext.sodalive.common.SodaException
|
||||
import kr.co.vividnext.sodalive.fcm.FcmEvent
|
||||
import kr.co.vividnext.sodalive.fcm.FcmEventType
|
||||
import kr.co.vividnext.sodalive.i18n.LangContext
|
||||
import kr.co.vividnext.sodalive.i18n.SodaMessageSource
|
||||
import kr.co.vividnext.sodalive.member.Member
|
||||
import kr.co.vividnext.sodalive.member.MemberRepository
|
||||
import kr.co.vividnext.sodalive.member.auth.AuthRepository
|
||||
@@ -26,7 +28,9 @@ class ChargeEventService(
|
||||
private val memberRepository: MemberRepository,
|
||||
private val chargeRepository: ChargeRepository,
|
||||
private val chargeEventRepository: ChargeEventRepository,
|
||||
private val applicationEventPublisher: ApplicationEventPublisher
|
||||
private val applicationEventPublisher: ApplicationEventPublisher,
|
||||
private val messageSource: SodaMessageSource,
|
||||
private val langContext: LangContext
|
||||
) {
|
||||
@Transactional
|
||||
fun applyChargeEvent(chargeId: Long, memberId: Long) {
|
||||
@@ -79,8 +83,10 @@ class ChargeEventService(
|
||||
FcmEvent(
|
||||
type = FcmEventType.INDIVIDUAL,
|
||||
title = chargeEvent.title,
|
||||
messageKey = "can.charge.event.additional_can_paid",
|
||||
args = listOf(additionalCan),
|
||||
message = formatMessage(
|
||||
"can.charge.event.additional_can_paid",
|
||||
additionalCan
|
||||
),
|
||||
recipients = listOf(member.id!!),
|
||||
isAuth = null
|
||||
)
|
||||
@@ -95,15 +101,21 @@ class ChargeEventService(
|
||||
additionalCan = additionalCan,
|
||||
member = member,
|
||||
paymentGateway = charge.payment?.paymentGateway!!,
|
||||
method = "첫 충전 이벤트"
|
||||
method = messageSource
|
||||
.getMessage("can.charge.event.first_title", langContext.lang)
|
||||
.orEmpty()
|
||||
)
|
||||
|
||||
applicationEventPublisher.publishEvent(
|
||||
FcmEvent(
|
||||
type = FcmEventType.INDIVIDUAL,
|
||||
titleKey = "can.charge.event.first_title",
|
||||
messageKey = "can.charge.event.additional_can_paid",
|
||||
args = listOf(additionalCan),
|
||||
title = messageSource
|
||||
.getMessage("can.charge.event.first_title", langContext.lang)
|
||||
.orEmpty(),
|
||||
message = formatMessage(
|
||||
"can.charge.event.additional_can_paid",
|
||||
additionalCan
|
||||
),
|
||||
recipients = listOf(member.id!!),
|
||||
isAuth = null
|
||||
)
|
||||
@@ -112,7 +124,7 @@ class ChargeEventService(
|
||||
|
||||
private fun applyEvent(additionalCan: Int, member: Member, paymentGateway: PaymentGateway, method: String) {
|
||||
val eventCharge = Charge(0, additionalCan, status = ChargeStatus.EVENT)
|
||||
eventCharge.title = "$additionalCan 캔"
|
||||
eventCharge.title = formatMessage("can.charge.title", additionalCan)
|
||||
eventCharge.member = member
|
||||
|
||||
val payment = Payment(
|
||||
@@ -129,4 +141,9 @@ class ChargeEventService(
|
||||
else -> member.charge(0, additionalCan, "pg")
|
||||
}
|
||||
}
|
||||
|
||||
private fun formatMessage(key: String, vararg args: Any): String {
|
||||
val template = messageSource.getMessage(key, langContext.lang) ?: return ""
|
||||
return String.format(template, *args)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ import kr.co.vividnext.sodalive.common.ApiResponse
|
||||
import kr.co.vividnext.sodalive.common.SodaException
|
||||
import kr.co.vividnext.sodalive.member.Member
|
||||
import org.springframework.security.core.annotation.AuthenticationPrincipal
|
||||
import org.springframework.security.core.userdetails.User
|
||||
import org.springframework.web.bind.annotation.PostMapping
|
||||
import org.springframework.web.bind.annotation.RequestBody
|
||||
import org.springframework.web.bind.annotation.RequestMapping
|
||||
@@ -28,12 +29,6 @@ class ChargeTempController(private val service: ChargeTempService) {
|
||||
@PostMapping("/verify")
|
||||
fun verify(
|
||||
@RequestBody request: VerifyRequest,
|
||||
@AuthenticationPrincipal(expression = "#this == 'anonymousUser' ? null : member") member: Member?
|
||||
) = run {
|
||||
if (member == null) {
|
||||
throw SodaException(messageKey = "common.error.bad_credentials")
|
||||
}
|
||||
|
||||
ApiResponse.ok(service.verify(member, request))
|
||||
}
|
||||
@AuthenticationPrincipal user: User
|
||||
) = ApiResponse.ok(service.verify(user, request))
|
||||
}
|
||||
|
||||
@@ -15,8 +15,10 @@ import kr.co.vividnext.sodalive.extensions.moneyFormat
|
||||
import kr.co.vividnext.sodalive.i18n.LangContext
|
||||
import kr.co.vividnext.sodalive.i18n.SodaMessageSource
|
||||
import kr.co.vividnext.sodalive.member.Member
|
||||
import kr.co.vividnext.sodalive.member.MemberRepository
|
||||
import org.springframework.beans.factory.annotation.Value
|
||||
import org.springframework.data.repository.findByIdOrNull
|
||||
import org.springframework.security.core.userdetails.User
|
||||
import org.springframework.stereotype.Service
|
||||
import org.springframework.transaction.annotation.Transactional
|
||||
|
||||
@@ -24,6 +26,7 @@ import org.springframework.transaction.annotation.Transactional
|
||||
@Transactional(readOnly = true)
|
||||
class ChargeTempService(
|
||||
private val chargeRepository: ChargeRepository,
|
||||
private val memberRepository: MemberRepository,
|
||||
|
||||
private val objectMapper: ObjectMapper,
|
||||
private val messageSource: SodaMessageSource,
|
||||
@@ -51,9 +54,11 @@ class ChargeTempService(
|
||||
}
|
||||
|
||||
@Transactional
|
||||
fun verify(member: Member, verifyRequest: VerifyRequest) {
|
||||
fun verify(user: User, verifyRequest: VerifyRequest) {
|
||||
val charge = chargeRepository.findByIdOrNull(verifyRequest.orderId.toLong())
|
||||
?: throw SodaException(messageKey = "can.charge.invalid_payment_info")
|
||||
val member = memberRepository.findByEmail(user.username)
|
||||
?: throw SodaException(messageKey = "common.error.bad_credentials")
|
||||
|
||||
if (charge.payment!!.paymentGateway == PaymentGateway.PG) {
|
||||
val bootpay = Bootpay(bootpayApplicationId, bootpayPrivateKey)
|
||||
|
||||
@@ -14,7 +14,6 @@ import kr.co.vividnext.sodalive.can.use.UseCanCalculateRepository
|
||||
import kr.co.vividnext.sodalive.can.use.UseCanCalculateStatus
|
||||
import kr.co.vividnext.sodalive.can.use.UseCanRepository
|
||||
import kr.co.vividnext.sodalive.chat.character.image.CharacterImage
|
||||
import kr.co.vividnext.sodalive.common.CountryContext
|
||||
import kr.co.vividnext.sodalive.common.SodaException
|
||||
import kr.co.vividnext.sodalive.content.AudioContent
|
||||
import kr.co.vividnext.sodalive.content.order.Order
|
||||
@@ -36,8 +35,7 @@ class CanPaymentService(
|
||||
private val useCanRepository: UseCanRepository,
|
||||
private val useCanCalculateRepository: UseCanCalculateRepository,
|
||||
private val messageSource: SodaMessageSource,
|
||||
private val langContext: LangContext,
|
||||
private val countryContext: CountryContext
|
||||
private val langContext: LangContext
|
||||
) {
|
||||
@Transactional
|
||||
fun spendCan(
|
||||
@@ -78,8 +76,7 @@ class CanPaymentService(
|
||||
canUsage = canUsage,
|
||||
can = useChargeCan?.total ?: 0,
|
||||
rewardCan = useRewardCan.total,
|
||||
isSecret = isSecret,
|
||||
countryCode = countryContext.countryCode
|
||||
isSecret = isSecret
|
||||
)
|
||||
|
||||
var recipientId: Long? = null
|
||||
@@ -381,8 +378,7 @@ class CanPaymentService(
|
||||
canUsage = CanUsage.CHARACTER_IMAGE_PURCHASE,
|
||||
can = useChargeCan?.total ?: 0,
|
||||
rewardCan = useRewardCan.total,
|
||||
isSecret = false,
|
||||
countryCode = countryContext.countryCode
|
||||
isSecret = false
|
||||
)
|
||||
useCan.member = member
|
||||
useCan.characterImage = image
|
||||
@@ -428,8 +424,7 @@ class CanPaymentService(
|
||||
canUsage = CanUsage.CHAT_MESSAGE_PURCHASE,
|
||||
can = useChargeCan?.total ?: 0,
|
||||
rewardCan = useRewardCan.total,
|
||||
isSecret = false,
|
||||
countryCode = countryContext.countryCode
|
||||
isSecret = false
|
||||
)
|
||||
useCan.member = member
|
||||
useCan.chatMessage = message
|
||||
|
||||
@@ -34,10 +34,7 @@ data class UseCan(
|
||||
|
||||
// 채팅 연동을 위한 식별자 (옵션)
|
||||
var chatRoomId: Long? = null,
|
||||
var characterId: Long? = null,
|
||||
|
||||
// ISO 3166-1 alpha-2 국가 코드
|
||||
var countryCode: String? = null
|
||||
var characterId: Long? = null
|
||||
) : BaseEntity() {
|
||||
@ManyToOne(fetch = FetchType.LAZY)
|
||||
@JoinColumn(name = "member_id", nullable = false)
|
||||
|
||||
@@ -67,10 +67,6 @@ class ChatCharacter(
|
||||
@Column(nullable = false)
|
||||
var characterType: CharacterType = CharacterType.Character,
|
||||
|
||||
// 리전 (기본값 KR, 수정 불가)
|
||||
@Column(nullable = false)
|
||||
val region: String = "KR",
|
||||
|
||||
var isActive: Boolean = true
|
||||
) : BaseEntity() {
|
||||
var imagePath: String? = null
|
||||
|
||||
@@ -582,7 +582,6 @@ class ChatCharacterService(
|
||||
originalTitle: String? = null,
|
||||
originalLink: String? = null,
|
||||
characterType: CharacterType = CharacterType.Character,
|
||||
region: String = "KR",
|
||||
tags: List<String> = emptyList(),
|
||||
values: List<String> = emptyList(),
|
||||
hobbies: List<String> = emptyList(),
|
||||
@@ -601,8 +600,7 @@ class ChatCharacterService(
|
||||
appearance = appearance,
|
||||
originalTitle = originalTitle,
|
||||
originalLink = originalLink,
|
||||
characterType = characterType,
|
||||
region = region
|
||||
characterType = characterType
|
||||
)
|
||||
|
||||
// 관련 엔티티 연결
|
||||
@@ -632,7 +630,6 @@ class ChatCharacterService(
|
||||
originalTitle: String? = null,
|
||||
originalLink: String? = null,
|
||||
characterType: CharacterType = CharacterType.Character,
|
||||
region: String = "KR",
|
||||
tags: List<String> = emptyList(),
|
||||
values: List<String> = emptyList(),
|
||||
hobbies: List<String> = emptyList(),
|
||||
@@ -656,7 +653,6 @@ class ChatCharacterService(
|
||||
originalTitle = originalTitle,
|
||||
originalLink = originalLink,
|
||||
characterType = characterType,
|
||||
region = region,
|
||||
tags = tags,
|
||||
values = values,
|
||||
hobbies = hobbies,
|
||||
|
||||
@@ -1,15 +0,0 @@
|
||||
package kr.co.vividnext.sodalive.common
|
||||
|
||||
import org.springframework.stereotype.Component
|
||||
import org.springframework.web.context.annotation.RequestScope
|
||||
|
||||
@Component
|
||||
@RequestScope
|
||||
class CountryContext {
|
||||
var countryCode: String? = null
|
||||
internal set
|
||||
|
||||
fun setCountryCode(code: String?) {
|
||||
this.countryCode = code
|
||||
}
|
||||
}
|
||||
@@ -1,21 +0,0 @@
|
||||
package kr.co.vividnext.sodalive.common
|
||||
|
||||
import org.springframework.stereotype.Component
|
||||
import org.springframework.web.servlet.HandlerInterceptor
|
||||
import javax.servlet.http.HttpServletRequest
|
||||
import javax.servlet.http.HttpServletResponse
|
||||
|
||||
@Component
|
||||
class CountryInterceptor(
|
||||
private val countryContext: CountryContext
|
||||
) : HandlerInterceptor {
|
||||
override fun preHandle(
|
||||
request: HttpServletRequest,
|
||||
response: HttpServletResponse,
|
||||
handler: Any
|
||||
): Boolean {
|
||||
val countryCode = request.getHeader("CloudFront-Viewer-Country")
|
||||
countryContext.setCountryCode(countryCode)
|
||||
return true
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
package kr.co.vividnext.sodalive.common
|
||||
|
||||
const val WAF_GEO_HEADER = "x-amzn-waf-geo-country"
|
||||
|
||||
enum class GeoCountry { KR, OTHER }
|
||||
|
||||
fun parseGeo(headerValue: String?): GeoCountry =
|
||||
if (headerValue?.trim()?.uppercase() == "KR") GeoCountry.KR else GeoCountry.OTHER
|
||||
@@ -0,0 +1,20 @@
|
||||
package kr.co.vividnext.sodalive.common
|
||||
|
||||
import org.springframework.stereotype.Component
|
||||
import org.springframework.web.filter.OncePerRequestFilter
|
||||
import javax.servlet.FilterChain
|
||||
import javax.servlet.http.HttpServletRequest
|
||||
import javax.servlet.http.HttpServletResponse
|
||||
|
||||
@Component
|
||||
class GeoCountryFilter : OncePerRequestFilter() {
|
||||
override fun doFilterInternal(
|
||||
request: HttpServletRequest,
|
||||
response: HttpServletResponse,
|
||||
filterChain: FilterChain
|
||||
) {
|
||||
val country = parseGeo(request.getHeader(WAF_GEO_HEADER))
|
||||
request.setAttribute("geoCountry", country)
|
||||
filterChain.doFilter(request, response)
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,5 @@
|
||||
package kr.co.vividnext.sodalive.common
|
||||
|
||||
import kr.co.vividnext.sodalive.i18n.Lang
|
||||
import kr.co.vividnext.sodalive.i18n.LangContext
|
||||
import kr.co.vividnext.sodalive.i18n.SodaMessageSource
|
||||
import org.slf4j.LoggerFactory
|
||||
@@ -21,14 +20,10 @@ class SodaExceptionHandler(
|
||||
private val messageSource: SodaMessageSource
|
||||
) {
|
||||
private val logger = LoggerFactory.getLogger(this::class.java)
|
||||
private val logLang = Lang.KO
|
||||
|
||||
@ExceptionHandler(SodaException::class)
|
||||
fun handleSodaException(e: SodaException) = run {
|
||||
val logMessage = e.messageKey?.takeIf { it.isNotBlank() }?.let { messageSource.getMessage(it, logLang) }
|
||||
?: e.message?.takeIf { it.isNotBlank() }
|
||||
?: messageSource.getMessage("common.error.unknown", logLang)
|
||||
logger.error("API error: {}", logMessage, e)
|
||||
logger.error("API error", e)
|
||||
val message = e.messageKey?.takeIf { it.isNotBlank() }?.let { messageSource.getMessage(it, langContext.lang) }
|
||||
?: e.message?.takeIf { it.isNotBlank() }
|
||||
?: messageSource.getMessage("common.error.unknown", langContext.lang)
|
||||
@@ -40,40 +35,35 @@ class SodaExceptionHandler(
|
||||
|
||||
@ExceptionHandler(MaxUploadSizeExceededException::class)
|
||||
fun handleMaxUploadSizeExceededException(e: MaxUploadSizeExceededException) = run {
|
||||
val logMessage = messageSource.getMessage("common.error.max_upload_size", logLang)
|
||||
logger.error("API error: {}", logMessage, e)
|
||||
logger.error("API error", e)
|
||||
val message = messageSource.getMessage("common.error.max_upload_size", langContext.lang)
|
||||
ApiResponse.error(message = message)
|
||||
}
|
||||
|
||||
@ExceptionHandler(AccessDeniedException::class)
|
||||
fun handleAccessDeniedException(e: AccessDeniedException) = run {
|
||||
val logMessage = messageSource.getMessage("common.error.access_denied", logLang)
|
||||
logger.error("API error: {}", logMessage, e)
|
||||
logger.error("API error", e)
|
||||
val message = messageSource.getMessage("common.error.access_denied", langContext.lang)
|
||||
ApiResponse.error(message = message)
|
||||
}
|
||||
|
||||
@ExceptionHandler(InternalAuthenticationServiceException::class)
|
||||
fun handleInternalAuthenticationServiceException(e: InternalAuthenticationServiceException) = run {
|
||||
val logMessage = messageSource.getMessage("common.error.bad_credentials", logLang)
|
||||
logger.error("API error: {}", logMessage, e)
|
||||
logger.error("API error", e)
|
||||
val message = messageSource.getMessage("common.error.bad_credentials", langContext.lang)
|
||||
ApiResponse.error(message)
|
||||
}
|
||||
|
||||
@ExceptionHandler(BadCredentialsException::class)
|
||||
fun handleBadCredentialsException(e: BadCredentialsException) = run {
|
||||
val logMessage = messageSource.getMessage("common.error.bad_credentials", logLang)
|
||||
logger.error("API error: {}", logMessage, e)
|
||||
logger.error("API error", e)
|
||||
val message = messageSource.getMessage("common.error.bad_credentials", langContext.lang)
|
||||
ApiResponse.error(message)
|
||||
}
|
||||
|
||||
@ExceptionHandler(DataIntegrityViolationException::class)
|
||||
fun handleDataIntegrityViolationException(e: DataIntegrityViolationException) = run {
|
||||
val logMessage = messageSource.getMessage("common.error.already_registered", logLang)
|
||||
logger.error("API error: {}", logMessage, e)
|
||||
logger.error("API error", e)
|
||||
val message = messageSource.getMessage("common.error.already_registered", langContext.lang)
|
||||
ApiResponse.error(message)
|
||||
}
|
||||
@@ -81,10 +71,7 @@ class SodaExceptionHandler(
|
||||
@ResponseStatus(value = HttpStatus.NOT_FOUND)
|
||||
@ExceptionHandler(AdsChargeException::class)
|
||||
fun handleAdsChargeException(e: AdsChargeException) = run {
|
||||
val logMessage = e.messageKey?.takeIf { it.isNotBlank() }?.let { messageSource.getMessage(it, logLang) }
|
||||
?: e.message?.takeIf { it.isNotBlank() }
|
||||
?: messageSource.getMessage("common.error.invalid_request", logLang)
|
||||
logger.error("API error - AdsChargeException: {}", logMessage, e)
|
||||
logger.error("API error - AdsChargeException ::: ", e)
|
||||
val message = e.messageKey?.takeIf { it.isNotBlank() }?.let { messageSource.getMessage(it, langContext.lang) }
|
||||
?: e.message?.takeIf { it.isNotBlank() }
|
||||
?: messageSource.getMessage("common.error.invalid_request", langContext.lang)
|
||||
@@ -94,8 +81,7 @@ class SodaExceptionHandler(
|
||||
@ExceptionHandler(Exception::class)
|
||||
fun handleException(e: Exception) = run {
|
||||
if (e is ResponseStatusException) throw e
|
||||
val logMessage = messageSource.getMessage("common.error.unknown", logLang)
|
||||
logger.error("API error: {}", logMessage, e)
|
||||
logger.error("API error", e)
|
||||
val message = messageSource.getMessage("common.error.unknown", langContext.lang)
|
||||
ApiResponse.error(message)
|
||||
}
|
||||
|
||||
@@ -72,8 +72,6 @@ class SecurityConfig(
|
||||
.antMatchers("/member/login").permitAll()
|
||||
.antMatchers("/member/login/google").permitAll()
|
||||
.antMatchers("/member/login/kakao").permitAll()
|
||||
.antMatchers("/member/login/apple").permitAll()
|
||||
.antMatchers("/member/login/line").permitAll()
|
||||
.antMatchers("/creator-admin/member/login").permitAll()
|
||||
.antMatchers("/member/forgot-password").permitAll()
|
||||
.antMatchers("/stplat/terms_of_service").permitAll()
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
package kr.co.vividnext.sodalive.configs
|
||||
|
||||
import kr.co.vividnext.sodalive.common.CountryInterceptor
|
||||
import kr.co.vividnext.sodalive.i18n.LangInterceptor
|
||||
import org.springframework.context.annotation.Configuration
|
||||
import org.springframework.web.servlet.config.annotation.CorsRegistry
|
||||
@@ -9,12 +8,10 @@ import org.springframework.web.servlet.config.annotation.WebMvcConfigurer
|
||||
|
||||
@Configuration
|
||||
class WebConfig(
|
||||
private val langInterceptor: LangInterceptor,
|
||||
private val countryInterceptor: CountryInterceptor
|
||||
private val langInterceptor: LangInterceptor
|
||||
) : WebMvcConfigurer {
|
||||
override fun addInterceptors(registry: InterceptorRegistry) {
|
||||
registry.addInterceptor(langInterceptor).addPathPatterns("/**")
|
||||
registry.addInterceptor(countryInterceptor).addPathPatterns("/**")
|
||||
}
|
||||
|
||||
override fun addCorsMappings(registry: CorsRegistry) {
|
||||
|
||||
@@ -44,7 +44,6 @@ interface AudioContentQueryRepository {
|
||||
fun findByIdAndCreatorId(contentId: Long, creatorId: Long): AudioContent?
|
||||
fun findByCreatorId(
|
||||
creatorId: Long,
|
||||
isCreator: Boolean = false,
|
||||
coverImageHost: String,
|
||||
isAdult: Boolean = false,
|
||||
contentType: ContentType = ContentType.ALL,
|
||||
@@ -56,7 +55,6 @@ interface AudioContentQueryRepository {
|
||||
|
||||
fun findTotalCountByCreatorId(
|
||||
creatorId: Long,
|
||||
isCreator: Boolean = false,
|
||||
isAdult: Boolean = false,
|
||||
categoryId: Long = 0,
|
||||
contentType: ContentType = ContentType.ALL
|
||||
@@ -232,7 +230,6 @@ class AudioContentQueryRepositoryImpl(
|
||||
|
||||
override fun findByCreatorId(
|
||||
creatorId: Long,
|
||||
isCreator: Boolean,
|
||||
coverImageHost: String,
|
||||
isAdult: Boolean,
|
||||
contentType: ContentType,
|
||||
@@ -249,18 +246,11 @@ class AudioContentQueryRepositoryImpl(
|
||||
}
|
||||
|
||||
var where = audioContent.member.id.eq(creatorId)
|
||||
|
||||
where = if (isCreator) {
|
||||
where.and(
|
||||
audioContent.releaseDate.isNotNull
|
||||
.and(audioContent.duration.isNotNull)
|
||||
)
|
||||
} else {
|
||||
where.and(
|
||||
.and(
|
||||
audioContent.isActive.isTrue
|
||||
.and(audioContent.duration.isNotNull)
|
||||
.or(audioContent.releaseDate.isNotNull.and(audioContent.duration.isNotNull))
|
||||
)
|
||||
}
|
||||
|
||||
if (!isAdult) {
|
||||
where = where.and(audioContent.isAdult.isFalse)
|
||||
@@ -342,24 +332,16 @@ class AudioContentQueryRepositoryImpl(
|
||||
|
||||
override fun findTotalCountByCreatorId(
|
||||
creatorId: Long,
|
||||
isCreator: Boolean,
|
||||
isAdult: Boolean,
|
||||
categoryId: Long,
|
||||
contentType: ContentType
|
||||
): Int {
|
||||
var where = audioContent.member.id.eq(creatorId)
|
||||
|
||||
where = if (isCreator) {
|
||||
where.and(
|
||||
audioContent.releaseDate.isNotNull
|
||||
.and(audioContent.duration.isNotNull)
|
||||
)
|
||||
} else {
|
||||
where.and(
|
||||
.and(
|
||||
audioContent.isActive.isTrue
|
||||
.and(audioContent.duration.isNotNull)
|
||||
.or(audioContent.releaseDate.isNotNull.and(audioContent.duration.isNotNull))
|
||||
)
|
||||
}
|
||||
|
||||
if (!isAdult) {
|
||||
where = where.and(audioContent.isAdult.isFalse)
|
||||
|
||||
@@ -459,7 +459,7 @@ class AudioContentService(
|
||||
applicationEventPublisher.publishEvent(
|
||||
FcmEvent(
|
||||
type = FcmEventType.INDIVIDUAL,
|
||||
titleKey = "content.notification.upload_complete_title",
|
||||
title = formatMessage("content.notification.upload_complete_title"),
|
||||
message = audioContent.title,
|
||||
recipients = listOf(audioContent.member!!.id!!),
|
||||
isAuth = null,
|
||||
@@ -474,11 +474,23 @@ class AudioContentService(
|
||||
FcmEvent(
|
||||
type = FcmEventType.UPLOAD_CONTENT,
|
||||
title = audioContent.member!!.nickname,
|
||||
messageKey = "content.notification.uploaded_message",
|
||||
args = listOf(audioContent.title),
|
||||
message = formatMessage("content.notification.uploaded_message", audioContent.title),
|
||||
isAuth = audioContent.isAdult,
|
||||
contentId = contentId,
|
||||
creatorId = audioContent.member!!.id
|
||||
creatorId = audioContent.member!!.id,
|
||||
container = "ios"
|
||||
)
|
||||
)
|
||||
|
||||
applicationEventPublisher.publishEvent(
|
||||
FcmEvent(
|
||||
type = FcmEventType.UPLOAD_CONTENT,
|
||||
title = audioContent.member!!.nickname,
|
||||
message = formatMessage("content.notification.uploaded_message", audioContent.title),
|
||||
isAuth = audioContent.isAdult,
|
||||
contentId = contentId,
|
||||
creatorId = audioContent.member!!.id,
|
||||
container = "aos"
|
||||
)
|
||||
)
|
||||
}
|
||||
@@ -496,11 +508,23 @@ class AudioContentService(
|
||||
FcmEvent(
|
||||
type = FcmEventType.UPLOAD_CONTENT,
|
||||
title = audioContent.member!!.nickname,
|
||||
messageKey = "content.notification.uploaded_message",
|
||||
args = listOf(audioContent.title),
|
||||
message = "콘텐츠를 업로드 하였습니다. - ${audioContent.title}",
|
||||
isAuth = audioContent.isAdult,
|
||||
contentId = audioContent.id!!,
|
||||
creatorId = audioContent.member!!.id
|
||||
creatorId = audioContent.member!!.id,
|
||||
container = "ios"
|
||||
)
|
||||
)
|
||||
|
||||
applicationEventPublisher.publishEvent(
|
||||
FcmEvent(
|
||||
type = FcmEventType.UPLOAD_CONTENT,
|
||||
title = audioContent.member!!.nickname,
|
||||
message = "콘텐츠를 업로드 하였습니다. - ${audioContent.title}",
|
||||
isAuth = audioContent.isAdult,
|
||||
contentId = audioContent.id!!,
|
||||
creatorId = audioContent.member!!.id,
|
||||
container = "aos"
|
||||
)
|
||||
)
|
||||
}
|
||||
@@ -955,11 +979,9 @@ class AudioContentService(
|
||||
limit: Long
|
||||
): GetAudioContentListResponse {
|
||||
val isAdult = member.auth != null && isAdultContentVisible
|
||||
val isCreator = member.id == creatorId
|
||||
|
||||
val totalCount = repository.findTotalCountByCreatorId(
|
||||
creatorId = creatorId,
|
||||
isCreator = isCreator,
|
||||
isAdult = isAdult,
|
||||
categoryId = categoryId,
|
||||
contentType = contentType
|
||||
@@ -967,7 +989,6 @@ class AudioContentService(
|
||||
|
||||
val audioContentList = repository.findByCreatorId(
|
||||
creatorId = creatorId,
|
||||
isCreator = isCreator,
|
||||
coverImageHost = coverImageHost,
|
||||
isAdult = isAdult,
|
||||
contentType = contentType,
|
||||
|
||||
@@ -4,9 +4,7 @@ import com.querydsl.core.types.dsl.Expressions
|
||||
import com.querydsl.jpa.impl.JPAQueryFactory
|
||||
import kr.co.vividnext.sodalive.content.QAudioContent.audioContent
|
||||
import kr.co.vividnext.sodalive.content.comment.QAudioContentComment.audioContentComment
|
||||
import kr.co.vividnext.sodalive.fcm.PushTokenInfo
|
||||
import kr.co.vividnext.sodalive.fcm.QPushToken.pushToken
|
||||
import kr.co.vividnext.sodalive.fcm.QPushTokenInfo
|
||||
import kr.co.vividnext.sodalive.member.QMember.member
|
||||
import org.springframework.data.jpa.repository.JpaRepository
|
||||
import org.springframework.stereotype.Repository
|
||||
@@ -40,7 +38,7 @@ interface AudioContentCommentQueryRepository {
|
||||
contentId: Long,
|
||||
commentParentId: Long?,
|
||||
myMemberId: Long
|
||||
): List<PushTokenInfo>
|
||||
): List<FindPushTokenByContentIdAndCommentParentIdMyMemberIdResponse>
|
||||
}
|
||||
|
||||
@Repository
|
||||
@@ -193,7 +191,7 @@ class AudioContentCommentQueryRepositoryImpl(
|
||||
contentId: Long,
|
||||
commentParentId: Long?,
|
||||
myMemberId: Long
|
||||
): List<PushTokenInfo> {
|
||||
): List<FindPushTokenByContentIdAndCommentParentIdMyMemberIdResponse> {
|
||||
var where = audioContent.id.eq(contentId)
|
||||
.and(member.id.ne(myMemberId))
|
||||
|
||||
@@ -208,10 +206,9 @@ class AudioContentCommentQueryRepositoryImpl(
|
||||
val response = if (commentParentId != null) {
|
||||
queryFactory
|
||||
.select(
|
||||
QPushTokenInfo(
|
||||
QFindPushTokenByContentIdAndCommentParentIdMyMemberIdResponse(
|
||||
pushToken.token,
|
||||
pushToken.deviceType,
|
||||
pushToken.languageCode.coalesce("ko")
|
||||
pushToken.deviceType
|
||||
)
|
||||
)
|
||||
.from(audioContentComment)
|
||||
@@ -223,10 +220,9 @@ class AudioContentCommentQueryRepositoryImpl(
|
||||
} else {
|
||||
queryFactory
|
||||
.select(
|
||||
QPushTokenInfo(
|
||||
QFindPushTokenByContentIdAndCommentParentIdMyMemberIdResponse(
|
||||
pushToken.token,
|
||||
pushToken.deviceType,
|
||||
pushToken.languageCode.coalesce("ko")
|
||||
pushToken.deviceType
|
||||
)
|
||||
)
|
||||
.from(audioContent)
|
||||
|
||||
@@ -83,12 +83,11 @@ class AudioContentCommentService(
|
||||
} else {
|
||||
member.nickname
|
||||
},
|
||||
messageKey = if (parent != null) {
|
||||
"content.comment.notification.reply"
|
||||
message = if (parent != null) {
|
||||
formatMessage("content.comment.notification.reply", audioContent.title)
|
||||
} else {
|
||||
"content.comment.notification.new"
|
||||
formatMessage("content.comment.notification.new", audioContent.title)
|
||||
},
|
||||
args = listOf(audioContent.title),
|
||||
contentId = audioContentId,
|
||||
commentParentId = parentId,
|
||||
myMemberId = member.id
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
package kr.co.vividnext.sodalive.content.comment
|
||||
|
||||
import com.querydsl.core.annotations.QueryProjection
|
||||
|
||||
data class FindPushTokenByContentIdAndCommentParentIdMyMemberIdResponse @QueryProjection constructor(
|
||||
val pushToken: String,
|
||||
val container: String
|
||||
)
|
||||
@@ -34,20 +34,15 @@ class AudioContentThemeService(
|
||||
isAdult: Boolean = false,
|
||||
isFree: Boolean = false,
|
||||
isPointAvailableOnly: Boolean = false,
|
||||
contentType: ContentType,
|
||||
excludeThemes: List<String> = emptyList()
|
||||
contentType: ContentType
|
||||
): List<String> {
|
||||
var themesWithIds = queryRepository.getActiveThemeWithIdsOfContent(
|
||||
val themesWithIds = queryRepository.getActiveThemeWithIdsOfContent(
|
||||
isAdult = isAdult,
|
||||
isFree = isFree,
|
||||
isPointAvailableOnly = isPointAvailableOnly,
|
||||
contentType = contentType
|
||||
)
|
||||
|
||||
if (excludeThemes.isNotEmpty()) {
|
||||
themesWithIds = themesWithIds.filter { it.theme !in excludeThemes }
|
||||
}
|
||||
|
||||
/**
|
||||
* langContext.lang == Lang.EN || Lang.JA 일 때 번역된 콘텐츠 테마 반환
|
||||
* 번역이 없으면 번역 API 호출 후 저장하고 반환
|
||||
|
||||
@@ -87,7 +87,7 @@ class CreatorAdminMemberService(
|
||||
userId = member.id!!,
|
||||
token = jwt,
|
||||
nickname = member.nickname,
|
||||
email = member.email ?: "",
|
||||
email = member.email,
|
||||
profileImage = if (member.profileImage != null) {
|
||||
"$cloudFrontHost/${member.profileImage}"
|
||||
} else {
|
||||
|
||||
@@ -7,7 +7,6 @@ import kr.co.vividnext.sodalive.explorer.profile.PostWriteCheersRequest
|
||||
import kr.co.vividnext.sodalive.explorer.profile.PutWriteCheersRequest
|
||||
import kr.co.vividnext.sodalive.i18n.LangContext
|
||||
import kr.co.vividnext.sodalive.i18n.SodaMessageSource
|
||||
import kr.co.vividnext.sodalive.member.DonationRankingPeriod
|
||||
import kr.co.vividnext.sodalive.member.Member
|
||||
import org.springframework.data.domain.Pageable
|
||||
import org.springframework.security.access.prepost.PreAuthorize
|
||||
@@ -76,12 +75,11 @@ class ExplorerController(
|
||||
@GetMapping("/profile/{id}/donation-rank")
|
||||
fun getCreatorProfileDonationRanking(
|
||||
@PathVariable("id") creatorId: Long,
|
||||
@RequestParam("period", required = false) period: DonationRankingPeriod? = null,
|
||||
@AuthenticationPrincipal(expression = "#this == 'anonymousUser' ? null : member") member: Member?,
|
||||
pageable: Pageable
|
||||
) = run {
|
||||
if (member == null) throw SodaException(messageKey = "common.error.bad_credentials")
|
||||
ApiResponse.ok(service.getCreatorProfileDonationRanking(creatorId, period, pageable, member))
|
||||
ApiResponse.ok(service.getCreatorProfileDonationRanking(creatorId, pageable, member))
|
||||
}
|
||||
|
||||
@PostMapping("/profile/cheers")
|
||||
|
||||
@@ -22,13 +22,11 @@ import kr.co.vividnext.sodalive.explorer.profile.TimeDifferenceResult
|
||||
import kr.co.vividnext.sodalive.i18n.Lang
|
||||
import kr.co.vividnext.sodalive.i18n.LangContext
|
||||
import kr.co.vividnext.sodalive.i18n.SodaMessageSource
|
||||
import kr.co.vividnext.sodalive.live.room.GenderRestriction
|
||||
import kr.co.vividnext.sodalive.live.room.LiveRoom
|
||||
import kr.co.vividnext.sodalive.live.room.LiveRoomType
|
||||
import kr.co.vividnext.sodalive.live.room.QLiveRoom.liveRoom
|
||||
import kr.co.vividnext.sodalive.live.room.cancel.QLiveRoomCancel.liveRoomCancel
|
||||
import kr.co.vividnext.sodalive.live.room.visit.QLiveRoomVisit.liveRoomVisit
|
||||
import kr.co.vividnext.sodalive.member.Gender
|
||||
import kr.co.vividnext.sodalive.member.Member
|
||||
import kr.co.vividnext.sodalive.member.MemberRole
|
||||
import kr.co.vividnext.sodalive.member.QMember
|
||||
@@ -40,7 +38,6 @@ import kr.co.vividnext.sodalive.member.tag.QCreatorTag.creatorTag
|
||||
import kr.co.vividnext.sodalive.member.tag.QMemberCreatorTag.memberCreatorTag
|
||||
import org.springframework.beans.factory.annotation.Value
|
||||
import org.springframework.stereotype.Repository
|
||||
import java.math.BigDecimal
|
||||
import java.time.Duration
|
||||
import java.time.LocalDate
|
||||
import java.time.LocalDateTime
|
||||
@@ -344,21 +341,6 @@ class ExplorerQueryRepository(
|
||||
.and(liveRoom.cancel.id.isNull)
|
||||
.and(liveRoom.isActive.isTrue)
|
||||
|
||||
val effectiveGender = if (userMember.auth != null) {
|
||||
if (userMember.auth!!.gender == 1) Gender.MALE else Gender.FEMALE
|
||||
} else {
|
||||
userMember.gender
|
||||
}
|
||||
|
||||
if (effectiveGender != Gender.NONE) {
|
||||
val genderCondition = when (effectiveGender) {
|
||||
Gender.MALE -> liveRoom.genderRestriction.`in`(GenderRestriction.ALL, GenderRestriction.MALE_ONLY)
|
||||
Gender.FEMALE -> liveRoom.genderRestriction.`in`(GenderRestriction.ALL, GenderRestriction.FEMALE_ONLY)
|
||||
Gender.NONE -> liveRoom.genderRestriction.isNotNull
|
||||
}
|
||||
where = where.and(genderCondition.or(liveRoom.member.id.eq(userMember.id)))
|
||||
}
|
||||
|
||||
if (userMember.auth == null) {
|
||||
where = where.and(liveRoom.isAdult.isFalse)
|
||||
}
|
||||
@@ -669,18 +651,6 @@ class ExplorerQueryRepository(
|
||||
.fetchFirst()
|
||||
}
|
||||
|
||||
fun getPaidContentCount(creatorId: Long): Long? {
|
||||
return queryFactory
|
||||
.select(audioContent.id.count())
|
||||
.from(audioContent)
|
||||
.where(
|
||||
audioContent.isActive.isTrue
|
||||
.and(audioContent.member.id.eq(creatorId))
|
||||
.and(audioContent.price.gt(BigDecimal.ZERO))
|
||||
)
|
||||
.fetchFirst()
|
||||
}
|
||||
|
||||
fun getOwnedContentCount(creatorId: Long, memberId: Long): Long {
|
||||
// 활성 주문 + 대여의 경우 유효기간 내 주문만 포함, 동일 콘텐츠 중복 구매는 1개로 카운트
|
||||
return queryFactory
|
||||
|
||||
@@ -24,7 +24,6 @@ import kr.co.vividnext.sodalive.i18n.Lang
|
||||
import kr.co.vividnext.sodalive.i18n.LangContext
|
||||
import kr.co.vividnext.sodalive.i18n.SodaMessageSource
|
||||
import kr.co.vividnext.sodalive.live.room.detail.GetRoomDetailUser
|
||||
import kr.co.vividnext.sodalive.member.DonationRankingPeriod
|
||||
import kr.co.vividnext.sodalive.member.Member
|
||||
import kr.co.vividnext.sodalive.member.MemberRole
|
||||
import kr.co.vividnext.sodalive.member.MemberService
|
||||
@@ -216,7 +215,6 @@ class ExplorerService(
|
||||
val notificationUserIds = queryRepository.getNotificationUserIds(creatorId)
|
||||
val creatorFollowing = queryRepository.getCreatorFollowing(creatorId = creatorId, memberId = member.id!!)
|
||||
val notificationRecipientCount = notificationUserIds.size
|
||||
val donationRankingPeriod = creatorAccount.donationRankingPeriod ?: DonationRankingPeriod.CUMULATIVE
|
||||
|
||||
// 후원랭킹
|
||||
val memberDonationRanking = if (
|
||||
@@ -225,8 +223,7 @@ class ExplorerService(
|
||||
donationRankingService.getMemberDonationRanking(
|
||||
creatorId = creatorId,
|
||||
limit = 10,
|
||||
withDonationCan = creatorId == member.id!!,
|
||||
period = donationRankingPeriod
|
||||
withDonationCan = creatorId == member.id!!
|
||||
)
|
||||
} else {
|
||||
listOf()
|
||||
@@ -290,9 +287,9 @@ class ExplorerService(
|
||||
null
|
||||
}
|
||||
|
||||
// 크리에이터의 전체 유료 콘텐츠 개수
|
||||
// 크리에이터의 전체 콘텐츠 개수
|
||||
val totalContentCount = if (isCreator) {
|
||||
queryRepository.getPaidContentCount(creatorId) ?: 0
|
||||
queryRepository.getContentCount(creatorId) ?: 0
|
||||
} else {
|
||||
0
|
||||
}
|
||||
@@ -399,37 +396,23 @@ class ExplorerService(
|
||||
|
||||
fun getCreatorProfileDonationRanking(
|
||||
creatorId: Long,
|
||||
period: DonationRankingPeriod?,
|
||||
pageable: Pageable,
|
||||
member: Member
|
||||
): GetDonationAllResponse {
|
||||
val creatorAccount = queryRepository.getMember(creatorId)
|
||||
?: throw SodaException(messageKey = "member.validation.user_not_found")
|
||||
val donationRankingPeriod = creatorAccount.donationRankingPeriod ?: DonationRankingPeriod.CUMULATIVE
|
||||
val isCreatorSelf = creatorId == member.id!!
|
||||
val effectivePeriod = if (isCreatorSelf && period != null) {
|
||||
period
|
||||
} else {
|
||||
donationRankingPeriod
|
||||
}
|
||||
val currentDate = LocalDate.now().atTime(0, 0, 0)
|
||||
val firstDayOfLastWeek = currentDate.with(TemporalAdjusters.previousOrSame(DayOfWeek.MONDAY)).minusDays(7)
|
||||
val firstDayOfMonth = currentDate.with(TemporalAdjusters.firstDayOfMonth())
|
||||
|
||||
val donationMemberTotal = donationRankingService.getMemberDonationRankingTotal(
|
||||
creatorId,
|
||||
effectivePeriod
|
||||
)
|
||||
val donationMemberTotal = donationRankingService.getMemberDonationRankingTotal(creatorId)
|
||||
val donationRanking = donationRankingService.getMemberDonationRanking(
|
||||
creatorId = creatorId,
|
||||
offset = pageable.offset,
|
||||
limit = pageable.pageSize.toLong(),
|
||||
withDonationCan = isCreatorSelf,
|
||||
period = effectivePeriod
|
||||
withDonationCan = creatorId == member.id!!
|
||||
)
|
||||
|
||||
return GetDonationAllResponse(
|
||||
accumulatedCansToday = if (isCreatorSelf) {
|
||||
accumulatedCansToday = if (creatorId == member.id!!) {
|
||||
queryRepository.getDonationCoinsDateRange(
|
||||
creatorId,
|
||||
currentDate,
|
||||
@@ -438,7 +421,7 @@ class ExplorerService(
|
||||
} else {
|
||||
0
|
||||
},
|
||||
accumulatedCansLastWeek = if (isCreatorSelf) {
|
||||
accumulatedCansLastWeek = if (creatorId == member.id!!) {
|
||||
queryRepository.getDonationCoinsDateRange(
|
||||
creatorId,
|
||||
firstDayOfLastWeek,
|
||||
@@ -447,7 +430,7 @@ class ExplorerService(
|
||||
} else {
|
||||
0
|
||||
},
|
||||
accumulatedCansThisMonth = if (isCreatorSelf) {
|
||||
accumulatedCansThisMonth = if (creatorId == member.id!!) {
|
||||
queryRepository.getDonationCoinsDateRange(
|
||||
creatorId,
|
||||
firstDayOfMonth,
|
||||
@@ -456,16 +439,11 @@ class ExplorerService(
|
||||
} else {
|
||||
0
|
||||
},
|
||||
isVisibleDonationRank = if (isCreatorSelf) {
|
||||
isVisibleDonationRank = if (creatorId == member.id!!) {
|
||||
queryRepository.getVisibleDonationRank(creatorId)
|
||||
} else {
|
||||
false
|
||||
},
|
||||
donationRankingPeriod = if (isCreatorSelf) {
|
||||
donationRankingPeriod
|
||||
} else {
|
||||
null
|
||||
},
|
||||
totalCount = donationMemberTotal,
|
||||
userDonationRanking = donationRanking
|
||||
)
|
||||
@@ -600,7 +578,7 @@ class ExplorerService(
|
||||
FcmEvent(
|
||||
type = FcmEventType.CHANGE_NOTICE,
|
||||
title = member.nickname,
|
||||
messageKey = "explorer.notice.fcm.message",
|
||||
message = messageSource.getMessage("explorer.notice.fcm.message", langContext.lang).orEmpty(),
|
||||
creatorId = member.id!!
|
||||
)
|
||||
)
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package kr.co.vividnext.sodalive.explorer
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty
|
||||
import kr.co.vividnext.sodalive.member.DonationRankingPeriod
|
||||
import java.io.Serializable
|
||||
|
||||
data class GetDonationAllResponse(
|
||||
@@ -9,7 +8,6 @@ data class GetDonationAllResponse(
|
||||
val accumulatedCansLastWeek: Int,
|
||||
val accumulatedCansThisMonth: Int,
|
||||
val isVisibleDonationRank: Boolean,
|
||||
val donationRankingPeriod: DonationRankingPeriod?,
|
||||
val totalCount: Int,
|
||||
val userDonationRanking: List<MemberDonationRankingResponse>
|
||||
) : Serializable
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package kr.co.vividnext.sodalive.explorer.profile
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty
|
||||
import com.querydsl.core.BooleanBuilder
|
||||
import com.querydsl.core.annotations.QueryProjection
|
||||
import com.querydsl.jpa.impl.JPAQueryFactory
|
||||
import kr.co.vividnext.sodalive.can.use.CanUsage
|
||||
@@ -9,17 +8,13 @@ import kr.co.vividnext.sodalive.can.use.QUseCan.useCan
|
||||
import kr.co.vividnext.sodalive.can.use.QUseCanCalculate.useCanCalculate
|
||||
import kr.co.vividnext.sodalive.member.QMember.member
|
||||
import org.springframework.stereotype.Repository
|
||||
import java.time.LocalDateTime
|
||||
import java.time.ZoneId
|
||||
|
||||
@Repository
|
||||
class CreatorDonationRankingQueryRepository(private val queryFactory: JPAQueryFactory) {
|
||||
fun getMemberDonationRanking(
|
||||
creatorId: Long,
|
||||
offset: Long,
|
||||
limit: Long,
|
||||
startDate: LocalDateTime? = null,
|
||||
endDate: LocalDateTime? = null
|
||||
limit: Long
|
||||
): List<DonationRankingProjection> {
|
||||
val donationCan = useCan.rewardCan.add(useCan.can).sum()
|
||||
return queryFactory
|
||||
@@ -43,7 +38,6 @@ class CreatorDonationRankingQueryRepository(private val queryFactory: JPAQueryFa
|
||||
.or(useCan.canUsage.eq(CanUsage.SPIN_ROULETTE))
|
||||
.or(useCan.canUsage.eq(CanUsage.LIVE))
|
||||
)
|
||||
.and(buildDateRangeCondition(startDate, endDate))
|
||||
)
|
||||
.offset(offset)
|
||||
.limit(limit)
|
||||
@@ -52,11 +46,7 @@ class CreatorDonationRankingQueryRepository(private val queryFactory: JPAQueryFa
|
||||
.fetch()
|
||||
}
|
||||
|
||||
fun getMemberDonationRankingTotal(
|
||||
creatorId: Long,
|
||||
startDate: LocalDateTime? = null,
|
||||
endDate: LocalDateTime? = null
|
||||
): Int {
|
||||
fun getMemberDonationRankingTotal(creatorId: Long): Int {
|
||||
return queryFactory
|
||||
.select(member.id)
|
||||
.from(useCanCalculate)
|
||||
@@ -71,32 +61,11 @@ class CreatorDonationRankingQueryRepository(private val queryFactory: JPAQueryFa
|
||||
.or(useCan.canUsage.eq(CanUsage.SPIN_ROULETTE))
|
||||
.or(useCan.canUsage.eq(CanUsage.LIVE))
|
||||
)
|
||||
.and(buildDateRangeCondition(startDate, endDate))
|
||||
)
|
||||
.groupBy(member.id)
|
||||
.fetch()
|
||||
.size
|
||||
}
|
||||
|
||||
private fun buildDateRangeCondition(
|
||||
startDate: LocalDateTime?,
|
||||
endDate: LocalDateTime?
|
||||
): BooleanBuilder {
|
||||
val condition = BooleanBuilder()
|
||||
if (startDate != null && endDate != null) {
|
||||
val startUtc = startDate
|
||||
.atZone(ZoneId.of("Asia/Seoul"))
|
||||
.withZoneSameInstant(ZoneId.of("UTC"))
|
||||
.toLocalDateTime()
|
||||
val endUtc = endDate
|
||||
.atZone(ZoneId.of("Asia/Seoul"))
|
||||
.withZoneSameInstant(ZoneId.of("UTC"))
|
||||
.toLocalDateTime()
|
||||
condition.and(useCanCalculate.createdAt.goe(startUtc))
|
||||
condition.and(useCanCalculate.createdAt.lt(endUtc))
|
||||
}
|
||||
return condition
|
||||
}
|
||||
}
|
||||
|
||||
data class DonationRankingProjection @QueryProjection constructor(
|
||||
|
||||
@@ -2,13 +2,11 @@ package kr.co.vividnext.sodalive.explorer.profile
|
||||
|
||||
import kr.co.vividnext.sodalive.explorer.MemberDonationRankingListResponse
|
||||
import kr.co.vividnext.sodalive.explorer.MemberDonationRankingResponse
|
||||
import kr.co.vividnext.sodalive.member.DonationRankingPeriod
|
||||
import org.springframework.beans.factory.annotation.Value
|
||||
import org.springframework.data.redis.core.RedisTemplate
|
||||
import org.springframework.stereotype.Service
|
||||
import java.time.DayOfWeek
|
||||
import java.time.Duration
|
||||
import java.time.LocalDate
|
||||
import java.time.LocalDateTime
|
||||
import java.time.LocalTime
|
||||
import java.time.temporal.ChronoUnit
|
||||
@@ -22,22 +20,14 @@ class CreatorDonationRankingService(
|
||||
@Value("\${cloud.aws.cloud-front.host}")
|
||||
private val imageHost: String
|
||||
) {
|
||||
fun getMemberDonationRankingTotal(
|
||||
creatorId: Long,
|
||||
period: DonationRankingPeriod = DonationRankingPeriod.CUMULATIVE
|
||||
): Int {
|
||||
val cacheKey = "creator_donation_ranking_member_total_v2:$creatorId:$period"
|
||||
fun getMemberDonationRankingTotal(creatorId: Long): Int {
|
||||
val cacheKey = "creator_donation_ranking_member_total_v2:$creatorId"
|
||||
val cachedTotal = redisTemplate.opsForValue().get(cacheKey) as? Int
|
||||
if (cachedTotal != null) {
|
||||
return cachedTotal
|
||||
}
|
||||
|
||||
val weeklyDateRange = getWeeklyDateRange(period)
|
||||
val total = if (weeklyDateRange == null) {
|
||||
repository.getMemberDonationRankingTotal(creatorId)
|
||||
} else {
|
||||
repository.getMemberDonationRankingTotal(creatorId, weeklyDateRange.first, weeklyDateRange.second)
|
||||
}
|
||||
val total = repository.getMemberDonationRankingTotal(creatorId)
|
||||
|
||||
val now = LocalDateTime.now()
|
||||
val nextMonday = now.with(TemporalAdjusters.next(DayOfWeek.MONDAY)).with(LocalTime.MIN)
|
||||
@@ -56,27 +46,15 @@ class CreatorDonationRankingService(
|
||||
creatorId: Long,
|
||||
offset: Long = 0,
|
||||
limit: Long = 10,
|
||||
withDonationCan: Boolean,
|
||||
period: DonationRankingPeriod = DonationRankingPeriod.CUMULATIVE
|
||||
withDonationCan: Boolean
|
||||
): List<MemberDonationRankingResponse> {
|
||||
val cacheKey = "creator_donation_ranking_v2:$creatorId:$period:$offset:$limit:$withDonationCan"
|
||||
val cacheKey = "creator_donation_ranking_v2:$creatorId:$offset:$limit:$withDonationCan"
|
||||
val cachedData = redisTemplate.opsForValue().get(cacheKey) as? MemberDonationRankingListResponse
|
||||
if (cachedData != null) {
|
||||
return cachedData.rankings
|
||||
}
|
||||
|
||||
val weeklyDateRange = getWeeklyDateRange(period)
|
||||
val memberDonationRanking = if (weeklyDateRange == null) {
|
||||
repository.getMemberDonationRanking(creatorId, offset, limit)
|
||||
} else {
|
||||
repository.getMemberDonationRanking(
|
||||
creatorId,
|
||||
offset,
|
||||
limit,
|
||||
weeklyDateRange.first,
|
||||
weeklyDateRange.second
|
||||
)
|
||||
}
|
||||
val memberDonationRanking = repository.getMemberDonationRanking(creatorId, offset, limit)
|
||||
|
||||
val result = memberDonationRanking.map {
|
||||
MemberDonationRankingResponse(
|
||||
@@ -99,17 +77,4 @@ class CreatorDonationRankingService(
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
private fun getWeeklyDateRange(period: DonationRankingPeriod): Pair<LocalDateTime, LocalDateTime>? {
|
||||
if (period != DonationRankingPeriod.WEEKLY) {
|
||||
return null
|
||||
}
|
||||
|
||||
val currentDate = LocalDate.now()
|
||||
val lastWeekMonday = currentDate.with(TemporalAdjusters.previousOrSame(DayOfWeek.MONDAY)).minusWeeks(1)
|
||||
val startDate = lastWeekMonday.atStartOfDay()
|
||||
val endDate = startDate.plusDays(7)
|
||||
|
||||
return startDate to endDate
|
||||
}
|
||||
}
|
||||
|
||||
@@ -123,7 +123,7 @@ class CreatorCommunityService(
|
||||
FcmEvent(
|
||||
type = FcmEventType.CHANGE_NOTICE,
|
||||
title = member.nickname,
|
||||
messageKey = "creator.community.fcm.new_post",
|
||||
message = messageSource.getMessage("creator.community.fcm.new_post", langContext.lang).orEmpty(),
|
||||
creatorId = member.id!!
|
||||
)
|
||||
)
|
||||
|
||||
@@ -33,6 +33,17 @@ class FcmController(private val applicationEventPublisher: ApplicationEventPubli
|
||||
type = FcmEventType.ALL,
|
||||
title = request.title,
|
||||
message = request.message,
|
||||
container = "ios",
|
||||
isAuth = request.isAuth
|
||||
)
|
||||
)
|
||||
|
||||
applicationEventPublisher.publishEvent(
|
||||
FcmEvent(
|
||||
type = FcmEventType.ALL,
|
||||
title = request.title,
|
||||
message = request.message,
|
||||
container = "aos",
|
||||
isAuth = request.isAuth
|
||||
)
|
||||
)
|
||||
|
||||
@@ -1,9 +1,6 @@
|
||||
package kr.co.vividnext.sodalive.fcm
|
||||
|
||||
import kr.co.vividnext.sodalive.content.comment.AudioContentCommentRepository
|
||||
import kr.co.vividnext.sodalive.i18n.Lang
|
||||
import kr.co.vividnext.sodalive.i18n.SodaMessageSource
|
||||
import kr.co.vividnext.sodalive.live.room.GenderRestriction
|
||||
import kr.co.vividnext.sodalive.member.MemberRepository
|
||||
import org.springframework.scheduling.annotation.Async
|
||||
import org.springframework.stereotype.Component
|
||||
@@ -18,14 +15,11 @@ enum class FcmEventType {
|
||||
|
||||
class FcmEvent(
|
||||
val type: FcmEventType,
|
||||
val title: String = "",
|
||||
val message: String = "",
|
||||
val titleKey: String? = null,
|
||||
val messageKey: String? = null,
|
||||
val args: List<Any> = listOf(),
|
||||
val title: String,
|
||||
val message: String,
|
||||
val container: String = "",
|
||||
val recipients: List<Long> = listOf(),
|
||||
val pushTokens: List<PushTokenInfo>? = null,
|
||||
val recipientsMap: Map<String, List<List<String>>>? = null,
|
||||
val isAuth: Boolean? = null,
|
||||
val roomId: Long? = null,
|
||||
val contentId: Long? = null,
|
||||
@@ -34,16 +28,14 @@ class FcmEvent(
|
||||
val auditionId: Long? = null,
|
||||
val commentParentId: Long? = null,
|
||||
val myMemberId: Long? = null,
|
||||
val isAvailableJoinCreator: Boolean? = null,
|
||||
val genderRestriction: GenderRestriction? = null
|
||||
val isAvailableJoinCreator: Boolean? = null
|
||||
)
|
||||
|
||||
@Component
|
||||
class FcmSendListener(
|
||||
private val pushService: FcmService,
|
||||
private val memberRepository: MemberRepository,
|
||||
private val contentCommentRepository: AudioContentCommentRepository,
|
||||
private val messageSource: SodaMessageSource
|
||||
private val contentCommentRepository: AudioContentCommentRepository
|
||||
) {
|
||||
@Async
|
||||
@TransactionalEventListener
|
||||
@@ -51,10 +43,21 @@ class FcmSendListener(
|
||||
fun send(fcmEvent: FcmEvent) {
|
||||
when (fcmEvent.type) {
|
||||
FcmEventType.ALL -> {
|
||||
val pushTokens = memberRepository.getAllRecipientPushTokens(
|
||||
fcmEvent.isAuth
|
||||
)
|
||||
sendPush(pushTokens, fcmEvent)
|
||||
if (fcmEvent.container.isNotBlank()) {
|
||||
val pushTokens = memberRepository.getAllRecipientPushTokens(
|
||||
fcmEvent.isAuth,
|
||||
fcmEvent.container
|
||||
)
|
||||
|
||||
for (tokens in pushTokens) {
|
||||
pushService.send(
|
||||
tokens = tokens,
|
||||
title = fcmEvent.title,
|
||||
message = fcmEvent.message,
|
||||
container = fcmEvent.container
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
FcmEventType.INDIVIDUAL -> {
|
||||
@@ -63,119 +66,254 @@ class FcmSendListener(
|
||||
recipients = fcmEvent.recipients,
|
||||
isAuth = fcmEvent.isAuth
|
||||
)
|
||||
sendPush(pushTokens, fcmEvent)
|
||||
|
||||
val iosPushTokens = pushTokens["ios"]
|
||||
val aosPushToken = pushTokens["aos"]
|
||||
|
||||
if (iosPushTokens != null) {
|
||||
for (tokens in iosPushTokens) {
|
||||
pushService.send(
|
||||
tokens = tokens,
|
||||
title = fcmEvent.title,
|
||||
message = fcmEvent.message,
|
||||
container = "ios",
|
||||
contentId = fcmEvent.contentId
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
if (aosPushToken != null) {
|
||||
for (tokens in aosPushToken) {
|
||||
pushService.send(
|
||||
tokens = tokens,
|
||||
title = fcmEvent.title,
|
||||
message = fcmEvent.message,
|
||||
container = "aos",
|
||||
contentId = fcmEvent.contentId
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
FcmEventType.CREATE_LIVE -> {
|
||||
val pushTokens = memberRepository.getCreateLiveRoomNotificationRecipientPushTokens(
|
||||
creatorId = fcmEvent.creatorId!!,
|
||||
isAuth = fcmEvent.isAuth ?: false,
|
||||
isAvailableJoinCreator = fcmEvent.isAvailableJoinCreator ?: false,
|
||||
genderRestriction = fcmEvent.genderRestriction
|
||||
)
|
||||
sendPush(pushTokens, fcmEvent, roomId = fcmEvent.roomId)
|
||||
if (fcmEvent.container.isNotBlank()) {
|
||||
val pushTokens = memberRepository.getCreateLiveRoomNotificationRecipientPushTokens(
|
||||
creatorId = fcmEvent.creatorId!!,
|
||||
isAuth = fcmEvent.isAuth ?: false,
|
||||
isAvailableJoinCreator = fcmEvent.isAvailableJoinCreator ?: false,
|
||||
container = fcmEvent.container
|
||||
)
|
||||
|
||||
for (tokens in pushTokens) {
|
||||
pushService.send(
|
||||
tokens = tokens,
|
||||
title = fcmEvent.title,
|
||||
message = fcmEvent.message,
|
||||
container = fcmEvent.container,
|
||||
roomId = fcmEvent.roomId
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
FcmEventType.START_LIVE -> {
|
||||
val pushTokens = memberRepository.getStartLiveRoomNotificationRecipientPushTokens(
|
||||
creatorId = fcmEvent.creatorId!!,
|
||||
roomId = fcmEvent.roomId!!,
|
||||
isAuth = fcmEvent.isAuth ?: false,
|
||||
isAvailableJoinCreator = fcmEvent.isAvailableJoinCreator ?: false,
|
||||
genderRestriction = fcmEvent.genderRestriction
|
||||
)
|
||||
sendPush(pushTokens, fcmEvent, roomId = fcmEvent.roomId)
|
||||
if (fcmEvent.container.isNotBlank()) {
|
||||
val pushTokens = memberRepository.getStartLiveRoomNotificationRecipientPushTokens(
|
||||
creatorId = fcmEvent.creatorId!!,
|
||||
roomId = fcmEvent.roomId!!,
|
||||
isAuth = fcmEvent.isAuth ?: false,
|
||||
isAvailableJoinCreator = fcmEvent.isAvailableJoinCreator ?: false,
|
||||
container = fcmEvent.container
|
||||
)
|
||||
|
||||
for (tokens in pushTokens) {
|
||||
pushService.send(
|
||||
tokens = tokens,
|
||||
title = fcmEvent.title,
|
||||
message = fcmEvent.message,
|
||||
container = fcmEvent.container,
|
||||
roomId = fcmEvent.roomId
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
FcmEventType.CANCEL_LIVE -> {
|
||||
if (fcmEvent.pushTokens != null) {
|
||||
sendPush(fcmEvent.pushTokens, fcmEvent)
|
||||
if (fcmEvent.recipientsMap != null) {
|
||||
val iosPushTokens = fcmEvent.recipientsMap["ios"]
|
||||
val aosPushToken = fcmEvent.recipientsMap["aos"]
|
||||
if (iosPushTokens != null) {
|
||||
for (tokens in iosPushTokens) {
|
||||
pushService.send(
|
||||
tokens = tokens,
|
||||
title = fcmEvent.title,
|
||||
message = fcmEvent.message,
|
||||
container = "ios"
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
if (aosPushToken != null) {
|
||||
for (tokens in aosPushToken) {
|
||||
pushService.send(
|
||||
tokens = tokens,
|
||||
title = fcmEvent.title,
|
||||
message = fcmEvent.message,
|
||||
container = "aos"
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
FcmEventType.UPLOAD_CONTENT -> {
|
||||
val pushTokens = memberRepository.getUploadContentNotificationRecipientPushTokens(
|
||||
creatorId = fcmEvent.creatorId!!,
|
||||
isAuth = fcmEvent.isAuth ?: false
|
||||
)
|
||||
sendPush(pushTokens, fcmEvent, contentId = fcmEvent.contentId)
|
||||
if (fcmEvent.container.isNotBlank()) {
|
||||
val pushTokens = memberRepository.getUploadContentNotificationRecipientPushTokens(
|
||||
creatorId = fcmEvent.creatorId!!,
|
||||
isAuth = fcmEvent.isAuth ?: false,
|
||||
container = fcmEvent.container
|
||||
)
|
||||
|
||||
for (tokens in pushTokens) {
|
||||
pushService.send(
|
||||
tokens = tokens,
|
||||
title = fcmEvent.title,
|
||||
message = fcmEvent.message,
|
||||
container = fcmEvent.container,
|
||||
contentId = fcmEvent.contentId
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
FcmEventType.SEND_MESSAGE -> {
|
||||
val pushToken = memberRepository.getMessageRecipientPushToken(messageId = fcmEvent.messageId!!)
|
||||
if (pushToken != null) {
|
||||
sendPush(listOf(pushToken), fcmEvent, messageId = fcmEvent.messageId)
|
||||
val response = memberRepository.getMessageRecipientPushToken(messageId = fcmEvent.messageId!!)
|
||||
|
||||
if (response != null) {
|
||||
pushService.send(
|
||||
tokens = listOf(response.pushToken),
|
||||
title = fcmEvent.title,
|
||||
message = fcmEvent.message,
|
||||
container = response.container,
|
||||
messageId = fcmEvent.messageId
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
FcmEventType.CHANGE_NOTICE -> {
|
||||
if (fcmEvent.creatorId != null) {
|
||||
val pushTokens = memberRepository.getChangeNoticeRecipientPushTokens(fcmEvent.creatorId)
|
||||
sendPush(pushTokens, fcmEvent, creatorId = fcmEvent.creatorId)
|
||||
val pushTokenList = memberRepository.getChangeNoticeRecipientPushTokens(fcmEvent.creatorId)
|
||||
|
||||
val iosPushTokens = pushTokenList["ios"]
|
||||
val aosPushToken = pushTokenList["aos"]
|
||||
|
||||
if (iosPushTokens != null) {
|
||||
for (tokens in iosPushTokens) {
|
||||
pushService.send(
|
||||
tokens = tokens,
|
||||
title = fcmEvent.title,
|
||||
message = fcmEvent.message,
|
||||
container = "ios",
|
||||
creatorId = fcmEvent.creatorId
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
if (aosPushToken != null) {
|
||||
for (tokens in aosPushToken) {
|
||||
pushService.send(
|
||||
tokens = tokens,
|
||||
title = fcmEvent.title,
|
||||
message = fcmEvent.message,
|
||||
container = "aos",
|
||||
creatorId = fcmEvent.creatorId
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
FcmEventType.CREATE_CONTENT_COMMENT -> {
|
||||
if (fcmEvent.myMemberId != null && fcmEvent.contentId != null) {
|
||||
val pushTokens = contentCommentRepository.findPushTokenByContentIdAndCommentParentIdMyMemberId(
|
||||
val response = contentCommentRepository.findPushTokenByContentIdAndCommentParentIdMyMemberId(
|
||||
contentId = fcmEvent.contentId,
|
||||
commentParentId = fcmEvent.commentParentId,
|
||||
myMemberId = fcmEvent.myMemberId
|
||||
)
|
||||
sendPush(pushTokens, fcmEvent, contentId = fcmEvent.contentId)
|
||||
|
||||
val iosPushTokens = response
|
||||
.asSequence()
|
||||
.distinct()
|
||||
.filter { it.pushToken.isNotBlank() }
|
||||
.filter { it.container == "ios" }
|
||||
.map { it.pushToken }
|
||||
.toList()
|
||||
|
||||
val aosPushTokens = response
|
||||
.asSequence()
|
||||
.distinct()
|
||||
.filter { it.pushToken.isNotBlank() }
|
||||
.filter { it.container == "aos" }
|
||||
.map { it.pushToken }
|
||||
.toList()
|
||||
|
||||
if (iosPushTokens.isNotEmpty()) {
|
||||
pushService.send(
|
||||
tokens = iosPushTokens,
|
||||
title = fcmEvent.title,
|
||||
message = fcmEvent.message,
|
||||
container = "ios",
|
||||
contentId = fcmEvent.contentId
|
||||
)
|
||||
}
|
||||
|
||||
if (aosPushTokens.isNotEmpty()) {
|
||||
pushService.send(
|
||||
tokens = aosPushTokens,
|
||||
title = fcmEvent.title,
|
||||
message = fcmEvent.message,
|
||||
container = "aos",
|
||||
contentId = fcmEvent.contentId
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
FcmEventType.IN_PROGRESS_AUDITION -> {
|
||||
if (fcmEvent.auditionId != null && fcmEvent.auditionId > 0) {
|
||||
val pushTokens = memberRepository.getAuditionNoticeRecipientPushTokens(
|
||||
val pushTokenList = memberRepository.getAuditionNoticeRecipientPushTokens(
|
||||
isAuth = fcmEvent.isAuth ?: false
|
||||
)
|
||||
sendPush(pushTokens, fcmEvent, auditionId = fcmEvent.auditionId)
|
||||
|
||||
val iosPushTokens = pushTokenList["ios"]
|
||||
val aosPushToken = pushTokenList["aos"]
|
||||
|
||||
if (iosPushTokens != null) {
|
||||
for (tokens in iosPushTokens) {
|
||||
pushService.send(
|
||||
tokens = tokens,
|
||||
title = fcmEvent.title,
|
||||
message = fcmEvent.message,
|
||||
container = "ios",
|
||||
auditionId = fcmEvent.auditionId
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
if (aosPushToken != null) {
|
||||
for (tokens in aosPushToken) {
|
||||
pushService.send(
|
||||
tokens = tokens,
|
||||
title = fcmEvent.title,
|
||||
message = fcmEvent.message,
|
||||
container = "aos",
|
||||
auditionId = fcmEvent.auditionId
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun sendPush(
|
||||
pushTokens: List<PushTokenInfo>,
|
||||
fcmEvent: FcmEvent,
|
||||
roomId: Long? = null,
|
||||
contentId: Long? = null,
|
||||
messageId: Long? = null,
|
||||
creatorId: Long? = null,
|
||||
auditionId: Long? = null
|
||||
) {
|
||||
val tokensByLang = pushTokens.groupBy { it.languageCode }
|
||||
|
||||
for ((langCode, tokens) in tokensByLang) {
|
||||
val lang = Lang.fromAcceptLanguage(langCode)
|
||||
val title = translate(fcmEvent.titleKey, fcmEvent.title, lang, fcmEvent.args)
|
||||
val message = translate(fcmEvent.messageKey, fcmEvent.message, lang, fcmEvent.args)
|
||||
|
||||
val tokensByOS = tokens.groupBy { it.deviceType }
|
||||
for ((os, osTokens) in tokensByOS) {
|
||||
osTokens.map { it.token }.distinct().chunked(500).forEach { batch ->
|
||||
pushService.send(
|
||||
tokens = batch,
|
||||
title = title,
|
||||
message = message,
|
||||
container = os,
|
||||
roomId = roomId ?: fcmEvent.roomId,
|
||||
contentId = contentId ?: fcmEvent.contentId,
|
||||
messageId = messageId ?: fcmEvent.messageId,
|
||||
creatorId = creatorId ?: fcmEvent.creatorId,
|
||||
auditionId = auditionId ?: fcmEvent.auditionId
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun translate(key: String?, default: String, lang: Lang, args: List<Any>): String {
|
||||
if (key == null) return default
|
||||
val template = messageSource.getMessage(key, lang) ?: return default
|
||||
return String.format(template, *args.toTypedArray())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
package kr.co.vividnext.sodalive.fcm
|
||||
|
||||
import com.querydsl.core.annotations.QueryProjection
|
||||
|
||||
data class GetMessageRecipientPushTokenResponse @QueryProjection constructor(
|
||||
val pushToken: String,
|
||||
val container: String
|
||||
)
|
||||
@@ -10,8 +10,7 @@ import javax.persistence.ManyToOne
|
||||
@Entity
|
||||
data class PushToken(
|
||||
var token: String,
|
||||
var deviceType: String,
|
||||
var languageCode: String? = null
|
||||
var deviceType: String
|
||||
) : BaseEntity() {
|
||||
@ManyToOne(fetch = FetchType.LAZY)
|
||||
@JoinColumn(name = "member_id", nullable = true)
|
||||
|
||||
@@ -1,9 +0,0 @@
|
||||
package kr.co.vividnext.sodalive.fcm
|
||||
|
||||
import com.querydsl.core.annotations.QueryProjection
|
||||
|
||||
data class PushTokenInfo @QueryProjection constructor(
|
||||
val token: String,
|
||||
val deviceType: String,
|
||||
val languageCode: String
|
||||
)
|
||||
@@ -9,7 +9,6 @@ interface PushTokenRepository : JpaRepository<PushToken, Long>, PushTokenQueryRe
|
||||
interface PushTokenQueryRepository {
|
||||
fun findByToken(token: String): PushToken?
|
||||
fun findByMemberId(memberId: Long): List<PushToken>
|
||||
fun findByMemberIds(memberIds: List<Long>): List<PushToken>
|
||||
}
|
||||
|
||||
class PushTokenQueryRepositoryImpl(
|
||||
@@ -28,12 +27,4 @@ class PushTokenQueryRepositoryImpl(
|
||||
.where(pushToken.member.id.eq(memberId))
|
||||
.fetch()
|
||||
}
|
||||
|
||||
override fun findByMemberIds(memberIds: List<Long>): List<PushToken> {
|
||||
if (memberIds.isEmpty()) return emptyList()
|
||||
return queryFactory
|
||||
.selectFrom(pushToken)
|
||||
.where(pushToken.member.id.`in`(memberIds))
|
||||
.fetch()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
package kr.co.vividnext.sodalive.fcm
|
||||
|
||||
import kr.co.vividnext.sodalive.i18n.LangContext
|
||||
import kr.co.vividnext.sodalive.member.MemberRepository
|
||||
import org.springframework.data.repository.findByIdOrNull
|
||||
import org.springframework.stereotype.Service
|
||||
@@ -9,8 +8,7 @@ import org.springframework.transaction.annotation.Transactional
|
||||
@Service
|
||||
class PushTokenService(
|
||||
private val repository: PushTokenRepository,
|
||||
private val memberRepository: MemberRepository,
|
||||
private val langContext: LangContext
|
||||
private val memberRepository: MemberRepository
|
||||
) {
|
||||
@Transactional
|
||||
fun registerToken(memberId: Long, token: String, deviceType: String) {
|
||||
@@ -22,9 +20,8 @@ class PushTokenService(
|
||||
existing.member = member
|
||||
existing.token = token
|
||||
existing.deviceType = deviceType
|
||||
existing.languageCode = langContext.lang.code
|
||||
} else {
|
||||
val newToken = PushToken(token, deviceType, langContext.lang.code)
|
||||
val newToken = PushToken(token, deviceType)
|
||||
newToken.member = member
|
||||
repository.save(newToken)
|
||||
}
|
||||
|
||||
@@ -1280,16 +1280,6 @@ class SodaMessageSource {
|
||||
Lang.EN to "Kakao login failed. Please try again.",
|
||||
Lang.JA to "Kakaoでログインできませんでした。もう一度お試しください。"
|
||||
),
|
||||
"member.social.apple_login_failed" to mapOf(
|
||||
Lang.KO to "애플 로그인을 하지 못했습니다. 다시 시도해 주세요",
|
||||
Lang.EN to "Apple sign-in failed. Please try again.",
|
||||
Lang.JA to "Appleでログインできませんでした。もう一度お試しください。"
|
||||
),
|
||||
"member.social.line_login_failed" to mapOf(
|
||||
Lang.KO to "라인 로그인을 하지 못했습니다. 다시 시도해 주세요",
|
||||
Lang.EN to "LINE sign-in failed. Please try again.",
|
||||
Lang.JA to "LINEでログインできませんでした。もう一度お試しください。"
|
||||
),
|
||||
"member.social.email_consent_required" to mapOf(
|
||||
Lang.KO to "이메일 제공에 동의하셔야 서비스 이용이 가능합니다.",
|
||||
Lang.EN to "You must agree to provide your email to use the service.",
|
||||
@@ -1448,11 +1438,6 @@ class SodaMessageSource {
|
||||
)
|
||||
|
||||
private val liveRoomMessages = mapOf(
|
||||
"live.room.gender_restricted" to mapOf(
|
||||
Lang.KO to "입장 가능한 성별이 아닙니다.",
|
||||
Lang.EN to "Your gender is not allowed to enter this room.",
|
||||
Lang.JA to "入場可能な性別ではありません。"
|
||||
),
|
||||
"live.room.max_reservations" to mapOf(
|
||||
Lang.KO to "예약 라이브는 최대 3개까지 가능합니다.",
|
||||
Lang.EN to "You can reserve up to 3 live sessions.",
|
||||
@@ -1578,21 +1563,6 @@ class SodaMessageSource {
|
||||
Lang.EN to "yyyy.MM.dd E hh:mm a",
|
||||
Lang.JA to "yyyy.MM.dd E hh:mm a"
|
||||
),
|
||||
"live.room.language_tag.korean" to mapOf(
|
||||
Lang.KO to "한국어",
|
||||
Lang.EN to "Korean",
|
||||
Lang.JA to "韓国語"
|
||||
),
|
||||
"live.room.language_tag.japanese" to mapOf(
|
||||
Lang.KO to "일본어",
|
||||
Lang.EN to "Japanese",
|
||||
Lang.JA to "日本語"
|
||||
),
|
||||
"live.room.language_tag.english" to mapOf(
|
||||
Lang.KO to "영어",
|
||||
Lang.EN to "English",
|
||||
Lang.JA to "英語"
|
||||
),
|
||||
"live.room.fcm.message.started" to mapOf(
|
||||
Lang.KO to "라이브를 시작했습니다. - %s",
|
||||
Lang.EN to "Live started. - %s",
|
||||
@@ -1658,11 +1628,6 @@ class SodaMessageSource {
|
||||
Lang.KO to "애플",
|
||||
Lang.EN to "Apple",
|
||||
Lang.JA to "Apple"
|
||||
),
|
||||
"member.provider.line" to mapOf(
|
||||
Lang.KO to "라인",
|
||||
Lang.EN to "LINE",
|
||||
Lang.JA to "LINE"
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
@@ -8,6 +8,5 @@ data class GetLiveReservationResponse(
|
||||
val price: Int,
|
||||
val masterNickname: String,
|
||||
val beginDateTime: String,
|
||||
val beginDateTimeUtc: String,
|
||||
val cancelable: Boolean
|
||||
)
|
||||
|
||||
@@ -83,8 +83,6 @@ class LiveReservationService(
|
||||
nickname = room.member!!.nickname,
|
||||
title = room.title,
|
||||
beginDateString = beginDateTime.format(DateTimeFormatter.ofPattern(reservationDateFormat)),
|
||||
beginDateTimeUtc = room.beginDateTime
|
||||
.format(DateTimeFormatter.ISO_LOCAL_DATE_TIME),
|
||||
price = if (room.price > 0) {
|
||||
val priceTemplate = messageSource.getMessage("live.room.can_title", langContext.lang).orEmpty()
|
||||
String.format(priceTemplate, room.price)
|
||||
@@ -124,9 +122,6 @@ class LiveReservationService(
|
||||
beginDateTime = beginDateTime.format(
|
||||
DateTimeFormatter.ofPattern(detailDateFormat)
|
||||
),
|
||||
beginDateTimeUtc = it.room!!.beginDateTime
|
||||
.atZone(ZoneId.of("UTC"))
|
||||
.format(DateTimeFormatter.ISO_LOCAL_DATE_TIME),
|
||||
cancelable = beginDateTime.minusHours(4).isAfter(
|
||||
LocalDateTime.now()
|
||||
.atZone(ZoneId.of("UTC"))
|
||||
@@ -163,9 +158,6 @@ class LiveReservationService(
|
||||
beginDateTime = beginDateTime.format(
|
||||
DateTimeFormatter.ofPattern(detailDateFormat)
|
||||
),
|
||||
beginDateTimeUtc = reservation.room!!.beginDateTime
|
||||
.atZone(ZoneId.of("UTC"))
|
||||
.format(DateTimeFormatter.ISO_LOCAL_DATE_TIME),
|
||||
cancelable = beginDateTime.minusHours(4).isAfter(
|
||||
LocalDateTime.now()
|
||||
.atZone(ZoneId.of("UTC"))
|
||||
|
||||
@@ -5,7 +5,6 @@ data class MakeLiveReservationResponse(
|
||||
val nickname: String,
|
||||
val title: String,
|
||||
val beginDateString: String,
|
||||
val beginDateTimeUtc: String,
|
||||
val price: String,
|
||||
val haveCan: Int,
|
||||
val useCan: Int,
|
||||
|
||||
@@ -15,6 +15,5 @@ data class CreateLiveRoomRequest(
|
||||
val menuPanId: Long = 0,
|
||||
val menuPan: String = "",
|
||||
val isActiveMenuPan: Boolean = false,
|
||||
val isAvailableJoinCreator: Boolean = true,
|
||||
val genderRestriction: GenderRestriction = GenderRestriction.ALL
|
||||
val isAvailableJoinCreator: Boolean = true
|
||||
)
|
||||
|
||||
@@ -9,6 +9,5 @@ data class EditLiveRoomInfoRequest(
|
||||
val menuPanId: Long = 0,
|
||||
val menuPan: String = "",
|
||||
val isActiveMenuPan: Boolean? = null,
|
||||
val isAdult: Boolean? = null,
|
||||
val genderRestriction: GenderRestriction? = null
|
||||
val isAdult: Boolean? = null
|
||||
)
|
||||
|
||||
@@ -5,6 +5,5 @@ data class GetRecentRoomInfoResponse(
|
||||
val notice: String,
|
||||
var coverImageUrl: String,
|
||||
val coverImagePath: String,
|
||||
val numberOfPeople: Int,
|
||||
val genderRestriction: GenderRestriction
|
||||
val numberOfPeople: Int
|
||||
)
|
||||
|
||||
@@ -32,9 +32,7 @@ data class LiveRoom(
|
||||
@Enumerated(value = EnumType.STRING)
|
||||
val type: LiveRoomType = LiveRoomType.OPEN,
|
||||
@Column(nullable = true)
|
||||
var password: String? = null,
|
||||
@Enumerated(value = EnumType.STRING)
|
||||
var genderRestriction: GenderRestriction = GenderRestriction.ALL
|
||||
var password: String? = null
|
||||
) : BaseEntity() {
|
||||
@OneToOne(fetch = FetchType.LAZY)
|
||||
@JoinColumn(name = "member_id", nullable = false)
|
||||
@@ -69,7 +67,3 @@ enum class LiveRoomType {
|
||||
enum class LiveRoomStatus {
|
||||
NOW, RESERVATION
|
||||
}
|
||||
|
||||
enum class GenderRestriction {
|
||||
ALL, MALE_ONLY, FEMALE_ONLY
|
||||
}
|
||||
|
||||
@@ -14,10 +14,8 @@ import kr.co.vividnext.sodalive.live.room.donation.GetLiveRoomDonationItem
|
||||
import kr.co.vividnext.sodalive.live.room.donation.QGetLiveRoomDonationItem
|
||||
import kr.co.vividnext.sodalive.live.room.like.GetLiveRoomHeartListItem
|
||||
import kr.co.vividnext.sodalive.live.room.like.QGetLiveRoomHeartListItem
|
||||
import kr.co.vividnext.sodalive.member.Gender
|
||||
import kr.co.vividnext.sodalive.member.MemberRole
|
||||
import kr.co.vividnext.sodalive.member.QMember.member
|
||||
import kr.co.vividnext.sodalive.member.block.QBlockMember.blockMember
|
||||
import org.springframework.beans.factory.annotation.Value
|
||||
import org.springframework.data.jpa.repository.JpaRepository
|
||||
import org.springframework.stereotype.Repository
|
||||
@@ -34,8 +32,7 @@ interface LiveRoomQueryRepository {
|
||||
timezone: String,
|
||||
memberId: Long?,
|
||||
isCreator: Boolean,
|
||||
isAdult: Boolean,
|
||||
effectiveGender: Gender?
|
||||
isAdult: Boolean
|
||||
): List<LiveRoom>
|
||||
|
||||
fun getLiveRoomListReservationWithDate(
|
||||
@@ -44,16 +41,14 @@ interface LiveRoomQueryRepository {
|
||||
limit: Long,
|
||||
memberId: Long?,
|
||||
isCreator: Boolean,
|
||||
isAdult: Boolean,
|
||||
effectiveGender: Gender?
|
||||
isAdult: Boolean
|
||||
): List<LiveRoom>
|
||||
|
||||
fun getLiveRoomListReservationWithoutDate(
|
||||
timezone: String,
|
||||
memberId: Long?,
|
||||
isCreator: Boolean,
|
||||
isAdult: Boolean,
|
||||
effectiveGender: Gender?
|
||||
isAdult: Boolean
|
||||
): List<LiveRoom>
|
||||
|
||||
fun getLiveRoom(id: Long): LiveRoom?
|
||||
@@ -81,55 +76,28 @@ class LiveRoomQueryRepositoryImpl(
|
||||
timezone: String,
|
||||
memberId: Long?,
|
||||
isCreator: Boolean,
|
||||
isAdult: Boolean,
|
||||
effectiveGender: Gender?
|
||||
isAdult: Boolean
|
||||
): List<LiveRoom> {
|
||||
var where = liveRoom.channelName.isNotNull
|
||||
.and(liveRoom.channelName.isNotEmpty)
|
||||
.and(liveRoom.isActive.isTrue)
|
||||
.and(liveRoom.member.isNotNull)
|
||||
|
||||
val isAdultRestricted = !isAdult || memberId == 17L || memberId == 16L
|
||||
if (isAdultRestricted) {
|
||||
if (!isAdult) {
|
||||
where = where.and(liveRoom.isAdult.isFalse)
|
||||
}
|
||||
|
||||
val hasMemberId = memberId != null
|
||||
if (isCreator && hasMemberId) {
|
||||
if (isCreator && memberId != null) {
|
||||
where = where.and(
|
||||
liveRoom.isAvailableJoinCreator.isTrue
|
||||
.or(liveRoom.member.id.eq(memberId))
|
||||
)
|
||||
}
|
||||
|
||||
if (effectiveGender != null && effectiveGender != Gender.NONE) {
|
||||
val genderCondition = when (effectiveGender) {
|
||||
Gender.MALE -> liveRoom.genderRestriction.`in`(GenderRestriction.ALL, GenderRestriction.MALE_ONLY)
|
||||
Gender.FEMALE -> liveRoom.genderRestriction.`in`(GenderRestriction.ALL, GenderRestriction.FEMALE_ONLY)
|
||||
Gender.NONE -> liveRoom.genderRestriction.isNotNull
|
||||
}
|
||||
where = if (hasMemberId) {
|
||||
where.and(genderCondition.or(liveRoom.member.id.eq(memberId)))
|
||||
} else {
|
||||
where.and(genderCondition)
|
||||
}
|
||||
}
|
||||
|
||||
var select = queryFactory
|
||||
return queryFactory
|
||||
.selectFrom(liveRoom)
|
||||
.innerJoin(liveRoom.member, member)
|
||||
.leftJoin(quarterLiveRankings).on(liveRoom.id.eq(quarterLiveRankings.roomId))
|
||||
|
||||
if (hasMemberId) {
|
||||
val blockMemberCondition = blockMember.member.id.eq(member.id)
|
||||
.and(blockMember.blockedMember.id.eq(memberId))
|
||||
.and(blockMember.isActive.isTrue)
|
||||
|
||||
select = select.leftJoin(blockMember).on(blockMemberCondition)
|
||||
where = where.and(blockMember.id.isNull)
|
||||
}
|
||||
|
||||
return select
|
||||
.where(where)
|
||||
.offset(offset)
|
||||
.limit(limit)
|
||||
@@ -148,8 +116,7 @@ class LiveRoomQueryRepositoryImpl(
|
||||
limit: Long,
|
||||
memberId: Long?,
|
||||
isCreator: Boolean,
|
||||
isAdult: Boolean,
|
||||
effectiveGender: Gender?
|
||||
isAdult: Boolean
|
||||
): List<LiveRoom> {
|
||||
var where = liveRoom.beginDateTime.goe(date)
|
||||
.and(liveRoom.beginDateTime.lt(date.plusDays(1)))
|
||||
@@ -160,8 +127,7 @@ class LiveRoomQueryRepositoryImpl(
|
||||
.and(liveRoom.isActive.isTrue)
|
||||
.and(liveRoom.member.isNotNull)
|
||||
|
||||
val isAdultRestricted = !isAdult || memberId == 17L || memberId == 16L
|
||||
if (isAdultRestricted) {
|
||||
if (!isAdult) {
|
||||
where = where.and(liveRoom.isAdult.isFalse)
|
||||
}
|
||||
|
||||
@@ -172,33 +138,9 @@ class LiveRoomQueryRepositoryImpl(
|
||||
)
|
||||
}
|
||||
|
||||
if (effectiveGender != null && effectiveGender != Gender.NONE) {
|
||||
val genderCondition = when (effectiveGender) {
|
||||
Gender.MALE -> liveRoom.genderRestriction.`in`(GenderRestriction.ALL, GenderRestriction.MALE_ONLY)
|
||||
Gender.FEMALE -> liveRoom.genderRestriction.`in`(GenderRestriction.ALL, GenderRestriction.FEMALE_ONLY)
|
||||
Gender.NONE -> liveRoom.genderRestriction.isNotNull
|
||||
}
|
||||
where = if (memberId != null) {
|
||||
where.and(genderCondition.or(liveRoom.member.id.eq(memberId)))
|
||||
} else {
|
||||
where.and(genderCondition)
|
||||
}
|
||||
}
|
||||
|
||||
var select = queryFactory
|
||||
return queryFactory
|
||||
.selectFrom(liveRoom)
|
||||
.innerJoin(liveRoom.member, member)
|
||||
|
||||
if (memberId != null) {
|
||||
val blockMemberCondition = blockMember.member.id.eq(member.id)
|
||||
.and(blockMember.blockedMember.id.eq(memberId))
|
||||
.and(blockMember.isActive.isTrue)
|
||||
|
||||
select = select.leftJoin(blockMember).on(blockMemberCondition)
|
||||
where = where.and(blockMember.id.isNull)
|
||||
}
|
||||
|
||||
return select
|
||||
.where(where)
|
||||
.offset(offset)
|
||||
.limit(limit)
|
||||
@@ -210,8 +152,7 @@ class LiveRoomQueryRepositoryImpl(
|
||||
timezone: String,
|
||||
memberId: Long?,
|
||||
isCreator: Boolean,
|
||||
isAdult: Boolean,
|
||||
effectiveGender: Gender?
|
||||
isAdult: Boolean
|
||||
): List<LiveRoom> {
|
||||
var where = liveRoom.beginDateTime.gt(
|
||||
LocalDateTime.now()
|
||||
@@ -226,8 +167,7 @@ class LiveRoomQueryRepositoryImpl(
|
||||
.and(liveRoom.isActive.isTrue)
|
||||
.and(liveRoom.member.isNotNull)
|
||||
|
||||
val isAdultRestricted = !isAdult || memberId == 17L || memberId == 16L
|
||||
if (isAdultRestricted) {
|
||||
if (!isAdult) {
|
||||
where = where.and(liveRoom.isAdult.isFalse)
|
||||
}
|
||||
|
||||
@@ -238,19 +178,6 @@ class LiveRoomQueryRepositoryImpl(
|
||||
)
|
||||
}
|
||||
|
||||
if (effectiveGender != null && effectiveGender != Gender.NONE) {
|
||||
val genderCondition = when (effectiveGender) {
|
||||
Gender.MALE -> liveRoom.genderRestriction.`in`(GenderRestriction.ALL, GenderRestriction.MALE_ONLY)
|
||||
Gender.FEMALE -> liveRoom.genderRestriction.`in`(GenderRestriction.ALL, GenderRestriction.FEMALE_ONLY)
|
||||
Gender.NONE -> liveRoom.genderRestriction.isNotNull
|
||||
}
|
||||
where = if (memberId != null) {
|
||||
where.and(genderCondition.or(liveRoom.member.id.eq(memberId)))
|
||||
} else {
|
||||
where.and(genderCondition)
|
||||
}
|
||||
}
|
||||
|
||||
val orderBy = if (memberId != null) {
|
||||
listOf(
|
||||
CaseBuilder()
|
||||
@@ -263,21 +190,10 @@ class LiveRoomQueryRepositoryImpl(
|
||||
listOf(liveRoom.beginDateTime.asc())
|
||||
}
|
||||
|
||||
var select = queryFactory
|
||||
return queryFactory
|
||||
.selectFrom(liveRoom)
|
||||
.innerJoin(liveRoom.member, member)
|
||||
.limit(10)
|
||||
|
||||
if (memberId != null) {
|
||||
val blockMemberCondition = blockMember.member.id.eq(member.id)
|
||||
.and(blockMember.blockedMember.id.eq(memberId))
|
||||
.and(blockMember.isActive.isTrue)
|
||||
|
||||
select = select.leftJoin(blockMember).on(blockMemberCondition)
|
||||
where = where.and(blockMember.id.isNull)
|
||||
}
|
||||
|
||||
return select
|
||||
.where(where)
|
||||
.orderBy(*orderBy.toTypedArray())
|
||||
.fetch()
|
||||
@@ -315,8 +231,7 @@ class LiveRoomQueryRepositoryImpl(
|
||||
liveRoom.notice,
|
||||
liveRoom.coverImage.prepend("/").prepend(cloudFrontHost),
|
||||
liveRoom.coverImage,
|
||||
liveRoom.numberOfPeople,
|
||||
liveRoom.genderRestriction
|
||||
liveRoom.numberOfPeople
|
||||
)
|
||||
)
|
||||
.from(liveRoom)
|
||||
|
||||
@@ -21,7 +21,6 @@ import kr.co.vividnext.sodalive.explorer.ExplorerQueryRepository
|
||||
import kr.co.vividnext.sodalive.extensions.convertLocalDateTime
|
||||
import kr.co.vividnext.sodalive.fcm.FcmEvent
|
||||
import kr.co.vividnext.sodalive.fcm.FcmEventType
|
||||
import kr.co.vividnext.sodalive.fcm.PushTokenRepository
|
||||
import kr.co.vividnext.sodalive.i18n.LangContext
|
||||
import kr.co.vividnext.sodalive.i18n.SodaMessageSource
|
||||
import kr.co.vividnext.sodalive.live.reservation.LiveReservationRepository
|
||||
@@ -71,6 +70,7 @@ import java.time.LocalDateTime
|
||||
import java.time.ZoneId
|
||||
import java.time.format.DateTimeFormatter
|
||||
import java.util.Date
|
||||
import java.util.Locale
|
||||
import java.util.concurrent.locks.ReentrantReadWriteLock
|
||||
import kotlin.concurrent.write
|
||||
|
||||
@@ -96,7 +96,6 @@ class LiveRoomService(
|
||||
private val roomVisitService: LiveRoomVisitService,
|
||||
private val canPaymentService: CanPaymentService,
|
||||
private val chargeRepository: ChargeRepository,
|
||||
private val pushTokenRepository: PushTokenRepository,
|
||||
private val memberRepository: MemberRepository,
|
||||
private val tagRepository: LiveTagRepository,
|
||||
private val canRepository: CanRepository,
|
||||
@@ -128,62 +127,6 @@ class LiveRoomService(
|
||||
}
|
||||
}
|
||||
|
||||
private fun applyLanguageTagToRoomTags(
|
||||
memberId: Long?,
|
||||
tags: List<String>,
|
||||
languageTagByMemberId: Map<Long, String?>? = null
|
||||
): List<String> {
|
||||
val randomizedTags = tags.shuffled()
|
||||
val languageTag = getCreatorLanguageTag(memberId, languageTagByMemberId) ?: return randomizedTags
|
||||
val filteredTags = randomizedTags.filterNot { it == languageTag }
|
||||
return listOf(languageTag) + filteredTags
|
||||
}
|
||||
|
||||
private fun getCreatorLanguageTag(
|
||||
memberId: Long?,
|
||||
languageTagByMemberId: Map<Long, String?>? = null
|
||||
): String? {
|
||||
if (memberId == null) return null
|
||||
if (languageTagByMemberId != null && languageTagByMemberId.containsKey(memberId)) {
|
||||
return languageTagByMemberId[memberId]
|
||||
}
|
||||
|
||||
val tokens = pushTokenRepository.findByMemberId(memberId)
|
||||
val languageCode = tokens
|
||||
.filterNot { it.languageCode.isNullOrBlank() }
|
||||
.maxByOrNull { it.updatedAt ?: LocalDateTime.MIN }
|
||||
?.languageCode
|
||||
|
||||
return resolveLanguageTag(languageCode)
|
||||
}
|
||||
|
||||
private fun buildLanguageTagMap(memberIds: List<Long>): Map<Long, String?> {
|
||||
val tokens = pushTokenRepository.findByMemberIds(memberIds)
|
||||
if (tokens.isEmpty()) return emptyMap()
|
||||
|
||||
val latestTokenByMemberId = tokens
|
||||
.filter { it.member?.id != null }
|
||||
.groupBy { it.member!!.id!! }
|
||||
.mapValues { (_, memberTokens) ->
|
||||
memberTokens.maxByOrNull { it.updatedAt ?: LocalDateTime.MIN }
|
||||
}
|
||||
|
||||
return latestTokenByMemberId.mapValues { (_, token) ->
|
||||
resolveLanguageTag(token?.languageCode)
|
||||
}
|
||||
}
|
||||
|
||||
private fun resolveLanguageTag(languageCode: String?): String? {
|
||||
val key = when (languageCode?.lowercase()?.take(2)) {
|
||||
"ko" -> "live.room.language_tag.korean"
|
||||
"ja" -> "live.room.language_tag.japanese"
|
||||
"en" -> "live.room.language_tag.english"
|
||||
else -> null
|
||||
} ?: return null
|
||||
|
||||
return messageSource.getMessage(key, langContext.lang)
|
||||
}
|
||||
|
||||
@Transactional(readOnly = true)
|
||||
fun getRoomList(
|
||||
dateString: String?,
|
||||
@@ -193,21 +136,13 @@ class LiveRoomService(
|
||||
member: Member?,
|
||||
timezone: String
|
||||
): List<GetRoomListResponse> {
|
||||
val effectiveGender = member?.let {
|
||||
if (it.auth != null) {
|
||||
if (it.auth!!.gender == 1) Gender.MALE else Gender.FEMALE
|
||||
} else {
|
||||
it.gender
|
||||
}
|
||||
}
|
||||
val roomList = if (status == LiveRoomStatus.NOW) {
|
||||
getLiveRoomListNow(
|
||||
pageable,
|
||||
timezone,
|
||||
memberId = member?.id,
|
||||
isCreator = member?.role == MemberRole.CREATOR,
|
||||
isAdult = true,
|
||||
effectiveGender = effectiveGender
|
||||
isAdult = member?.auth != null && isAdultContentVisible
|
||||
)
|
||||
} else if (dateString != null) {
|
||||
getLiveRoomListReservationWithDate(
|
||||
@@ -216,23 +151,25 @@ class LiveRoomService(
|
||||
timezone,
|
||||
memberId = member?.id,
|
||||
isCreator = member?.role == MemberRole.CREATOR,
|
||||
isAdult = member?.auth != null && isAdultContentVisible,
|
||||
effectiveGender = effectiveGender
|
||||
isAdult = member?.auth != null && isAdultContentVisible
|
||||
)
|
||||
} else {
|
||||
getLiveRoomListReservationWithoutDate(
|
||||
timezone,
|
||||
isCreator = member?.role == MemberRole.CREATOR,
|
||||
memberId = member?.id,
|
||||
isAdult = member?.auth != null && isAdultContentVisible,
|
||||
effectiveGender = effectiveGender
|
||||
isAdult = member?.auth != null && isAdultContentVisible
|
||||
)
|
||||
}
|
||||
|
||||
val creatorIds = roomList.mapNotNull { it.member?.id }.distinct()
|
||||
val languageTagByMemberId = buildLanguageTagMap(creatorIds)
|
||||
|
||||
return roomList
|
||||
.filter {
|
||||
if (member?.id != null) {
|
||||
!blockMemberRepository.isBlocked(blockedMemberId = member.id!!, memberId = it.member!!.id!!)
|
||||
} else {
|
||||
true
|
||||
}
|
||||
}
|
||||
.map {
|
||||
val roomInfo = roomInfoRepository.findByIdOrNull(it.id!!)
|
||||
|
||||
@@ -250,18 +187,13 @@ class LiveRoomService(
|
||||
.withZoneSameInstant(ZoneId.of(timezone))
|
||||
.format(
|
||||
DateTimeFormatter
|
||||
.ofPattern(messageSource.getMessage("live.room.datetime_format", langContext.lang).orEmpty())
|
||||
.withLocale(langContext.lang.locale)
|
||||
.ofPattern("yyyy년 MM월 dd일 (E) a hh시 mm분")
|
||||
.withLocale(Locale.KOREAN)
|
||||
)
|
||||
|
||||
val beginDateTimeUtc = it.beginDateTime
|
||||
.format(DateTimeFormatter.ISO_LOCAL_DATE_TIME)
|
||||
|
||||
val tags = it.tags
|
||||
.filter { tag -> tag.tag.isActive }
|
||||
.map { tag -> tag.tag.tag }
|
||||
.let { list -> applyLanguageTagToRoomTags(it.member?.id, list, languageTagByMemberId) }
|
||||
|
||||
GetRoomListResponse(
|
||||
roomId = it.id!!,
|
||||
title = it.title,
|
||||
@@ -282,7 +214,11 @@ class LiveRoomService(
|
||||
},
|
||||
creatorNickname = it.member!!.nickname,
|
||||
creatorId = it.member!!.id!!,
|
||||
tags = tags,
|
||||
tags = it.tags
|
||||
.asSequence()
|
||||
.filter { tag -> tag.tag.isActive }
|
||||
.map { tag -> tag.tag.tag }
|
||||
.toList(),
|
||||
coverImageUrl = if (it.coverImage!!.startsWith("https://")) {
|
||||
it.coverImage!!
|
||||
} else {
|
||||
@@ -299,8 +235,7 @@ class LiveRoomService(
|
||||
timezone: String,
|
||||
memberId: Long?,
|
||||
isCreator: Boolean,
|
||||
isAdult: Boolean,
|
||||
effectiveGender: Gender?
|
||||
isAdult: Boolean
|
||||
): List<LiveRoom> {
|
||||
return repository.getLiveRoomListNow(
|
||||
offset = pageable.offset,
|
||||
@@ -308,8 +243,7 @@ class LiveRoomService(
|
||||
timezone = timezone,
|
||||
memberId = memberId,
|
||||
isCreator = isCreator,
|
||||
isAdult = isAdult,
|
||||
effectiveGender = effectiveGender
|
||||
isAdult = isAdult
|
||||
)
|
||||
}
|
||||
|
||||
@@ -319,8 +253,7 @@ class LiveRoomService(
|
||||
timezone: String,
|
||||
memberId: Long?,
|
||||
isCreator: Boolean,
|
||||
isAdult: Boolean,
|
||||
effectiveGender: Gender?
|
||||
isAdult: Boolean
|
||||
): List<LiveRoom> {
|
||||
val dateTimeFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd")
|
||||
val date = LocalDate.parse(dateString, dateTimeFormatter).atStartOfDay()
|
||||
@@ -334,8 +267,7 @@ class LiveRoomService(
|
||||
limit = pageable.pageSize.toLong(),
|
||||
memberId = memberId,
|
||||
isCreator = isCreator,
|
||||
isAdult = isAdult,
|
||||
effectiveGender = effectiveGender
|
||||
isAdult = isAdult
|
||||
)
|
||||
}
|
||||
|
||||
@@ -343,10 +275,9 @@ class LiveRoomService(
|
||||
timezone: String,
|
||||
memberId: Long?,
|
||||
isCreator: Boolean,
|
||||
isAdult: Boolean,
|
||||
effectiveGender: Gender?
|
||||
isAdult: Boolean
|
||||
): List<LiveRoom> {
|
||||
return repository.getLiveRoomListReservationWithoutDate(timezone, memberId, isCreator, isAdult, effectiveGender)
|
||||
return repository.getLiveRoomListReservationWithoutDate(timezone, memberId, isCreator, isAdult)
|
||||
}
|
||||
|
||||
@Transactional
|
||||
@@ -408,8 +339,7 @@ class LiveRoomService(
|
||||
},
|
||||
type = request.type,
|
||||
password = request.password,
|
||||
isAvailableJoinCreator = request.isAvailableJoinCreator,
|
||||
genderRestriction = request.genderRestriction
|
||||
isAvailableJoinCreator = request.isAvailableJoinCreator
|
||||
)
|
||||
room.member = member
|
||||
|
||||
@@ -475,21 +405,35 @@ class LiveRoomService(
|
||||
}
|
||||
}
|
||||
|
||||
val createdMessage = if (createdRoom.channelName != null) {
|
||||
formatMessage("live.room.fcm.message.started", createdRoom.title)
|
||||
} else {
|
||||
formatMessage("live.room.fcm.message.reserved", createdRoom.title)
|
||||
}
|
||||
|
||||
applicationEventPublisher.publishEvent(
|
||||
FcmEvent(
|
||||
type = FcmEventType.CREATE_LIVE,
|
||||
title = createdRoom.member!!.nickname,
|
||||
messageKey = if (createdRoom.channelName != null) {
|
||||
"live.room.fcm.message.started"
|
||||
} else {
|
||||
"live.room.fcm.message.reserved"
|
||||
},
|
||||
args = listOf(createdRoom.title),
|
||||
message = createdMessage,
|
||||
isAuth = createdRoom.isAdult,
|
||||
isAvailableJoinCreator = createdRoom.isAvailableJoinCreator,
|
||||
roomId = createdRoom.id,
|
||||
creatorId = createdRoom.member!!.id,
|
||||
genderRestriction = createdRoom.genderRestriction
|
||||
container = "ios"
|
||||
)
|
||||
)
|
||||
|
||||
applicationEventPublisher.publishEvent(
|
||||
FcmEvent(
|
||||
type = FcmEventType.CREATE_LIVE,
|
||||
title = createdRoom.member!!.nickname,
|
||||
message = createdMessage,
|
||||
isAuth = createdRoom.isAdult,
|
||||
isAvailableJoinCreator = createdRoom.isAvailableJoinCreator,
|
||||
roomId = createdRoom.id,
|
||||
creatorId = createdRoom.member!!.id,
|
||||
container = "aos"
|
||||
)
|
||||
)
|
||||
|
||||
@@ -504,10 +448,6 @@ class LiveRoomService(
|
||||
throw SodaException(messageKey = "live.room.adult_verification_required")
|
||||
}
|
||||
|
||||
if (!member.canEnter(room.genderRestriction) && room.member!!.id!! != member.id!!) {
|
||||
throw SodaException(messageKey = "live.room.gender_restricted")
|
||||
}
|
||||
|
||||
val beginDateTime = room.beginDateTime
|
||||
.atZone(ZoneId.of("UTC"))
|
||||
.withZoneSameInstant(ZoneId.of(timezone))
|
||||
@@ -516,16 +456,12 @@ class LiveRoomService(
|
||||
val beginDateTimeUtc = room.beginDateTime
|
||||
.format(DateTimeFormatter.ISO_LOCAL_DATE_TIME)
|
||||
|
||||
val languageTagByMemberId = buildLanguageTagMap(listOfNotNull(room.member?.id))
|
||||
val response = GetRoomDetailResponse(
|
||||
roomId = roomId,
|
||||
title = room.title,
|
||||
notice = room.notice,
|
||||
price = room.price,
|
||||
tags = room.tags
|
||||
.filter { it.tag.isActive }
|
||||
.map { it.tag.tag }
|
||||
.let { tags -> applyLanguageTagToRoomTags(room.member?.id, tags, languageTagByMemberId) },
|
||||
tags = room.tags.asSequence().filter { it.tag.isActive }.map { it.tag.tag }.toList(),
|
||||
numberOfParticipantsTotal = room.numberOfPeople,
|
||||
numberOfParticipants = 0,
|
||||
channelName = room.channelName,
|
||||
@@ -534,7 +470,6 @@ class LiveRoomService(
|
||||
isPaid = false,
|
||||
isAdult = room.isAdult,
|
||||
isPrivateRoom = room.type == LiveRoomType.PRIVATE,
|
||||
genderRestriction = room.genderRestriction,
|
||||
password = room.password
|
||||
)
|
||||
response.manager = GetRoomDetailManager(room.member!!, cloudFrontHost = cloudFrontHost)
|
||||
@@ -645,17 +580,30 @@ class LiveRoomService(
|
||||
|
||||
room.beginDateTime = nowDateTime
|
||||
|
||||
val startedMessage = formatMessage("live.room.fcm.message.started_now", room.title)
|
||||
applicationEventPublisher.publishEvent(
|
||||
FcmEvent(
|
||||
type = FcmEventType.START_LIVE,
|
||||
title = room.member!!.nickname,
|
||||
messageKey = "live.room.fcm.message.started",
|
||||
args = listOf(room.title),
|
||||
message = startedMessage,
|
||||
isAuth = room.isAdult,
|
||||
isAvailableJoinCreator = room.isAvailableJoinCreator,
|
||||
roomId = room.id,
|
||||
creatorId = room.member!!.id,
|
||||
genderRestriction = room.genderRestriction
|
||||
container = "ios"
|
||||
)
|
||||
)
|
||||
|
||||
applicationEventPublisher.publishEvent(
|
||||
FcmEvent(
|
||||
type = FcmEventType.START_LIVE,
|
||||
title = room.member!!.nickname,
|
||||
message = startedMessage,
|
||||
isAuth = room.isAdult,
|
||||
isAvailableJoinCreator = room.isAvailableJoinCreator,
|
||||
roomId = room.id,
|
||||
creatorId = room.member!!.id,
|
||||
container = "aos"
|
||||
)
|
||||
)
|
||||
}
|
||||
@@ -712,17 +660,16 @@ class LiveRoomService(
|
||||
}
|
||||
}
|
||||
|
||||
val pushTokens = memberRepository.getPushTokenFromReservationList(request.roomId)
|
||||
|
||||
val pushTokenListMap = memberRepository.getPushTokenFromReservationList(request.roomId)
|
||||
reservationRepository.cancelReservation(roomId = room.id!!)
|
||||
|
||||
val cancelMessage = formatMessage("live.room.fcm.message.canceled", room.title)
|
||||
applicationEventPublisher.publishEvent(
|
||||
FcmEvent(
|
||||
type = FcmEventType.CANCEL_LIVE,
|
||||
title = room.member!!.nickname,
|
||||
messageKey = "live.room.fcm.message.canceled",
|
||||
args = listOf(room.title),
|
||||
pushTokens = pushTokens
|
||||
message = cancelMessage,
|
||||
recipientsMap = pushTokenListMap
|
||||
)
|
||||
)
|
||||
}
|
||||
@@ -754,10 +701,6 @@ class LiveRoomService(
|
||||
)
|
||||
}
|
||||
|
||||
if (room.member!!.id!! != member.id!! && !member.canEnter(room.genderRestriction)) {
|
||||
throw SodaException(messageKey = "live.room.gender_restricted")
|
||||
}
|
||||
|
||||
val lock = getOrCreateLock(memberId = member.id!!)
|
||||
lock.write {
|
||||
var roomInfo = roomInfoRepository.findByIdOrNull(request.roomId)
|
||||
@@ -872,10 +815,6 @@ class LiveRoomService(
|
||||
room.isAdult = request.isAdult
|
||||
}
|
||||
|
||||
if (request.genderRestriction != null) {
|
||||
room.genderRestriction = request.genderRestriction
|
||||
}
|
||||
|
||||
if (request.isActiveMenuPan != null) {
|
||||
if (request.isActiveMenuPan) {
|
||||
if (request.menuPanId > 0) {
|
||||
@@ -917,7 +856,7 @@ class LiveRoomService(
|
||||
agoraAppId,
|
||||
agoraAppCertificate,
|
||||
room.channelName!!,
|
||||
member.id!!.toString(),
|
||||
member.id!!.toInt(),
|
||||
expireTimestamp.toInt()
|
||||
)
|
||||
|
||||
@@ -928,14 +867,6 @@ class LiveRoomService(
|
||||
expireTimestamp.toInt()
|
||||
)
|
||||
|
||||
val v2vWorkerToken = rtcTokenBuilder.buildTokenWithUid(
|
||||
agoraAppId,
|
||||
agoraAppCertificate,
|
||||
room.channelName!!,
|
||||
"${member.id!!}333",
|
||||
expireTimestamp.toInt()
|
||||
)
|
||||
|
||||
val isFollowing = explorerQueryRepository
|
||||
.getNotificationUserIds(room.member!!.id!!)
|
||||
.contains(member.id)
|
||||
@@ -963,12 +894,6 @@ class LiveRoomService(
|
||||
}
|
||||
|
||||
val menuPan = menuService.getLiveMenu(creatorId = room.member!!.id!!)
|
||||
val creatorLanguageCode = pushTokenRepository.findByMemberId(room.member!!.id!!)
|
||||
.filterNot { it.languageCode.isNullOrBlank() }
|
||||
.maxByOrNull { it.updatedAt ?: LocalDateTime.MIN }
|
||||
?.languageCode
|
||||
?.lowercase()
|
||||
?.take(2)
|
||||
|
||||
return GetRoomInfoResponse(
|
||||
roomId = roomId,
|
||||
@@ -990,7 +915,6 @@ class LiveRoomService(
|
||||
channelName = room.channelName!!,
|
||||
rtcToken = rtcToken,
|
||||
rtmToken = rtmToken,
|
||||
v2vWorkerToken = v2vWorkerToken,
|
||||
creatorId = room.member!!.id!!,
|
||||
creatorNickname = room.member!!.nickname,
|
||||
creatorProfileUrl = if (room.member!!.profileImage != null) {
|
||||
@@ -1007,7 +931,6 @@ class LiveRoomService(
|
||||
managerList = roomInfo.managerList,
|
||||
donationRankingTop3UserIds = donationRankingTop3UserIds,
|
||||
menuPan = menuPan?.menu ?: "",
|
||||
creatorLanguageCode = creatorLanguageCode,
|
||||
isPrivateRoom = room.type == LiveRoomType.PRIVATE,
|
||||
password = room.password,
|
||||
isActiveRoulette = isActiveRoulette
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
package kr.co.vividnext.sodalive.live.room.detail
|
||||
|
||||
import kr.co.vividnext.sodalive.live.room.GenderRestriction
|
||||
import kr.co.vividnext.sodalive.member.Member
|
||||
import kr.co.vividnext.sodalive.member.MemberRole
|
||||
|
||||
@@ -12,7 +11,6 @@ data class GetRoomDetailResponse(
|
||||
var isPaid: Boolean,
|
||||
val isAdult: Boolean,
|
||||
val isPrivateRoom: Boolean,
|
||||
val genderRestriction: GenderRestriction,
|
||||
val password: String?,
|
||||
val tags: List<String>,
|
||||
val channelName: String?,
|
||||
|
||||
@@ -8,7 +8,6 @@ data class GetRoomInfoResponse(
|
||||
val channelName: String,
|
||||
val rtcToken: String,
|
||||
val rtmToken: String,
|
||||
val v2vWorkerToken: String,
|
||||
val creatorId: Long,
|
||||
val creatorNickname: String,
|
||||
val creatorProfileUrl: String,
|
||||
@@ -21,7 +20,6 @@ data class GetRoomInfoResponse(
|
||||
val managerList: List<LiveRoomMember>,
|
||||
val donationRankingTop3UserIds: List<Long>,
|
||||
val menuPan: String,
|
||||
val creatorLanguageCode: String?,
|
||||
val isPrivateRoom: Boolean = false,
|
||||
val password: String? = null,
|
||||
val isActiveRoulette: Boolean = false
|
||||
|
||||
@@ -2,7 +2,6 @@ package kr.co.vividnext.sodalive.member
|
||||
|
||||
import kr.co.vividnext.sodalive.common.BaseEntity
|
||||
import kr.co.vividnext.sodalive.explorer.GetExplorerSectionCreatorResponse
|
||||
import kr.co.vividnext.sodalive.live.room.GenderRestriction
|
||||
import kr.co.vividnext.sodalive.member.auth.Auth
|
||||
import kr.co.vividnext.sodalive.member.following.CreatorFollowing
|
||||
import kr.co.vividnext.sodalive.member.notification.MemberNotification
|
||||
@@ -20,7 +19,7 @@ import javax.persistence.OneToOne
|
||||
|
||||
@Entity
|
||||
data class Member(
|
||||
var email: String? = null,
|
||||
val email: String,
|
||||
var password: String,
|
||||
var nickname: String,
|
||||
var profileImage: String? = null,
|
||||
@@ -28,7 +27,6 @@ data class Member(
|
||||
val kakaoId: Long? = null,
|
||||
val googleId: String? = null,
|
||||
val appleId: String? = null,
|
||||
val lineId: String? = null,
|
||||
|
||||
@Enumerated(EnumType.STRING)
|
||||
val provider: MemberProvider = MemberProvider.EMAIL,
|
||||
@@ -47,15 +45,9 @@ data class Member(
|
||||
|
||||
var isVisibleDonationRank: Boolean = true,
|
||||
|
||||
@Enumerated(value = EnumType.STRING)
|
||||
var donationRankingPeriod: DonationRankingPeriod? = DonationRankingPeriod.CUMULATIVE,
|
||||
|
||||
var isActive: Boolean = true,
|
||||
|
||||
var container: String = "web",
|
||||
|
||||
// ISO 3166-1 alpha-2 국가 코드
|
||||
var countryCode: String? = null
|
||||
var container: String = "web"
|
||||
) : BaseEntity() {
|
||||
@OneToMany(mappedBy = "member", cascade = [CascadeType.ALL])
|
||||
val stipulationAgrees: MutableList<StipulationAgree> = mutableListOf()
|
||||
@@ -152,22 +144,6 @@ data class Member(
|
||||
follow = follow
|
||||
)
|
||||
}
|
||||
|
||||
fun canEnter(restriction: GenderRestriction): Boolean {
|
||||
val effectiveGender = if (auth != null) {
|
||||
if (auth!!.gender == 1) Gender.MALE else Gender.FEMALE
|
||||
} else {
|
||||
gender
|
||||
}
|
||||
|
||||
if (effectiveGender == Gender.NONE) return true
|
||||
|
||||
return when (restriction) {
|
||||
GenderRestriction.ALL -> true
|
||||
GenderRestriction.MALE_ONLY -> effectiveGender == Gender.MALE
|
||||
GenderRestriction.FEMALE_ONLY -> effectiveGender == Gender.FEMALE
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
enum class Gender {
|
||||
@@ -179,9 +155,5 @@ enum class MemberRole {
|
||||
}
|
||||
|
||||
enum class MemberProvider {
|
||||
EMAIL, KAKAO, GOOGLE, APPLE, LINE
|
||||
}
|
||||
|
||||
enum class DonationRankingPeriod {
|
||||
WEEKLY, CUMULATIVE
|
||||
EMAIL, KAKAO, GOOGLE, APPLE
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@ import org.springframework.security.core.authority.SimpleGrantedAuthority
|
||||
import org.springframework.security.core.userdetails.User
|
||||
|
||||
class MemberAdapter(val member: Member) : User(
|
||||
member.email ?: "member:${member.id}",
|
||||
member.email,
|
||||
member.password,
|
||||
listOf(SimpleGrantedAuthority("ROLE_${member.role.name}"))
|
||||
)
|
||||
|
||||
@@ -13,7 +13,8 @@ import kr.co.vividnext.sodalive.member.login.LoginResponse
|
||||
import kr.co.vividnext.sodalive.member.login.SocialLoginRequest
|
||||
import kr.co.vividnext.sodalive.member.notification.UpdateNotificationSettingRequest
|
||||
import kr.co.vividnext.sodalive.member.signUp.SignUpRequestV2
|
||||
import kr.co.vividnext.sodalive.member.social.SocialAuthServiceResolver
|
||||
import kr.co.vividnext.sodalive.member.social.google.GoogleAuthService
|
||||
import kr.co.vividnext.sodalive.member.social.kakao.KakaoAuthService
|
||||
import kr.co.vividnext.sodalive.useraction.ActionType
|
||||
import kr.co.vividnext.sodalive.useraction.UserActionService
|
||||
import org.springframework.data.domain.Pageable
|
||||
@@ -35,7 +36,8 @@ import org.springframework.web.multipart.MultipartFile
|
||||
@RequestMapping("/member")
|
||||
class MemberController(
|
||||
private val service: MemberService,
|
||||
private val socialAuthServiceResolver: SocialAuthServiceResolver,
|
||||
private val kakaoAuthService: KakaoAuthService,
|
||||
private val googleAuthService: GoogleAuthService,
|
||||
private val trackingService: AdTrackingService,
|
||||
private val userActionService: UserActionService,
|
||||
private val messageSource: SodaMessageSource,
|
||||
@@ -343,59 +345,12 @@ class MemberController(
|
||||
@RequestHeader("Authorization") authHeader: String,
|
||||
@RequestBody request: SocialLoginRequest
|
||||
): ApiResponse<LoginResponse> {
|
||||
val token = extractBearerToken(authHeader, MemberProvider.GOOGLE)
|
||||
return processSocialLogin(MemberProvider.GOOGLE, token, request, null)
|
||||
}
|
||||
if (!authHeader.startsWith("Bearer ")) {
|
||||
throw SodaException(messageKey = "member.social.google_login_failed")
|
||||
}
|
||||
|
||||
@PostMapping("/login/kakao")
|
||||
fun loginKakao(
|
||||
@RequestHeader("Authorization") authHeader: String,
|
||||
@RequestBody request: SocialLoginRequest
|
||||
): ApiResponse<LoginResponse> {
|
||||
val token = extractBearerToken(authHeader, MemberProvider.KAKAO)
|
||||
return processSocialLogin(MemberProvider.KAKAO, token, request, null)
|
||||
}
|
||||
|
||||
@PostMapping("/login/apple")
|
||||
fun loginApple(
|
||||
@RequestBody request: SocialLoginRequest
|
||||
): ApiResponse<LoginResponse> {
|
||||
val errorKey = socialLoginErrorKey(MemberProvider.APPLE)
|
||||
val token = request.identityToken?.takeIf { it.isNotBlank() }
|
||||
?: throw SodaException(messageKey = errorKey)
|
||||
val nonce = request.nonce?.takeIf { it.isNotBlank() }
|
||||
?: throw SodaException(messageKey = errorKey)
|
||||
|
||||
return processSocialLogin(MemberProvider.APPLE, token, request, nonce)
|
||||
}
|
||||
|
||||
@PostMapping("/login/line")
|
||||
fun loginLine(
|
||||
@RequestBody request: SocialLoginRequest
|
||||
): ApiResponse<LoginResponse> {
|
||||
val errorKey = socialLoginErrorKey(MemberProvider.LINE)
|
||||
val token = request.identityToken?.takeIf { it.isNotBlank() }
|
||||
?: throw SodaException(messageKey = errorKey)
|
||||
val nonce = request.nonce?.takeIf { it.isNotBlank() }
|
||||
?: throw SodaException(messageKey = errorKey)
|
||||
|
||||
return processSocialLogin(MemberProvider.LINE, token, request, nonce)
|
||||
}
|
||||
|
||||
private fun processSocialLogin(
|
||||
provider: MemberProvider,
|
||||
token: String,
|
||||
request: SocialLoginRequest,
|
||||
nonce: String?
|
||||
): ApiResponse<LoginResponse> {
|
||||
val authService = socialAuthServiceResolver.resolve(provider)
|
||||
val response = authService.authenticate(
|
||||
token = token,
|
||||
container = request.container,
|
||||
marketingPid = request.marketingPid,
|
||||
pushToken = request.pushToken,
|
||||
nonce = nonce
|
||||
)
|
||||
val token = authHeader.substring(7)
|
||||
val response = googleAuthService.authenticate(token, request.container, request.marketingPid, request.pushToken)
|
||||
|
||||
if (!response.marketingPid.isNullOrBlank()) {
|
||||
trackingService.saveTrackingHistory(
|
||||
@@ -417,21 +372,35 @@ class MemberController(
|
||||
return ApiResponse.ok(message = message, data = response.loginResponse)
|
||||
}
|
||||
|
||||
private fun extractBearerToken(authHeader: String, provider: MemberProvider): String {
|
||||
val errorKey = socialLoginErrorKey(provider)
|
||||
@PostMapping("/login/kakao")
|
||||
fun loginKakao(
|
||||
@RequestHeader("Authorization") authHeader: String,
|
||||
@RequestBody request: SocialLoginRequest
|
||||
): ApiResponse<LoginResponse> {
|
||||
if (!authHeader.startsWith("Bearer ")) {
|
||||
throw SodaException(messageKey = errorKey)
|
||||
throw SodaException(messageKey = "member.social.kakao_login_failed")
|
||||
}
|
||||
return authHeader.substring(7)
|
||||
}
|
||||
|
||||
private fun socialLoginErrorKey(provider: MemberProvider): String {
|
||||
return when (provider) {
|
||||
MemberProvider.GOOGLE -> "member.social.google_login_failed"
|
||||
MemberProvider.KAKAO -> "member.social.kakao_login_failed"
|
||||
MemberProvider.APPLE -> "member.social.apple_login_failed"
|
||||
MemberProvider.LINE -> "member.social.line_login_failed"
|
||||
else -> "common.error.bad_request"
|
||||
val token = authHeader.substring(7)
|
||||
val response = kakaoAuthService.authenticate(token, request.container, request.marketingPid, request.pushToken)
|
||||
|
||||
if (!response.marketingPid.isNullOrBlank()) {
|
||||
trackingService.saveTrackingHistory(
|
||||
pid = response.marketingPid,
|
||||
type = AdTrackingHistoryType.SIGNUP,
|
||||
memberId = response.memberId
|
||||
)
|
||||
}
|
||||
|
||||
if (response.isNew) {
|
||||
userActionService.recordAction(
|
||||
memberId = response.memberId,
|
||||
isAuth = false,
|
||||
actionType = ActionType.SIGN_UP
|
||||
)
|
||||
}
|
||||
|
||||
val message = messageSource.getMessage("member.signup.success", langContext.lang)
|
||||
return ApiResponse.ok(message = message, data = response.loginResponse)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,11 +1,10 @@
|
||||
package kr.co.vividnext.sodalive.member
|
||||
|
||||
import com.querydsl.jpa.impl.JPAQueryFactory
|
||||
import kr.co.vividnext.sodalive.fcm.PushTokenInfo
|
||||
import kr.co.vividnext.sodalive.fcm.GetMessageRecipientPushTokenResponse
|
||||
import kr.co.vividnext.sodalive.fcm.QGetMessageRecipientPushTokenResponse
|
||||
import kr.co.vividnext.sodalive.fcm.QPushToken.pushToken
|
||||
import kr.co.vividnext.sodalive.fcm.QPushTokenInfo
|
||||
import kr.co.vividnext.sodalive.live.reservation.QLiveReservation.liveReservation
|
||||
import kr.co.vividnext.sodalive.live.room.GenderRestriction
|
||||
import kr.co.vividnext.sodalive.live.room.QLiveRoom.liveRoom
|
||||
import kr.co.vividnext.sodalive.member.QMember.member
|
||||
import kr.co.vividnext.sodalive.member.auth.QAuth.auth
|
||||
@@ -21,46 +20,45 @@ import org.springframework.stereotype.Repository
|
||||
|
||||
@Repository
|
||||
interface MemberRepository : JpaRepository<Member, Long>, MemberQueryRepository {
|
||||
fun findByEmail(email: String?): Member?
|
||||
fun findByEmail(email: String): Member?
|
||||
fun findByNickname(nickname: String): Member?
|
||||
fun findByGoogleId(googleId: String): Member?
|
||||
fun findByKakaoId(kakaoId: Long): Member?
|
||||
fun findByAppleId(appleId: String): Member?
|
||||
fun findByLineId(lineId: String): Member?
|
||||
}
|
||||
|
||||
interface MemberQueryRepository {
|
||||
fun findByNicknameAndOtherCondition(nickname: String, member: Member): List<Member>
|
||||
fun findCreatorByIdOrNull(memberId: Long): Member?
|
||||
fun getAllRecipientPushTokens(isAuth: Boolean?): List<PushTokenInfo>
|
||||
fun getAllRecipientPushTokens(isAuth: Boolean?, container: String): List<List<String>>
|
||||
fun getCreateLiveRoomNotificationRecipientPushTokens(
|
||||
creatorId: Long,
|
||||
isAuth: Boolean,
|
||||
isAvailableJoinCreator: Boolean,
|
||||
genderRestriction: GenderRestriction? = null
|
||||
): List<PushTokenInfo>
|
||||
container: String
|
||||
): List<List<String>>
|
||||
|
||||
fun getStartLiveRoomNotificationRecipientPushTokens(
|
||||
creatorId: Long,
|
||||
roomId: Long,
|
||||
isAuth: Boolean,
|
||||
isAvailableJoinCreator: Boolean,
|
||||
genderRestriction: GenderRestriction? = null
|
||||
): List<PushTokenInfo>
|
||||
container: String
|
||||
): List<List<String>>
|
||||
|
||||
fun getUploadContentNotificationRecipientPushTokens(
|
||||
creatorId: Long,
|
||||
isAuth: Boolean
|
||||
): List<PushTokenInfo>
|
||||
isAuth: Boolean,
|
||||
container: String
|
||||
): List<List<String>>
|
||||
|
||||
fun getMessageRecipientPushToken(messageId: Long): PushTokenInfo?
|
||||
fun getIndividualRecipientPushTokens(recipients: List<Long>, isAuth: Boolean?): List<PushTokenInfo>
|
||||
fun getMessageRecipientPushToken(messageId: Long): GetMessageRecipientPushTokenResponse?
|
||||
fun getIndividualRecipientPushTokens(recipients: List<Long>, isAuth: Boolean?): Map<String, List<List<String>>>
|
||||
fun getChangeNicknamePrice(memberId: Long): GetChangeNicknamePriceResponse
|
||||
fun getMemberByEmail(email: String?): Member?
|
||||
fun getMemberByEmail(email: String): Member?
|
||||
|
||||
fun getChangeNoticeRecipientPushTokens(creatorId: Long): List<PushTokenInfo>
|
||||
fun getPushTokenFromReservationList(roomId: Long): List<PushTokenInfo>
|
||||
fun getAuditionNoticeRecipientPushTokens(isAuth: Boolean): List<PushTokenInfo>
|
||||
fun getChangeNoticeRecipientPushTokens(creatorId: Long): Map<String, List<List<String>>>
|
||||
fun getPushTokenFromReservationList(roomId: Long): Map<String, List<List<String>>>
|
||||
fun getAuditionNoticeRecipientPushTokens(isAuth: Boolean): Map<String, List<List<String>>>
|
||||
|
||||
fun getMemberProfile(memberId: Long, myMemberId: Long): GetMemberProfileResponse
|
||||
|
||||
@@ -105,9 +103,10 @@ class MemberQueryRepositoryImpl(
|
||||
.fetchFirst()
|
||||
}
|
||||
|
||||
override fun getAllRecipientPushTokens(isAuth: Boolean?): List<PushTokenInfo> {
|
||||
override fun getAllRecipientPushTokens(isAuth: Boolean?, container: String): List<List<String>> {
|
||||
var where = member.isActive.isTrue
|
||||
.and(member.email.notIn("admin@sodalive.net"))
|
||||
.and(pushToken.deviceType.eq(container))
|
||||
|
||||
if (isAuth != null) {
|
||||
where = if (isAuth) {
|
||||
@@ -118,26 +117,22 @@ class MemberQueryRepositoryImpl(
|
||||
}
|
||||
|
||||
return queryFactory
|
||||
.select(
|
||||
QPushTokenInfo(
|
||||
pushToken.token,
|
||||
pushToken.deviceType,
|
||||
pushToken.languageCode.coalesce("ko")
|
||||
)
|
||||
)
|
||||
.select(pushToken.token)
|
||||
.from(member)
|
||||
.innerJoin(pushToken).on(member.id.eq(pushToken.member.id))
|
||||
.leftJoin(member.auth, auth)
|
||||
.where(where)
|
||||
.fetch()
|
||||
.toSet()
|
||||
.chunked(500)
|
||||
}
|
||||
|
||||
override fun getCreateLiveRoomNotificationRecipientPushTokens(
|
||||
creatorId: Long,
|
||||
isAuth: Boolean,
|
||||
isAvailableJoinCreator: Boolean,
|
||||
genderRestriction: GenderRestriction?
|
||||
): List<PushTokenInfo> {
|
||||
container: String
|
||||
): List<List<String>> {
|
||||
val member = QMember.member
|
||||
val creator = QMember.member
|
||||
|
||||
@@ -152,6 +147,7 @@ class MemberQueryRepositoryImpl(
|
||||
)
|
||||
)
|
||||
.and(creatorFollowing.isNotify.isTrue)
|
||||
.and(pushToken.deviceType.eq(container))
|
||||
.or(member.id.eq(4))
|
||||
|
||||
if (isAuth) {
|
||||
@@ -162,18 +158,8 @@ class MemberQueryRepositoryImpl(
|
||||
where = where.and(creatorFollowing.member.role.ne(MemberRole.CREATOR))
|
||||
}
|
||||
|
||||
if (genderRestriction != null && genderRestriction != GenderRestriction.ALL) {
|
||||
where = where.and(getGenderCondition(genderRestriction))
|
||||
}
|
||||
|
||||
return queryFactory
|
||||
.select(
|
||||
QPushTokenInfo(
|
||||
pushToken.token,
|
||||
pushToken.deviceType,
|
||||
pushToken.languageCode.coalesce("ko")
|
||||
)
|
||||
)
|
||||
.select(pushToken.token)
|
||||
.from(creatorFollowing)
|
||||
.innerJoin(creatorFollowing.creator, creator)
|
||||
.innerJoin(creatorFollowing.member, member)
|
||||
@@ -182,6 +168,8 @@ class MemberQueryRepositoryImpl(
|
||||
.leftJoin(creatorFollowing.member.auth, auth)
|
||||
.where(where)
|
||||
.fetch()
|
||||
.toSet()
|
||||
.chunked(500)
|
||||
}
|
||||
|
||||
override fun getStartLiveRoomNotificationRecipientPushTokens(
|
||||
@@ -189,8 +177,8 @@ class MemberQueryRepositoryImpl(
|
||||
roomId: Long,
|
||||
isAuth: Boolean,
|
||||
isAvailableJoinCreator: Boolean,
|
||||
genderRestriction: GenderRestriction?
|
||||
): List<PushTokenInfo> {
|
||||
container: String
|
||||
): List<List<String>> {
|
||||
val member = QMember.member
|
||||
val creator = QMember.member
|
||||
|
||||
@@ -205,6 +193,7 @@ class MemberQueryRepositoryImpl(
|
||||
)
|
||||
)
|
||||
.and(creatorFollowing.isNotify.isTrue)
|
||||
.and(pushToken.deviceType.eq(container))
|
||||
.or(creatorFollowing.member.id.eq(4))
|
||||
|
||||
if (isAuth) {
|
||||
@@ -215,18 +204,8 @@ class MemberQueryRepositoryImpl(
|
||||
where = where.and(creatorFollowing.member.role.ne(MemberRole.CREATOR))
|
||||
}
|
||||
|
||||
if (genderRestriction != null && genderRestriction != GenderRestriction.ALL) {
|
||||
where = where.and(getGenderCondition(genderRestriction))
|
||||
}
|
||||
|
||||
val followingMemberPushToken = queryFactory
|
||||
.select(
|
||||
QPushTokenInfo(
|
||||
pushToken.token,
|
||||
pushToken.deviceType,
|
||||
pushToken.languageCode.coalesce("ko")
|
||||
)
|
||||
)
|
||||
.select(pushToken.token)
|
||||
.from(creatorFollowing)
|
||||
.innerJoin(creatorFollowing.creator, creator)
|
||||
.innerJoin(creatorFollowing.member, member)
|
||||
@@ -245,23 +224,14 @@ class MemberQueryRepositoryImpl(
|
||||
blockMemberRepository.getBlockedMemberIdList(creatorId)
|
||||
)
|
||||
)
|
||||
.and(pushToken.deviceType.eq(container))
|
||||
|
||||
if (isAuth) {
|
||||
where = where.and(auth.isNotNull)
|
||||
}
|
||||
|
||||
if (genderRestriction != null && genderRestriction != GenderRestriction.ALL) {
|
||||
where = where.and(getGenderCondition(genderRestriction, liveReservation.member))
|
||||
}
|
||||
|
||||
val reservationMemberPushToken = queryFactory
|
||||
.select(
|
||||
QPushTokenInfo(
|
||||
pushToken.token,
|
||||
pushToken.deviceType,
|
||||
pushToken.languageCode.coalesce("ko")
|
||||
)
|
||||
)
|
||||
.select(pushToken.token)
|
||||
.from(liveReservation)
|
||||
.innerJoin(liveReservation.member, member)
|
||||
.innerJoin(liveReservation.member.notification, memberNotification)
|
||||
@@ -270,40 +240,16 @@ class MemberQueryRepositoryImpl(
|
||||
.where(where)
|
||||
.fetch()
|
||||
|
||||
return (followingMemberPushToken + reservationMemberPushToken).distinctBy { it.token }
|
||||
}
|
||||
|
||||
private fun getGenderCondition(
|
||||
genderRestriction: GenderRestriction,
|
||||
qMember: QMember = member
|
||||
) = when (genderRestriction) {
|
||||
GenderRestriction.MALE_ONLY -> {
|
||||
auth.isNotNull.and(auth.gender.eq(1))
|
||||
.or(
|
||||
auth.isNull.and(
|
||||
qMember.gender.eq(Gender.MALE)
|
||||
.or(qMember.gender.eq(Gender.NONE))
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
GenderRestriction.FEMALE_ONLY -> {
|
||||
auth.isNotNull.and(auth.gender.eq(0))
|
||||
.or(
|
||||
auth.isNull.and(
|
||||
qMember.gender.eq(Gender.FEMALE)
|
||||
.or(qMember.gender.eq(Gender.NONE))
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
else -> null
|
||||
return (followingMemberPushToken + reservationMemberPushToken)
|
||||
.toSet()
|
||||
.chunked(500)
|
||||
}
|
||||
|
||||
override fun getUploadContentNotificationRecipientPushTokens(
|
||||
creatorId: Long,
|
||||
isAuth: Boolean
|
||||
): List<PushTokenInfo> {
|
||||
isAuth: Boolean,
|
||||
container: String
|
||||
): List<List<String>> {
|
||||
val member = QMember.member
|
||||
val creator = QMember.member
|
||||
|
||||
@@ -317,6 +263,7 @@ class MemberQueryRepositoryImpl(
|
||||
)
|
||||
)
|
||||
.and(creatorFollowing.isNotify.isTrue)
|
||||
.and(pushToken.deviceType.eq(container))
|
||||
.or(member.id.eq(4))
|
||||
|
||||
if (isAuth) {
|
||||
@@ -324,13 +271,7 @@ class MemberQueryRepositoryImpl(
|
||||
}
|
||||
|
||||
return queryFactory
|
||||
.select(
|
||||
QPushTokenInfo(
|
||||
pushToken.token,
|
||||
pushToken.deviceType,
|
||||
pushToken.languageCode.coalesce("ko")
|
||||
)
|
||||
)
|
||||
.select(pushToken.token)
|
||||
.from(creatorFollowing)
|
||||
.innerJoin(creatorFollowing.creator, creator)
|
||||
.innerJoin(creatorFollowing.member, member)
|
||||
@@ -339,15 +280,16 @@ class MemberQueryRepositoryImpl(
|
||||
.leftJoin(member.auth, auth)
|
||||
.where(where)
|
||||
.fetch()
|
||||
.toSet()
|
||||
.chunked(500)
|
||||
}
|
||||
|
||||
override fun getMessageRecipientPushToken(messageId: Long): PushTokenInfo? {
|
||||
override fun getMessageRecipientPushToken(messageId: Long): GetMessageRecipientPushTokenResponse? {
|
||||
return queryFactory
|
||||
.select(
|
||||
QPushTokenInfo(
|
||||
QGetMessageRecipientPushTokenResponse(
|
||||
pushToken.token,
|
||||
pushToken.deviceType,
|
||||
pushToken.languageCode.coalesce("ko")
|
||||
pushToken.deviceType
|
||||
)
|
||||
)
|
||||
.from(message)
|
||||
@@ -364,7 +306,7 @@ class MemberQueryRepositoryImpl(
|
||||
override fun getIndividualRecipientPushTokens(
|
||||
recipients: List<Long>,
|
||||
isAuth: Boolean?
|
||||
): List<PushTokenInfo> {
|
||||
): Map<String, List<List<String>>> {
|
||||
var where = member.isActive.isTrue
|
||||
.and(member.email.notIn("admin@sodalive.net"))
|
||||
.and(member.id.`in`(*recipients.toTypedArray()))
|
||||
@@ -377,19 +319,27 @@ class MemberQueryRepositoryImpl(
|
||||
}
|
||||
}
|
||||
|
||||
return queryFactory
|
||||
.select(
|
||||
QPushTokenInfo(
|
||||
pushToken.token,
|
||||
pushToken.deviceType,
|
||||
pushToken.languageCode.coalesce("ko")
|
||||
)
|
||||
)
|
||||
val aosPushTokens = queryFactory
|
||||
.select(pushToken.token)
|
||||
.from(member)
|
||||
.innerJoin(pushToken).on(member.id.eq(pushToken.member.id))
|
||||
.leftJoin(member.auth, auth)
|
||||
.where(where)
|
||||
.where(where.and(pushToken.deviceType.eq("aos")))
|
||||
.fetch()
|
||||
.toSet()
|
||||
.chunked(500)
|
||||
|
||||
val iosPushTokens = queryFactory
|
||||
.select(pushToken.token)
|
||||
.from(member)
|
||||
.innerJoin(pushToken).on(member.id.eq(pushToken.member.id))
|
||||
.leftJoin(member.auth, auth)
|
||||
.where(where.and(pushToken.deviceType.eq("ios")))
|
||||
.fetch()
|
||||
.toSet()
|
||||
.chunked(500)
|
||||
|
||||
return mapOf("aos" to aosPushTokens, "ios" to iosPushTokens)
|
||||
}
|
||||
|
||||
override fun getChangeNicknamePrice(memberId: Long): GetChangeNicknamePriceResponse {
|
||||
@@ -409,15 +359,14 @@ class MemberQueryRepositoryImpl(
|
||||
)
|
||||
}
|
||||
|
||||
override fun getMemberByEmail(email: String?): Member? {
|
||||
if (email == null) return null
|
||||
override fun getMemberByEmail(email: String): Member? {
|
||||
return queryFactory
|
||||
.selectFrom(member)
|
||||
.where(member.email.eq(email))
|
||||
.fetchOne()
|
||||
}
|
||||
|
||||
override fun getChangeNoticeRecipientPushTokens(creatorId: Long): List<PushTokenInfo> {
|
||||
override fun getChangeNoticeRecipientPushTokens(creatorId: Long): Map<String, List<List<String>>> {
|
||||
val member = QMember.member
|
||||
val creator = QMember.member
|
||||
|
||||
@@ -432,63 +381,90 @@ class MemberQueryRepositoryImpl(
|
||||
)
|
||||
.and(creatorFollowing.isNotify.isTrue)
|
||||
|
||||
return queryFactory
|
||||
.select(
|
||||
QPushTokenInfo(
|
||||
pushToken.token,
|
||||
pushToken.deviceType,
|
||||
pushToken.languageCode.coalesce("ko")
|
||||
)
|
||||
)
|
||||
val aosPushTokens = queryFactory
|
||||
.select(pushToken.token)
|
||||
.from(creatorFollowing)
|
||||
.innerJoin(creatorFollowing.creator, creator)
|
||||
.innerJoin(creatorFollowing.member, member)
|
||||
.innerJoin(pushToken).on(creatorFollowing.member.id.eq(pushToken.member.id))
|
||||
.where(where)
|
||||
.where(where.and(pushToken.deviceType.eq("aos")))
|
||||
.fetch()
|
||||
.toSet()
|
||||
.chunked(500)
|
||||
|
||||
val iosPushTokens = queryFactory
|
||||
.select(pushToken.token)
|
||||
.from(creatorFollowing)
|
||||
.innerJoin(creatorFollowing.creator, creator)
|
||||
.innerJoin(creatorFollowing.member, member)
|
||||
.innerJoin(pushToken).on(creatorFollowing.member.id.eq(pushToken.member.id))
|
||||
.where(where.and(pushToken.deviceType.eq("ios")))
|
||||
.fetch()
|
||||
.toSet()
|
||||
.chunked(500)
|
||||
|
||||
return mapOf("aos" to aosPushTokens, "ios" to iosPushTokens)
|
||||
}
|
||||
|
||||
override fun getPushTokenFromReservationList(roomId: Long): List<PushTokenInfo> {
|
||||
override fun getPushTokenFromReservationList(roomId: Long): Map<String, List<List<String>>> {
|
||||
val where = liveRoom.id.eq(roomId)
|
||||
.and(liveReservation.isActive.isTrue)
|
||||
|
||||
return queryFactory
|
||||
.select(
|
||||
QPushTokenInfo(
|
||||
pushToken.token,
|
||||
pushToken.deviceType,
|
||||
pushToken.languageCode.coalesce("ko")
|
||||
)
|
||||
)
|
||||
val aosPushTokens = queryFactory
|
||||
.select(pushToken.token)
|
||||
.from(liveReservation)
|
||||
.innerJoin(liveReservation.room, liveRoom)
|
||||
.innerJoin(liveReservation.member, member)
|
||||
.innerJoin(pushToken).on(member.id.eq(pushToken.member.id))
|
||||
.where(where)
|
||||
.where(where.and(pushToken.deviceType.eq("aos")))
|
||||
.fetch()
|
||||
.toSet()
|
||||
.chunked(500)
|
||||
|
||||
val iosPushTokens = queryFactory
|
||||
.select(pushToken.token)
|
||||
.from(liveReservation)
|
||||
.innerJoin(liveReservation.room, liveRoom)
|
||||
.innerJoin(liveReservation.member, member)
|
||||
.innerJoin(pushToken).on(member.id.eq(pushToken.member.id))
|
||||
.where(where.and(pushToken.deviceType.eq("ios")))
|
||||
.fetch()
|
||||
.toSet()
|
||||
.chunked(500)
|
||||
|
||||
return mapOf("aos" to aosPushTokens, "ios" to iosPushTokens)
|
||||
}
|
||||
|
||||
override fun getAuditionNoticeRecipientPushTokens(isAuth: Boolean): List<PushTokenInfo> {
|
||||
override fun getAuditionNoticeRecipientPushTokens(isAuth: Boolean): Map<String, List<List<String>>> {
|
||||
var where = memberNotification.audition.isTrue
|
||||
|
||||
if (isAuth) {
|
||||
where = where.and(auth.isNotNull)
|
||||
}
|
||||
|
||||
return queryFactory
|
||||
.select(
|
||||
QPushTokenInfo(
|
||||
pushToken.token,
|
||||
pushToken.deviceType,
|
||||
pushToken.languageCode.coalesce("ko")
|
||||
)
|
||||
)
|
||||
val aosPushTokens = queryFactory
|
||||
.select(pushToken.token)
|
||||
.from(member)
|
||||
.innerJoin(pushToken).on(member.id.eq(pushToken.member.id))
|
||||
.leftJoin(member.auth, auth)
|
||||
.leftJoin(memberNotification).on(memberNotification.member.id.eq(member.id))
|
||||
.where(where)
|
||||
.where(where.and(pushToken.deviceType.eq("aos")))
|
||||
.fetch()
|
||||
.toSet()
|
||||
.chunked(500)
|
||||
|
||||
val iosPushTokens = queryFactory
|
||||
.select(pushToken.token)
|
||||
.from(member)
|
||||
.innerJoin(pushToken).on(member.id.eq(pushToken.member.id))
|
||||
.leftJoin(member.auth, auth)
|
||||
.leftJoin(memberNotification).on(memberNotification.member.id.eq(member.id))
|
||||
.where(where.and(pushToken.deviceType.eq("ios")))
|
||||
.fetch()
|
||||
.toSet()
|
||||
.chunked(500)
|
||||
|
||||
return mapOf("aos" to aosPushTokens, "ios" to iosPushTokens)
|
||||
}
|
||||
|
||||
override fun getMemberProfile(memberId: Long, myMemberId: Long): GetMemberProfileResponse {
|
||||
|
||||
@@ -7,7 +7,6 @@ import kr.co.vividnext.sodalive.can.charge.ChargeRepository
|
||||
import kr.co.vividnext.sodalive.can.payment.CanPaymentService
|
||||
import kr.co.vividnext.sodalive.can.use.CanUsage
|
||||
import kr.co.vividnext.sodalive.common.ApiResponse
|
||||
import kr.co.vividnext.sodalive.common.CountryContext
|
||||
import kr.co.vividnext.sodalive.common.SodaException
|
||||
import kr.co.vividnext.sodalive.content.order.OrderService
|
||||
import kr.co.vividnext.sodalive.email.SendEmailService
|
||||
@@ -37,10 +36,8 @@ import kr.co.vividnext.sodalive.member.signUp.SignUpRequestV2
|
||||
import kr.co.vividnext.sodalive.member.signUp.SignUpResponse
|
||||
import kr.co.vividnext.sodalive.member.signUp.SignUpValidator
|
||||
import kr.co.vividnext.sodalive.member.social.MemberResolveResult
|
||||
import kr.co.vividnext.sodalive.member.social.apple.AppleUserInfo
|
||||
import kr.co.vividnext.sodalive.member.social.google.GoogleUserInfo
|
||||
import kr.co.vividnext.sodalive.member.social.kakao.KakaoUserInfo
|
||||
import kr.co.vividnext.sodalive.member.social.line.LineUserInfo
|
||||
import kr.co.vividnext.sodalive.member.stipulation.Stipulation
|
||||
import kr.co.vividnext.sodalive.member.stipulation.StipulationAgree
|
||||
import kr.co.vividnext.sodalive.member.stipulation.StipulationAgreeRepository
|
||||
@@ -102,7 +99,6 @@ class MemberService(
|
||||
|
||||
private val messageSource: SodaMessageSource,
|
||||
private val langContext: LangContext,
|
||||
private val countryContext: CountryContext,
|
||||
|
||||
private val objectMapper: ObjectMapper,
|
||||
|
||||
@@ -130,15 +126,14 @@ class MemberService(
|
||||
duplicateCheckEmail(request.email)
|
||||
validatePassword(request.password)
|
||||
|
||||
val nickname = nicknameGenerateService.generateUniqueNickname(langContext.lang)
|
||||
val nickname = nicknameGenerateService.generateUniqueNickname()
|
||||
val member = Member(
|
||||
email = request.email,
|
||||
password = passwordEncoder.encode(request.password),
|
||||
nickname = nickname,
|
||||
profileImage = "profile/default-profile.png",
|
||||
gender = Gender.NONE,
|
||||
container = request.container,
|
||||
countryCode = countryContext.countryCode
|
||||
container = request.container
|
||||
)
|
||||
|
||||
if (!request.marketingPid.isNullOrBlank()) {
|
||||
@@ -348,7 +343,7 @@ class MemberService(
|
||||
userId = member.id!!,
|
||||
token = jwt,
|
||||
nickname = member.nickname,
|
||||
email = member.email ?: "",
|
||||
email = member.email,
|
||||
profileImage = if (member.profileImage != null) {
|
||||
"$cloudFrontHost/${member.profileImage}"
|
||||
} else {
|
||||
@@ -395,8 +390,7 @@ class MemberService(
|
||||
password = passwordEncoder.encode(request.password),
|
||||
nickname = request.nickname,
|
||||
gender = request.gender,
|
||||
container = request.container,
|
||||
countryCode = countryContext.countryCode
|
||||
container = request.container
|
||||
)
|
||||
|
||||
if (!request.marketingPid.isNullOrBlank()) {
|
||||
@@ -456,16 +450,8 @@ class MemberService(
|
||||
}
|
||||
|
||||
override fun loadUserByUsername(username: String): UserDetails {
|
||||
val member = if (username.startsWith("member:")) {
|
||||
val id = username.substringAfter("member:").toLongOrNull()
|
||||
if (id != null) {
|
||||
repository.findByIdOrNull(id)
|
||||
} else {
|
||||
null
|
||||
}
|
||||
} else {
|
||||
repository.findByEmail(email = username)
|
||||
} ?: throw UsernameNotFoundException(username)
|
||||
val member = repository.findByEmail(email = username)
|
||||
?: throw UsernameNotFoundException(username)
|
||||
|
||||
return MemberAdapter(member)
|
||||
}
|
||||
@@ -602,7 +588,7 @@ class MemberService(
|
||||
|
||||
@Transactional
|
||||
fun signOut(signOutRequest: SignOutRequest, user: User) {
|
||||
val member = findMemberByUsername(user.username)
|
||||
val member = repository.findByEmail(user.username)
|
||||
?: throw SodaException(messageKey = "common.error.bad_credentials")
|
||||
if (
|
||||
member.provider == MemberProvider.EMAIL &&
|
||||
@@ -630,7 +616,11 @@ class MemberService(
|
||||
|
||||
@Transactional
|
||||
fun updateNickname(profileUpdateRequest: ProfileUpdateRequest, user: User) {
|
||||
val member = findMemberByUsername(user.username)
|
||||
if (profileUpdateRequest.email != user.username) {
|
||||
throw SodaException(messageKey = "common.error.bad_credentials")
|
||||
}
|
||||
|
||||
val member = repository.findByEmail(user.username)
|
||||
?: throw SodaException(messageKey = "common.error.bad_credentials")
|
||||
|
||||
if (profileUpdateRequest.nickname != null) {
|
||||
@@ -658,7 +648,11 @@ class MemberService(
|
||||
|
||||
@Transactional
|
||||
fun profileUpdate(profileUpdateRequest: ProfileUpdateRequest, user: User): ProfileResponse {
|
||||
val member = findMemberByUsername(user.username)
|
||||
if (profileUpdateRequest.email != user.username) {
|
||||
throw SodaException(messageKey = "common.error.bad_credentials")
|
||||
}
|
||||
|
||||
val member = repository.findByEmail(user.username)
|
||||
?: throw SodaException(messageKey = "common.error.bad_credentials")
|
||||
|
||||
if (profileUpdateRequest.modifyPassword != null) {
|
||||
@@ -726,16 +720,12 @@ class MemberService(
|
||||
member.isVisibleDonationRank = profileUpdateRequest.isVisibleDonationRank
|
||||
}
|
||||
|
||||
if (profileUpdateRequest.donationRankingPeriod != null) {
|
||||
member.donationRankingPeriod = profileUpdateRequest.donationRankingPeriod
|
||||
}
|
||||
|
||||
return ProfileResponse(member, cloudFrontHost, profileUpdateRequest.container)
|
||||
}
|
||||
|
||||
@Transactional
|
||||
fun profileImageUpdate(multipartFile: MultipartFile, user: User): String {
|
||||
val member = findMemberByUsername(user.username)
|
||||
val member = repository.findByEmail(user.username)
|
||||
?: throw SodaException(messageKey = "common.error.bad_credentials")
|
||||
|
||||
val metadata = ObjectMetadata()
|
||||
@@ -797,7 +787,6 @@ class MemberService(
|
||||
MemberProvider.KAKAO -> "member.provider.kakao"
|
||||
MemberProvider.GOOGLE -> "member.provider.google"
|
||||
MemberProvider.APPLE -> "member.provider.apple"
|
||||
MemberProvider.LINE -> "member.provider.line"
|
||||
}
|
||||
return messageSource.getMessage(key, langContext.lang) ?: provider.name
|
||||
}
|
||||
@@ -850,7 +839,7 @@ class MemberService(
|
||||
val email = googleUserInfo.email
|
||||
checkEmail(email)
|
||||
|
||||
val nickname = nicknameGenerateService.generateUniqueNickname(langContext.lang)
|
||||
val nickname = nicknameGenerateService.generateUniqueNickname()
|
||||
val member = Member(
|
||||
googleId = googleUserInfo.sub,
|
||||
email = email,
|
||||
@@ -859,8 +848,7 @@ class MemberService(
|
||||
profileImage = "profile/default-profile.png",
|
||||
gender = Gender.NONE,
|
||||
provider = MemberProvider.GOOGLE,
|
||||
container = container,
|
||||
countryCode = countryContext.countryCode
|
||||
container = container
|
||||
)
|
||||
|
||||
if (!marketingPid.isNullOrBlank()) {
|
||||
@@ -907,7 +895,7 @@ class MemberService(
|
||||
val email = kakaoUserInfo.email
|
||||
checkEmail(email)
|
||||
|
||||
val nickname = nicknameGenerateService.generateUniqueNickname(langContext.lang)
|
||||
val nickname = nicknameGenerateService.generateUniqueNickname()
|
||||
val member = Member(
|
||||
kakaoId = kakaoUserInfo.id,
|
||||
email = email,
|
||||
@@ -916,8 +904,7 @@ class MemberService(
|
||||
profileImage = "profile/default-profile.png",
|
||||
gender = Gender.NONE,
|
||||
provider = MemberProvider.KAKAO,
|
||||
container = container,
|
||||
countryCode = countryContext.countryCode
|
||||
container = container
|
||||
)
|
||||
|
||||
if (!marketingPid.isNullOrBlank()) {
|
||||
@@ -939,138 +926,7 @@ class MemberService(
|
||||
return MemberResolveResult(member = member, isNew = true)
|
||||
}
|
||||
|
||||
@Transactional
|
||||
fun findOrRegister(
|
||||
appleUserInfo: AppleUserInfo,
|
||||
container: String,
|
||||
marketingPid: String?,
|
||||
pushToken: String?
|
||||
): MemberResolveResult {
|
||||
val findMember = repository.findByAppleId(appleUserInfo.sub)
|
||||
if (findMember != null) {
|
||||
if (findMember.isActive) {
|
||||
return MemberResolveResult(member = findMember, isNew = false)
|
||||
} else {
|
||||
throw SodaException(messageKey = "member.validation.inactive_account")
|
||||
}
|
||||
}
|
||||
|
||||
val stipulationTermsOfService = stipulationRepository.findByIdOrNull(StipulationIds.TERMS_OF_SERVICE_ID)
|
||||
?: throw SodaException(messageKey = "member.validation.invalid_request_retry")
|
||||
|
||||
val stipulationPrivacyPolicy = stipulationRepository.findByIdOrNull(StipulationIds.PRIVACY_POLICY_ID)
|
||||
?: throw SodaException(messageKey = "member.validation.invalid_request_retry")
|
||||
|
||||
val email = appleUserInfo.email
|
||||
checkEmail(email)
|
||||
|
||||
val nickname = nicknameGenerateService.generateUniqueNickname(langContext.lang)
|
||||
val member = Member(
|
||||
appleId = appleUserInfo.sub,
|
||||
email = email,
|
||||
password = "",
|
||||
nickname = nickname,
|
||||
profileImage = "profile/default-profile.png",
|
||||
gender = Gender.NONE,
|
||||
provider = MemberProvider.APPLE,
|
||||
container = container,
|
||||
countryCode = countryContext.countryCode
|
||||
)
|
||||
|
||||
if (!marketingPid.isNullOrBlank()) {
|
||||
member.activePid = marketingPid
|
||||
member.partnerExpirationDatetime = LocalDateTime.now().plusYears(1)
|
||||
}
|
||||
|
||||
repository.save(member)
|
||||
agreeTermsOfServiceAndPrivacyPolicy(member, stipulationTermsOfService, stipulationPrivacyPolicy)
|
||||
|
||||
if (pushToken != null) {
|
||||
pushTokenService.registerToken(
|
||||
memberId = member.id!!,
|
||||
token = pushToken,
|
||||
deviceType = container
|
||||
)
|
||||
}
|
||||
|
||||
return MemberResolveResult(member = member, isNew = true)
|
||||
}
|
||||
|
||||
@Transactional
|
||||
fun findOrRegister(
|
||||
lineUserInfo: LineUserInfo,
|
||||
container: String,
|
||||
marketingPid: String?,
|
||||
pushToken: String?
|
||||
): MemberResolveResult {
|
||||
val findMember = repository.findByLineId(lineUserInfo.sub)
|
||||
if (findMember != null) {
|
||||
if (findMember.isActive) {
|
||||
return MemberResolveResult(member = findMember, isNew = false)
|
||||
} else {
|
||||
throw SodaException(messageKey = "member.validation.inactive_account")
|
||||
}
|
||||
}
|
||||
|
||||
val stipulationTermsOfService = stipulationRepository.findByIdOrNull(StipulationIds.TERMS_OF_SERVICE_ID)
|
||||
?: throw SodaException(messageKey = "member.validation.invalid_request_retry")
|
||||
|
||||
val stipulationPrivacyPolicy = stipulationRepository.findByIdOrNull(StipulationIds.PRIVACY_POLICY_ID)
|
||||
?: throw SodaException(messageKey = "member.validation.invalid_request_retry")
|
||||
|
||||
val email = lineUserInfo.email
|
||||
checkEmail(email)
|
||||
|
||||
val nickname = nicknameGenerateService.generateUniqueNickname(langContext.lang)
|
||||
val member = Member(
|
||||
lineId = lineUserInfo.sub,
|
||||
email = email,
|
||||
password = "",
|
||||
nickname = nickname,
|
||||
profileImage = "profile/default-profile.png",
|
||||
gender = Gender.NONE,
|
||||
provider = MemberProvider.LINE,
|
||||
container = container,
|
||||
countryCode = countryContext.countryCode
|
||||
)
|
||||
|
||||
if (!marketingPid.isNullOrBlank()) {
|
||||
member.activePid = marketingPid
|
||||
member.partnerExpirationDatetime = LocalDateTime.now().plusYears(1)
|
||||
}
|
||||
|
||||
repository.save(member)
|
||||
agreeTermsOfServiceAndPrivacyPolicy(member, stipulationTermsOfService, stipulationPrivacyPolicy)
|
||||
|
||||
if (pushToken != null) {
|
||||
pushTokenService.registerToken(
|
||||
memberId = member.id!!,
|
||||
token = pushToken,
|
||||
deviceType = container
|
||||
)
|
||||
}
|
||||
|
||||
return MemberResolveResult(member = member, isNew = true)
|
||||
}
|
||||
|
||||
private fun findMemberByUsername(username: String): Member? {
|
||||
return if (username.startsWith("member:")) {
|
||||
val id = username.substringAfter("member:").toLongOrNull()
|
||||
if (id != null) {
|
||||
repository.findByIdOrNull(id)
|
||||
} else {
|
||||
null
|
||||
}
|
||||
} else {
|
||||
repository.findByEmail(email = username)
|
||||
}
|
||||
}
|
||||
|
||||
private fun checkEmail(email: String?) {
|
||||
if (email.isNullOrBlank()) {
|
||||
return
|
||||
}
|
||||
|
||||
private fun checkEmail(email: String) {
|
||||
val member = repository.findByEmail(email)
|
||||
|
||||
if (member != null) {
|
||||
|
||||
@@ -17,7 +17,7 @@ data class ProfileResponse(
|
||||
) {
|
||||
constructor(member: Member, cloudFrontHost: String, container: String) : this(
|
||||
userId = member.id!!,
|
||||
email = member.email ?: "",
|
||||
email = member.email,
|
||||
nickname = member.nickname,
|
||||
gender = member.gender,
|
||||
profileUrl = if (member.profileImage != null) {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
package kr.co.vividnext.sodalive.member
|
||||
|
||||
data class ProfileUpdateRequest(
|
||||
val email: String? = null,
|
||||
val email: String,
|
||||
val password: String? = null,
|
||||
val modifyPassword: String? = null,
|
||||
val nickname: String? = null,
|
||||
@@ -14,6 +14,5 @@ data class ProfileUpdateRequest(
|
||||
val websiteUrl: String? = null,
|
||||
val blogUrl: String? = null,
|
||||
val isVisibleDonationRank: Boolean? = null,
|
||||
val donationRankingPeriod: DonationRankingPeriod? = null,
|
||||
val container: String
|
||||
)
|
||||
|
||||
@@ -10,7 +10,5 @@ data class LoginRequest(
|
||||
data class SocialLoginRequest(
|
||||
val container: String,
|
||||
val pushToken: String? = null,
|
||||
val marketingPid: String? = null,
|
||||
val identityToken: String? = null,
|
||||
val nonce: String? = null
|
||||
val marketingPid: String? = null
|
||||
)
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package kr.co.vividnext.sodalive.member.nickname
|
||||
|
||||
import kr.co.vividnext.sodalive.common.SodaException
|
||||
import kr.co.vividnext.sodalive.i18n.Lang
|
||||
import kr.co.vividnext.sodalive.member.MemberRepository
|
||||
import org.springframework.stereotype.Service
|
||||
import kotlin.random.Random
|
||||
@@ -9,121 +8,59 @@ import kotlin.random.Random
|
||||
@Service
|
||||
class NicknameGenerateService(private val repository: MemberRepository) {
|
||||
private val adjectives = listOf(
|
||||
"활기찬", "명랑한", "씩씩한", "용감한", "지혜론", "슬기론", "넉넉한", "든든한", "알찬듯",
|
||||
"빠른듯", "느긋한", "당당한", "솔직한", "진실한", "겸손한", "성실한", "꼼꼼한", "야무진",
|
||||
"재빠른", "영리한", "총명한", "현명한", "착실한", "올곧은", "바른듯", "곧은듯", "힘찬듯", "굳센듯",
|
||||
"의젓한", "점잖은", "듬직한", "너그런", "관대한", "인자한", "자애론", "헌신적", "열정적", "적극적",
|
||||
"능숙한", "탁월한", "뛰어난", "출중한", "비범한", "특별한", "독특한", "개성적", "창의적", "혁신적",
|
||||
"진취적", "도전적", "패기찬", "호쾌한", "시원찬", "통쾌한", "유능한", "민첩한", "기민한", "재치론",
|
||||
"센스찬", "감각적", "세련된", "품격찬", "격조찬", "기품찬", "위풍찬", "늠름한", "씩씩찬", "호탕한",
|
||||
"대범한", "거뜬한", "가뿐한", "홀가분", "산뜻찬", "깔끔찬", "정갈한", "단정한", "반짝찬", "영롱한",
|
||||
"찬란찬", "눈부찬", "환한듯", "밝은찬", "빛깔찬", "색다른", "새로운", "신선찬", "풋풋한", "싱그런",
|
||||
"생기찬", "발랄한", "경쾌한", "리듬찬", "율동적", "역동적", "활발한", "생동찬", "약동찬", "힘있는",
|
||||
"건장한", "튼튼한", "건강한", "탄탄한", "단련된", "숙련된", "노련한", "원숙한", "성숙한", "완숙한",
|
||||
"정확한", "치밀한", "정밀한", "철저한", "완벽찬", "흠없는", "나무랄", "빈틈없", "알뜰한", "꾸준한",
|
||||
"한결찬", "변함없", "굳건한", "확고한", "견고한", "탄탄찬", "안정적", "평화론", "온유한", "자비론",
|
||||
"배려찬", "사려찬", "깊숙한", "심오한", "오묘한", "현묘한", "신묘한", "경이론", "놀라운", "대단한",
|
||||
"훌륭한", "멋스런", "근사한", "기특한"
|
||||
"따뜻한", "은은한", "고요한", "푸른", "맑은", "강한", "평온한", "깊은", "고독한",
|
||||
"거친", "빛바랜", "차가운", "꿈꾸는", "숨겨진", "고귀한", "깨어난", "끝없는", "청명한",
|
||||
"어두운", "희미한", "선명한", "눈부신", "불타는", "차분한", "아련한", "선선한", "상쾌한", "온화한",
|
||||
"포근한", "황금빛", "청량한", "시원한", "서늘한", "우아한", "단단한", "투명한", "가벼운", "조용한",
|
||||
"화려한", "찬란한", "순수한", "흐릿한", "고결한", "달콤한", "무한한", "아득한", "화사한", "평안한",
|
||||
"웅장한", "황홀한", "빛나는", "쓸쓸한", "청순한", "흐르는", "미묘한", "그윽한", "몽롱한", "청아한",
|
||||
"섬세한", "촉촉한", "강렬한", "싱싱한", "부드런", "아늑한", "매운듯", "고운듯", "느린듯", "밝은듯",
|
||||
"짧은듯", "달큰한", "깊은듯", "기쁜듯", "쌀쌀한", "무거운", "연한듯", "편안한", "깨끗한", "말끔한",
|
||||
"뽀얀듯", "푸르른", "붉은듯", "노오란", "무딘듯", "살짝한", "상큼한", "시큰한", "고소한", "나긋한",
|
||||
"화끈한", "중후한", "정겨운", "날렵한", "기묘한", "참신한", "담백한", "퉁명한", "꾸밈없", "소박한",
|
||||
"뾰족한", "무심한", "도도한", "따끔한", "무난한", "단호한", "냉정한", "따스한", "유연한", "묵직한",
|
||||
"나른한", "몽환적", "정돈된", "쾌활한", "날카론", "묘한듯", "예쁜듯", "뽀얗게", "다정한", "푸근한",
|
||||
"애틋한", "낭만적", "건조한", "훈훈한", "섹시한", "정적인", "유쾌한", "멍한듯", "혼란한", "상냥한",
|
||||
"뚜렷한", "신비한", "허전한", "그리운", "들뜬듯", "절실한", "반듯한", "반가운", "새하얀", "흐린듯",
|
||||
"엄숙한", "깊잖은", "산뜻한", "낯선듯"
|
||||
)
|
||||
|
||||
private val nouns = listOf(
|
||||
"다람쥐", "청설모", "두루미", "기러기", "올빼미", "부엉이", "딱따구", "꾀꼬리", "직박구", "동박새",
|
||||
"참새", "종달새", "제비", "뻐꾸기", "앵무새", "공작새", "원앙", "두더지", "고슴도", "족제비",
|
||||
"오소리", "수리부", "해오라", "갈매기", "펭귄", "코알라", "알파카", "카멜레", "이구아", "플라밍",
|
||||
"돌고래", "해달", "라쿤", "미어캣", "친칠라", "햄스터", "기니피", "토끼", "강아지", "고양이",
|
||||
"망아지", "송아지", "병아리", "올챙이", "개구리", "도롱뇽", "거북이", "앵무", "카나리", "비둘기",
|
||||
"참매", "독수리", "콘도르", "벌새", "홍학", "타조", "키위새", "투칸", "앵콩이", "물까치",
|
||||
"루비", "사파이", "에메랄", "자수정", "진주", "산호", "호박", "비취", "오팔", "토파즈",
|
||||
"다이아", "크리스", "아쿠아", "코발트", "인디고", "라벤더", "마젠타", "터콰이", "세룰리", "버밀리",
|
||||
"카푸치", "에스프", "아메리", "마키아", "바닐라", "캐러멜", "시나몬", "민트", "자스민", "캐모마",
|
||||
"히비스", "라일락", "프리지", "튤립", "수선화", "동백", "매화", "목련", "벚꽃", "진달래",
|
||||
"철쭉", "개나리", "무궁화", "해바라", "코스모", "달리아", "작약", "모란", "연꽃", "수련",
|
||||
"클로버", "민들레", "제비꽃", "은방울", "안개꽃", "라넌큘", "아네모", "델피니", "글라디", "프로테",
|
||||
"유칼립", "로즈마", "바질", "타임", "오레가", "세이지", "딜", "파슬리", "고수", "루꼴라",
|
||||
"아보카", "블루베", "라즈베", "크랜베", "아사이", "망고", "파파야", "리치", "패션후", "구아바",
|
||||
"석류", "무화과", "살구", "자두", "체리", "복숭아", "포도", "감귤", "유자", "한라봉",
|
||||
"천혜향", "레드향", "금귤", "모과", "비파", "대추", "밤", "호두", "잣", "은행"
|
||||
"소리", "울림", "공명", "음색", "감성", "리듬", "바람", "늑대", "태양", "대지",
|
||||
"강", "하늘", "불꽃", "별빛", "나무", "산", "달빛", "폭풍", "눈", "밤",
|
||||
"노을", "물결", "노래", "파도", "구름", "사슴", "신비", "영혼", "선율", "평원",
|
||||
"빛", "고래", "모래", "사자", "표범", "여우", "곰", "수달", "판다", "들소",
|
||||
"까치", "매", "솔개", "물총새", "철새", "황새", "은어", "붕어", "산양", "담비",
|
||||
"설표", "물개", "자라", "나비", "노루", "해마", "백조", "청어", "호수", "샘물",
|
||||
"쿼카", "상어", "무드", "나노", "루프", "네온", "모아", "아토", "플로", "루미",
|
||||
"도트", "비트", "토브", "온기", "클리", "위드", "제로", "베이", "미오", "시그",
|
||||
"쿠나", "오로", "폴라", "바움", "포잇", "누아", "오브", "파인", "조이", "아뜰",
|
||||
"티노", "소마", "하루", "밀크", "아린", "토로", "벨로", "위시", "뮤즈", "노블",
|
||||
"카노", "미카", "하라", "엘로", "피오", "라임", "노이", "루다", "이브", "마리",
|
||||
"블루", "시온", "레아", "도르", "하노", "네리", "키노", "쿠키", "라노", "수이",
|
||||
"우노", "파루", "크리", "포유", "코코", "아라", "토리", "누리", "보노", "페어",
|
||||
"리아", "모리", "세리", "리브", "헤이", "모카", "아이", "르네", "이로", "미노",
|
||||
"다라", "노바", "디노", "오미", "카라", "니아", "루아", "네오", "하이", "레인",
|
||||
"피카", "유카", "제니", "이든", "라비", "아벨", "솔라", "쿠로", "시라", "리코"
|
||||
)
|
||||
|
||||
private val jaAdjectives = listOf(
|
||||
"元気な", "明るい", "優しい", "強い", "賢い", "穏やかな", "爽やかな", "楽しい",
|
||||
"勇敢な", "素敵な", "可愛い", "美しい", "清らかな", "温かい", "輝く", "華やかな",
|
||||
"凛とした", "朗らかな", "逞しい", "麗しい", "雅な", "粋な", "健やかな", "晴れやかな",
|
||||
"鮮やかな", "煌めく", "微笑む", "誠実な", "丁寧な", "真っ直ぐな", "気高い", "聡明な",
|
||||
"快活な", "軽やかな", "しなやかな", "伸びやかな", "瑞々しい", "初々しい", "艶やかな", "柔らかな",
|
||||
"澄んだ", "静かな", "豊かな", "深い", "広い", "高い", "清い", "涼しい",
|
||||
"眩しい", "暖かな", "和やかな", "安らかな", "のどかな", "ほがらかな", "すこやかな", "たくましい",
|
||||
"ひたむきな", "まめな", "きらきらな", "ふわふわな", "にこにこな", "わくわくな", "すくすくな", "のびのびな",
|
||||
"きりっとした", "はきはきな", "てきぱきな", "しっかりな", "どっしりな", "ゆったりな", "さっぱりな", "すっきりな",
|
||||
"ぴかぴかな", "つやつやな", "さらさらな", "もちもちな", "ぷるぷるな", "ころころな", "ぽかぽかな", "そよそよな",
|
||||
"堅実な", "勤勉な", "忠実な", "素直な", "謙虚な", "大胆な", "情熱的な", "積極的な",
|
||||
"独創的な", "繊細な", "壮大な", "格調高い", "品のある", "風格ある", "趣のある", "奥深い",
|
||||
"颯爽とした", "堂々とした", "悠々とした", "泰然とした", "毅然とした", "端正な", "清楚な", "典雅な",
|
||||
"俊敏な", "機敏な", "敏捷な", "軽快な", "活発な", "溌剌とした", "生き生きな", "伸び伸びな",
|
||||
"揺るぎない", "確かな", "頼もしい", "心強い", "力強い", "逞しき", "雄々しい", "凜々しい",
|
||||
"慈しみの", "思いやりの", "気配りの", "心優しい", "情け深い", "懐の深い", "器の大きい", "包容力の"
|
||||
)
|
||||
|
||||
private val jaNouns = listOf(
|
||||
"うさぎ", "ねこ", "いぬ", "たぬき", "きつね", "しか", "りす", "ふくろう",
|
||||
"つばめ", "すずめ", "ひばり", "うぐいす", "めじろ", "つる", "はと", "かもめ",
|
||||
"いるか", "くじら", "らっこ", "ペンギン", "コアラ", "パンダ", "アルパカ", "ハムスター",
|
||||
"かめ", "かえる", "ほたる", "ちょう", "とんぼ", "てんとう", "こねこ", "こいぬ",
|
||||
"ひよこ", "こじか", "こぐま", "こうさぎ", "こりす", "こだぬき", "こぎつね", "こばと",
|
||||
"さくら", "うめ", "もみじ", "つばき", "すみれ", "たんぽぽ", "ひまわり", "あじさい",
|
||||
"コスモス", "ラベンダー", "チューリップ", "カーネーション", "バラ", "ユリ", "ダリア", "マーガレット",
|
||||
"なでしこ", "あやめ", "ききょう", "はぎ", "ふじ", "ぼたん", "しゃくやく", "れんげ",
|
||||
"ルビー", "サファイア", "エメラルド", "アメジスト", "パール", "オパール", "トパーズ", "ガーネット",
|
||||
"ひかり", "そら", "うみ", "かぜ", "つき", "ほし", "にじ", "ゆめ",
|
||||
"あかね", "みずき", "はるか", "あおい", "ひなた", "こはる", "いろは", "かなで",
|
||||
"しずく", "つゆ", "あられ", "みぞれ", "こはく", "あかり", "ともしび", "かがやき",
|
||||
"やまと", "みやび", "まこと", "ちはや", "あさひ", "ゆうひ", "あけぼの", "たそがれ",
|
||||
"わかば", "あおば", "もえぎ", "ときわ", "さつき", "やよい", "きさらぎ", "むつき",
|
||||
"抹茶", "桜餅", "団子", "大福", "最中", "羊羹", "煎餅", "饅頭",
|
||||
"柚子", "梅干し", "味噌", "醤油", "わさび", "生姜", "山椒", "昆布",
|
||||
"風鈴", "提灯", "扇子", "千鶴", "折鶴", "手毬", "万華鏡", "花火",
|
||||
"雪うさぎ", "だるま", "こけし", "招き猫", "風車", "独楽", "竹とんぼ", "紙風船",
|
||||
"朝露", "夕凪", "木漏れ日", "花吹雪", "月明かり", "星空", "天の川", "春風",
|
||||
"小春日和", "花曇り", "薄紅", "若草", "深緑", "紺碧", "茜色", "藤色"
|
||||
)
|
||||
|
||||
private fun getAdjectives(lang: Lang): List<String> = when (lang) {
|
||||
Lang.JA -> jaAdjectives
|
||||
else -> adjectives
|
||||
}
|
||||
|
||||
private fun getNouns(lang: Lang): List<String> = when (lang) {
|
||||
Lang.JA -> jaNouns
|
||||
else -> nouns
|
||||
}
|
||||
|
||||
private fun getParticle(lang: Lang): String = when (lang) {
|
||||
Lang.JA -> "の"
|
||||
else -> "의"
|
||||
}
|
||||
|
||||
private fun generateRandomNickname(lang: Lang): String {
|
||||
val adj = getAdjectives(lang)
|
||||
val noun = getNouns(lang)
|
||||
val particle = getParticle(lang)
|
||||
private fun generateRandomNickname(): String {
|
||||
val formatType = Random.nextInt(3)
|
||||
return when (formatType) {
|
||||
0 -> "${adj.random()}${noun.random()}"
|
||||
1 -> "${noun.random()}${particle}${noun.random()}"
|
||||
else -> "${adj.random()}${noun.random()}${particle}${noun.random()}"
|
||||
0 -> "${adjectives.random()}${nouns.random()}"
|
||||
1 -> "${nouns.random()}의${nouns.random()}"
|
||||
else -> "${adjectives.random()}${nouns.random()}의${nouns.random()}"
|
||||
}
|
||||
}
|
||||
|
||||
private fun generateNonConflictingNickname(usedNicknames: Set<String>, lang: Lang): String {
|
||||
val usedNicknameSet = HashSet(usedNicknames)
|
||||
private fun generateNonConflictingNickname(usedNicknames: Set<String>): String {
|
||||
val usedNicknameSet = HashSet(usedNicknames) // 해시셋으로 변환 (O(1) 조회 가능)
|
||||
val availableNumbers = (1000..9999).shuffled()
|
||||
val adj = getAdjectives(lang)
|
||||
val noun = getNouns(lang)
|
||||
|
||||
for (num in availableNumbers) {
|
||||
for (a in adj.shuffled()) {
|
||||
for (n in noun.shuffled()) {
|
||||
val candidate = "$a$n$num"
|
||||
for (num in availableNumbers) { // 숫자를 먼저 결정 (무작위)
|
||||
for (adj in adjectives.shuffled()) { // 형용사 순서 랜덤화
|
||||
for (noun in nouns.shuffled()) { // 명사 순서 랜덤화
|
||||
val candidate = "$adj$noun$num"
|
||||
if (!usedNicknameSet.contains(candidate)) {
|
||||
return candidate
|
||||
}
|
||||
@@ -133,13 +70,13 @@ class NicknameGenerateService(private val repository: MemberRepository) {
|
||||
throw SodaException(messageKey = "member.signup.failed_retry")
|
||||
}
|
||||
|
||||
fun generateUniqueNickname(lang: Lang = Lang.KO): String {
|
||||
fun generateUniqueNickname(): String {
|
||||
repeat(5) {
|
||||
val candidates = (1..10).map { generateRandomNickname(lang) }
|
||||
val candidates = (1..10).map { generateRandomNickname() }
|
||||
val available = candidates.firstOrNull { !repository.existsByNickname(it) }
|
||||
if (available != null) return available
|
||||
}
|
||||
|
||||
return generateNonConflictingNickname(repository.findNicknamesWithPrefix("").toSet(), lang)
|
||||
return generateNonConflictingNickname(repository.findNicknamesWithPrefix("").toSet())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,14 +0,0 @@
|
||||
package kr.co.vividnext.sodalive.member.social
|
||||
|
||||
import kr.co.vividnext.sodalive.member.MemberProvider
|
||||
|
||||
interface SocialAuthService {
|
||||
fun getProvider(): MemberProvider
|
||||
fun authenticate(
|
||||
token: String,
|
||||
container: String,
|
||||
marketingPid: String?,
|
||||
pushToken: String?,
|
||||
nonce: String?
|
||||
): SocialLoginResponse
|
||||
}
|
||||
@@ -1,15 +0,0 @@
|
||||
package kr.co.vividnext.sodalive.member.social
|
||||
|
||||
import kr.co.vividnext.sodalive.member.MemberProvider
|
||||
import org.springframework.stereotype.Component
|
||||
|
||||
@Component
|
||||
class SocialAuthServiceResolver(
|
||||
val services: List<SocialAuthService>
|
||||
) {
|
||||
private val serviceMap: Map<MemberProvider, SocialAuthService> = services.associateBy { it.getProvider() }
|
||||
|
||||
fun resolve(provider: MemberProvider): SocialAuthService {
|
||||
return serviceMap[provider] ?: throw IllegalArgumentException("Unsupported social provider: $provider")
|
||||
}
|
||||
}
|
||||
@@ -1,68 +0,0 @@
|
||||
package kr.co.vividnext.sodalive.member.social.apple
|
||||
|
||||
import kr.co.vividnext.sodalive.common.SodaException
|
||||
import kr.co.vividnext.sodalive.jwt.TokenProvider
|
||||
import kr.co.vividnext.sodalive.member.MemberAdapter
|
||||
import kr.co.vividnext.sodalive.member.MemberProvider
|
||||
import kr.co.vividnext.sodalive.member.MemberService
|
||||
import kr.co.vividnext.sodalive.member.login.LoginResponse
|
||||
import kr.co.vividnext.sodalive.member.social.SocialAuthService
|
||||
import kr.co.vividnext.sodalive.member.social.SocialLoginResponse
|
||||
import org.springframework.beans.factory.annotation.Value
|
||||
import org.springframework.security.core.context.SecurityContextHolder
|
||||
import org.springframework.stereotype.Service
|
||||
|
||||
@Service
|
||||
class AppleAuthService(
|
||||
private val appleIdentityTokenVerifier: AppleIdentityTokenVerifier,
|
||||
private val memberService: MemberService,
|
||||
private val tokenProvider: TokenProvider,
|
||||
|
||||
@Value("\${cloud.aws.cloud-front.host}")
|
||||
private val cloudFrontHost: String
|
||||
) : SocialAuthService {
|
||||
override fun getProvider(): MemberProvider = MemberProvider.APPLE
|
||||
|
||||
override fun authenticate(
|
||||
token: String,
|
||||
container: String,
|
||||
marketingPid: String?,
|
||||
pushToken: String?,
|
||||
nonce: String?
|
||||
): SocialLoginResponse {
|
||||
val rawNonce = nonce?.takeIf { it.isNotBlank() }
|
||||
?: throw SodaException(messageKey = "member.social.apple_login_failed")
|
||||
|
||||
val appleUserInfo = appleIdentityTokenVerifier.verify(token, rawNonce)
|
||||
val memberResolveResult = memberService.findOrRegister(appleUserInfo, container, marketingPid, pushToken)
|
||||
val member = memberResolveResult.member
|
||||
val principal = MemberAdapter(member)
|
||||
val authToken = AppleAuthenticationToken(token, principal.authorities)
|
||||
authToken.setPrincipal(principal)
|
||||
SecurityContextHolder.getContext().authentication = authToken
|
||||
|
||||
val jwt = tokenProvider.createToken(
|
||||
authentication = authToken,
|
||||
memberId = member.id!!
|
||||
)
|
||||
|
||||
val loginResponse = LoginResponse(
|
||||
userId = member.id!!,
|
||||
token = jwt,
|
||||
nickname = member.nickname,
|
||||
email = member.email ?: "",
|
||||
profileImage = if (member.profileImage != null) {
|
||||
"$cloudFrontHost/${member.profileImage}"
|
||||
} else {
|
||||
"$cloudFrontHost/profile/default-profile.png"
|
||||
}
|
||||
)
|
||||
|
||||
return SocialLoginResponse(
|
||||
memberId = member.id!!,
|
||||
marketingPid = marketingPid,
|
||||
loginResponse = loginResponse,
|
||||
isNew = memberResolveResult.isNew
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -1,23 +0,0 @@
|
||||
package kr.co.vividnext.sodalive.member.social.apple
|
||||
|
||||
import org.springframework.security.authentication.AbstractAuthenticationToken
|
||||
import org.springframework.security.core.GrantedAuthority
|
||||
|
||||
class AppleAuthenticationToken(
|
||||
private val idToken: String,
|
||||
authorities: Collection<GrantedAuthority>? = null
|
||||
) : AbstractAuthenticationToken(authorities) {
|
||||
private var principal: Any? = null
|
||||
|
||||
init {
|
||||
isAuthenticated = authorities != null
|
||||
}
|
||||
|
||||
override fun getCredentials(): Any = idToken
|
||||
|
||||
override fun getPrincipal(): Any? = principal
|
||||
|
||||
fun setPrincipal(principal: Any) {
|
||||
this.principal = principal
|
||||
}
|
||||
}
|
||||
@@ -1,93 +0,0 @@
|
||||
package kr.co.vividnext.sodalive.member.social.apple
|
||||
|
||||
import com.nimbusds.jose.JWSAlgorithm
|
||||
import com.nimbusds.jose.jwk.source.JWKSource
|
||||
import com.nimbusds.jose.jwk.source.JWKSourceBuilder
|
||||
import com.nimbusds.jose.proc.JWSVerificationKeySelector
|
||||
import com.nimbusds.jose.proc.SecurityContext
|
||||
import com.nimbusds.jwt.JWTClaimsSet
|
||||
import com.nimbusds.jwt.proc.ConfigurableJWTProcessor
|
||||
import com.nimbusds.jwt.proc.DefaultJWTProcessor
|
||||
import kr.co.vividnext.sodalive.common.SodaException
|
||||
import org.springframework.beans.factory.annotation.Value
|
||||
import org.springframework.stereotype.Service
|
||||
import java.net.URL
|
||||
import java.nio.charset.StandardCharsets
|
||||
import java.security.MessageDigest
|
||||
import java.util.Base64
|
||||
import java.util.Date
|
||||
|
||||
@Service
|
||||
class AppleIdentityTokenVerifier(
|
||||
@Value("\${apple.bundle-id}")
|
||||
private val bundleId: String
|
||||
) {
|
||||
private val jwkUrl = URL("https://appleid.apple.com/auth/keys")
|
||||
private val jwkSource: JWKSource<SecurityContext> = JWKSourceBuilder.create<SecurityContext>(jwkUrl)
|
||||
.build()
|
||||
|
||||
private val jwtProcessor: ConfigurableJWTProcessor<SecurityContext> =
|
||||
DefaultJWTProcessor<SecurityContext>().apply {
|
||||
jwsKeySelector = JWSVerificationKeySelector(JWSAlgorithm.RS256, jwkSource)
|
||||
}
|
||||
|
||||
fun verify(identityToken: String, rawNonce: String): AppleUserInfo {
|
||||
if (bundleId.isBlank()) {
|
||||
throw SodaException(messageKey = "member.social.apple_login_failed")
|
||||
}
|
||||
|
||||
if (rawNonce.isBlank()) {
|
||||
throw SodaException(messageKey = "member.social.apple_login_failed")
|
||||
}
|
||||
|
||||
val claims = try {
|
||||
jwtProcessor.process(identityToken, null)
|
||||
} catch (_: Exception) {
|
||||
throw SodaException(messageKey = "member.social.apple_login_failed")
|
||||
}
|
||||
|
||||
validateClaims(claims, rawNonce)
|
||||
|
||||
return AppleUserInfo(
|
||||
sub = claims.subject ?: throw SodaException(messageKey = "member.social.apple_login_failed"),
|
||||
email = claims.getStringClaim("email")
|
||||
)
|
||||
}
|
||||
|
||||
private fun validateClaims(claims: JWTClaimsSet, rawNonce: String) {
|
||||
if (claims.issuer != ISSUER) {
|
||||
throw SodaException(messageKey = "member.social.apple_login_failed")
|
||||
}
|
||||
|
||||
if (!claims.audience.contains(bundleId)) {
|
||||
throw SodaException(messageKey = "member.social.apple_login_failed")
|
||||
}
|
||||
|
||||
val now = Date()
|
||||
val expirationTime = claims.expirationTime ?: throw SodaException(messageKey = "member.social.apple_login_failed")
|
||||
if (expirationTime.before(now)) {
|
||||
throw SodaException(messageKey = "member.social.apple_login_failed")
|
||||
}
|
||||
|
||||
val issuedAt = claims.issueTime ?: throw SodaException(messageKey = "member.social.apple_login_failed")
|
||||
if (issuedAt.after(now)) {
|
||||
throw SodaException(messageKey = "member.social.apple_login_failed")
|
||||
}
|
||||
|
||||
val nonce = claims.getStringClaim("nonce") ?: throw SodaException(messageKey = "member.social.apple_login_failed")
|
||||
val expectedNonce = hashNonce(rawNonce)
|
||||
if (nonce != expectedNonce) {
|
||||
throw SodaException(messageKey = "member.social.apple_login_failed")
|
||||
}
|
||||
}
|
||||
|
||||
private fun hashNonce(rawNonce: String): String {
|
||||
val digest = MessageDigest.getInstance("SHA-256")
|
||||
val hashed = digest.digest(rawNonce.toByteArray(StandardCharsets.UTF_8))
|
||||
return Base64.getUrlEncoder().withoutPadding().encodeToString(hashed)
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val ISSUER = "https://appleid.apple.com"
|
||||
}
|
||||
}
|
||||
@@ -1,6 +0,0 @@
|
||||
package kr.co.vividnext.sodalive.member.social.apple
|
||||
|
||||
data class AppleUserInfo(
|
||||
val sub: String,
|
||||
val email: String?
|
||||
)
|
||||
@@ -3,10 +3,8 @@ package kr.co.vividnext.sodalive.member.social.google
|
||||
import kr.co.vividnext.sodalive.common.SodaException
|
||||
import kr.co.vividnext.sodalive.jwt.TokenProvider
|
||||
import kr.co.vividnext.sodalive.member.MemberAdapter
|
||||
import kr.co.vividnext.sodalive.member.MemberProvider
|
||||
import kr.co.vividnext.sodalive.member.MemberService
|
||||
import kr.co.vividnext.sodalive.member.login.LoginResponse
|
||||
import kr.co.vividnext.sodalive.member.social.SocialAuthService
|
||||
import kr.co.vividnext.sodalive.member.social.SocialLoginResponse
|
||||
import org.springframework.beans.factory.annotation.Value
|
||||
import org.springframework.security.core.context.SecurityContextHolder
|
||||
@@ -20,22 +18,19 @@ class GoogleAuthService(
|
||||
|
||||
@Value("\${cloud.aws.cloud-front.host}")
|
||||
private val cloudFrontHost: String
|
||||
) : SocialAuthService {
|
||||
override fun getProvider(): MemberProvider = MemberProvider.GOOGLE
|
||||
|
||||
override fun authenticate(
|
||||
token: String,
|
||||
) {
|
||||
fun authenticate(
|
||||
idToken: String,
|
||||
container: String,
|
||||
marketingPid: String?,
|
||||
pushToken: String?,
|
||||
nonce: String?
|
||||
pushToken: String?
|
||||
): SocialLoginResponse {
|
||||
val googleUserInfo = googleService.getUserInfo(token)
|
||||
val googleUserInfo = googleService.getUserInfo(idToken)
|
||||
?: throw SodaException(messageKey = "member.social.google_login_failed")
|
||||
val memberResolveResult = memberService.findOrRegister(googleUserInfo, container, marketingPid, pushToken)
|
||||
val member = memberResolveResult.member
|
||||
val principal = MemberAdapter(member)
|
||||
val authToken = GoogleAuthenticationToken(token, principal.authorities)
|
||||
val authToken = GoogleAuthenticationToken(idToken, principal.authorities)
|
||||
authToken.setPrincipal(principal)
|
||||
SecurityContextHolder.getContext().authentication = authToken
|
||||
|
||||
@@ -48,7 +43,7 @@ class GoogleAuthService(
|
||||
userId = member.id!!,
|
||||
token = jwt,
|
||||
nickname = member.nickname,
|
||||
email = member.email ?: "",
|
||||
email = member.email,
|
||||
profileImage = if (member.profileImage != null) {
|
||||
"$cloudFrontHost/${member.profileImage}"
|
||||
} else {
|
||||
|
||||
@@ -3,6 +3,7 @@ package kr.co.vividnext.sodalive.member.social.google
|
||||
import com.google.api.client.googleapis.auth.oauth2.GoogleIdTokenVerifier
|
||||
import com.google.api.client.googleapis.javanet.GoogleNetHttpTransport
|
||||
import com.google.api.client.json.gson.GsonFactory
|
||||
import kr.co.vividnext.sodalive.common.SodaException
|
||||
import org.springframework.beans.factory.annotation.Value
|
||||
import org.springframework.stereotype.Service
|
||||
|
||||
@@ -26,7 +27,7 @@ class GoogleService(
|
||||
|
||||
if (token != null) {
|
||||
val payload = token.payload
|
||||
val email = payload.email
|
||||
val email = payload.email ?: throw SodaException(messageKey = "member.social.email_consent_required")
|
||||
|
||||
GoogleUserInfo(
|
||||
sub = payload.subject,
|
||||
|
||||
@@ -2,6 +2,6 @@ package kr.co.vividnext.sodalive.member.social.google
|
||||
|
||||
data class GoogleUserInfo(
|
||||
val sub: String,
|
||||
val email: String?,
|
||||
val email: String,
|
||||
val name: String?
|
||||
)
|
||||
|
||||
@@ -3,10 +3,8 @@ package kr.co.vividnext.sodalive.member.social.kakao
|
||||
import kr.co.vividnext.sodalive.common.SodaException
|
||||
import kr.co.vividnext.sodalive.jwt.TokenProvider
|
||||
import kr.co.vividnext.sodalive.member.MemberAdapter
|
||||
import kr.co.vividnext.sodalive.member.MemberProvider
|
||||
import kr.co.vividnext.sodalive.member.MemberService
|
||||
import kr.co.vividnext.sodalive.member.login.LoginResponse
|
||||
import kr.co.vividnext.sodalive.member.social.SocialAuthService
|
||||
import kr.co.vividnext.sodalive.member.social.SocialLoginResponse
|
||||
import org.springframework.beans.factory.annotation.Value
|
||||
import org.springframework.security.core.context.SecurityContextHolder
|
||||
@@ -20,22 +18,19 @@ class KakaoAuthService(
|
||||
|
||||
@Value("\${cloud.aws.cloud-front.host}")
|
||||
private val cloudFrontHost: String
|
||||
) : SocialAuthService {
|
||||
override fun getProvider(): MemberProvider = MemberProvider.KAKAO
|
||||
|
||||
override fun authenticate(
|
||||
token: String,
|
||||
) {
|
||||
fun authenticate(
|
||||
accessToken: String,
|
||||
container: String,
|
||||
marketingPid: String?,
|
||||
pushToken: String?,
|
||||
nonce: String?
|
||||
pushToken: String?
|
||||
): SocialLoginResponse {
|
||||
val kakaoUserInfo = kakaoService.getUserInfo(token)
|
||||
val kakaoUserInfo = kakaoService.getUserInfo(accessToken)
|
||||
?: throw SodaException(messageKey = "member.social.kakao_login_failed")
|
||||
val memberResolveResult = memberService.findOrRegister(kakaoUserInfo, container, marketingPid, pushToken)
|
||||
val member = memberResolveResult.member
|
||||
val principal = MemberAdapter(member)
|
||||
val authToken = KakaoAuthenticationToken(token, principal.authorities)
|
||||
val authToken = KakaoAuthenticationToken(accessToken, principal.authorities)
|
||||
authToken.setPrincipal(principal)
|
||||
SecurityContextHolder.getContext().authentication = authToken
|
||||
|
||||
@@ -48,7 +43,7 @@ class KakaoAuthService(
|
||||
userId = member.id!!,
|
||||
token = jwt,
|
||||
nickname = member.nickname,
|
||||
email = member.email ?: "",
|
||||
email = member.email,
|
||||
profileImage = if (member.profileImage != null) {
|
||||
"$cloudFrontHost/${member.profileImage}"
|
||||
} else {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package kr.co.vividnext.sodalive.member.social.kakao
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper
|
||||
import kr.co.vividnext.sodalive.common.SodaException
|
||||
import org.springframework.http.HttpEntity
|
||||
import org.springframework.http.HttpHeaders
|
||||
import org.springframework.http.HttpMethod
|
||||
@@ -36,6 +37,7 @@ class KakaoService(
|
||||
val id = jsonNode.get("id").asLong()
|
||||
val kakaoAccount = jsonNode.get("kakao_account")
|
||||
val email = kakaoAccount?.get("email")?.asText()
|
||||
?: throw SodaException(messageKey = "member.social.kakao_login_failed")
|
||||
val properties = jsonNode.get("properties")
|
||||
val nickname = properties?.get("nickname")?.asText()
|
||||
|
||||
|
||||
@@ -2,6 +2,6 @@ package kr.co.vividnext.sodalive.member.social.kakao
|
||||
|
||||
data class KakaoUserInfo(
|
||||
val id: Long,
|
||||
val email: String?,
|
||||
val email: String,
|
||||
val nickname: String?
|
||||
)
|
||||
|
||||
@@ -1,112 +0,0 @@
|
||||
package kr.co.vividnext.sodalive.member.social.line
|
||||
|
||||
import kr.co.vividnext.sodalive.common.SodaException
|
||||
import kr.co.vividnext.sodalive.jwt.TokenProvider
|
||||
import kr.co.vividnext.sodalive.member.MemberAdapter
|
||||
import kr.co.vividnext.sodalive.member.MemberProvider
|
||||
import kr.co.vividnext.sodalive.member.MemberService
|
||||
import kr.co.vividnext.sodalive.member.login.LoginResponse
|
||||
import kr.co.vividnext.sodalive.member.social.SocialAuthService
|
||||
import kr.co.vividnext.sodalive.member.social.SocialLoginResponse
|
||||
import org.springframework.beans.factory.annotation.Value
|
||||
import org.springframework.security.core.context.SecurityContextHolder
|
||||
import org.springframework.stereotype.Service
|
||||
import java.time.Instant
|
||||
|
||||
@Service
|
||||
class LineAuthService(
|
||||
private val lineService: LineService,
|
||||
private val memberService: MemberService,
|
||||
private val tokenProvider: TokenProvider,
|
||||
|
||||
@Value("\${cloud.aws.cloud-front.host}")
|
||||
private val cloudFrontHost: String,
|
||||
|
||||
@Value("\${line.channel-id}")
|
||||
private val lineChannelId: String
|
||||
) : SocialAuthService {
|
||||
override fun getProvider(): MemberProvider = MemberProvider.LINE
|
||||
|
||||
override fun authenticate(
|
||||
token: String,
|
||||
container: String,
|
||||
marketingPid: String?,
|
||||
pushToken: String?,
|
||||
nonce: String?
|
||||
): SocialLoginResponse {
|
||||
val rawNonce = nonce?.takeIf { it.isNotBlank() }
|
||||
?: throw SodaException(messageKey = "member.social.line_login_failed")
|
||||
|
||||
if (lineChannelId.isBlank()) {
|
||||
throw SodaException(messageKey = "member.social.line_login_failed")
|
||||
}
|
||||
|
||||
val verifyResponse = lineService.verifyIdToken(token, lineChannelId, rawNonce)
|
||||
?: throw SodaException(messageKey = "member.social.line_login_failed")
|
||||
|
||||
validateVerifyResponse(verifyResponse, lineChannelId, rawNonce)
|
||||
|
||||
val lineUserInfo = LineUserInfo(
|
||||
sub = verifyResponse.sub,
|
||||
email = verifyResponse.email
|
||||
)
|
||||
|
||||
val memberResolveResult = memberService.findOrRegister(lineUserInfo, container, marketingPid, pushToken)
|
||||
val member = memberResolveResult.member
|
||||
val principal = MemberAdapter(member)
|
||||
val authToken = LineAuthenticationToken(token, principal.authorities)
|
||||
authToken.setPrincipal(principal)
|
||||
SecurityContextHolder.getContext().authentication = authToken
|
||||
|
||||
val jwt = tokenProvider.createToken(
|
||||
authentication = authToken,
|
||||
memberId = member.id!!
|
||||
)
|
||||
|
||||
val loginResponse = LoginResponse(
|
||||
userId = member.id!!,
|
||||
token = jwt,
|
||||
nickname = member.nickname,
|
||||
email = member.email ?: "",
|
||||
profileImage = if (member.profileImage != null) {
|
||||
"$cloudFrontHost/${member.profileImage}"
|
||||
} else {
|
||||
"$cloudFrontHost/profile/default-profile.png"
|
||||
}
|
||||
)
|
||||
|
||||
return SocialLoginResponse(
|
||||
memberId = member.id!!,
|
||||
marketingPid = marketingPid,
|
||||
loginResponse = loginResponse,
|
||||
isNew = memberResolveResult.isNew
|
||||
)
|
||||
}
|
||||
|
||||
private fun validateVerifyResponse(response: LineVerifyResponse, clientId: String, nonce: String) {
|
||||
if (response.iss != ISSUER) {
|
||||
throw SodaException(messageKey = "member.social.line_login_failed")
|
||||
}
|
||||
|
||||
if (response.aud != clientId) {
|
||||
throw SodaException(messageKey = "member.social.line_login_failed")
|
||||
}
|
||||
|
||||
val now = Instant.now().epochSecond
|
||||
if (response.exp <= now) {
|
||||
throw SodaException(messageKey = "member.social.line_login_failed")
|
||||
}
|
||||
|
||||
if (response.iat > now) {
|
||||
throw SodaException(messageKey = "member.social.line_login_failed")
|
||||
}
|
||||
|
||||
if (response.nonce != null && response.nonce != nonce) {
|
||||
throw SodaException(messageKey = "member.social.line_login_failed")
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val ISSUER = "https://access.line.me"
|
||||
}
|
||||
}
|
||||
@@ -1,23 +0,0 @@
|
||||
package kr.co.vividnext.sodalive.member.social.line
|
||||
|
||||
import org.springframework.security.authentication.AbstractAuthenticationToken
|
||||
import org.springframework.security.core.GrantedAuthority
|
||||
|
||||
class LineAuthenticationToken(
|
||||
private val idToken: String,
|
||||
authorities: Collection<GrantedAuthority>? = null
|
||||
) : AbstractAuthenticationToken(authorities) {
|
||||
private var principal: Any? = null
|
||||
|
||||
init {
|
||||
isAuthenticated = authorities != null
|
||||
}
|
||||
|
||||
override fun getCredentials(): Any = idToken
|
||||
|
||||
override fun getPrincipal(): Any? = principal
|
||||
|
||||
fun setPrincipal(principal: Any) {
|
||||
this.principal = principal
|
||||
}
|
||||
}
|
||||
@@ -1,48 +0,0 @@
|
||||
package kr.co.vividnext.sodalive.member.social.line
|
||||
|
||||
import org.springframework.http.HttpEntity
|
||||
import org.springframework.http.HttpHeaders
|
||||
import org.springframework.http.MediaType
|
||||
import org.springframework.http.ResponseEntity
|
||||
import org.springframework.stereotype.Service
|
||||
import org.springframework.util.LinkedMultiValueMap
|
||||
import org.springframework.web.client.RestTemplate
|
||||
|
||||
@Service
|
||||
class LineService(
|
||||
private val restTemplate: RestTemplate = RestTemplate()
|
||||
) {
|
||||
fun verifyIdToken(idToken: String, clientId: String, nonce: String?): LineVerifyResponse? {
|
||||
val url = "https://api.line.me/oauth2/v2.1/verify"
|
||||
val headers = HttpHeaders().apply {
|
||||
contentType = MediaType.APPLICATION_FORM_URLENCODED
|
||||
}
|
||||
|
||||
val body = LinkedMultiValueMap<String, String>().apply {
|
||||
add("id_token", idToken)
|
||||
add("client_id", clientId)
|
||||
if (!nonce.isNullOrBlank()) {
|
||||
add("nonce", nonce)
|
||||
}
|
||||
}
|
||||
|
||||
val entity = HttpEntity(body, headers)
|
||||
|
||||
return try {
|
||||
val response: ResponseEntity<LineVerifyResponse> = restTemplate.postForEntity(
|
||||
url,
|
||||
entity,
|
||||
LineVerifyResponse::class.java
|
||||
)
|
||||
|
||||
if (response.statusCode.is2xxSuccessful) {
|
||||
response.body
|
||||
} else {
|
||||
null
|
||||
}
|
||||
} catch (ex: Exception) {
|
||||
ex.printStackTrace()
|
||||
null
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,6 +0,0 @@
|
||||
package kr.co.vividnext.sodalive.member.social.line
|
||||
|
||||
data class LineUserInfo(
|
||||
val sub: String,
|
||||
val email: String? = null
|
||||
)
|
||||
@@ -1,16 +0,0 @@
|
||||
package kr.co.vividnext.sodalive.member.social.line
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties
|
||||
|
||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||
data class LineVerifyResponse(
|
||||
val iss: String,
|
||||
val sub: String,
|
||||
val aud: String,
|
||||
val exp: Long,
|
||||
val iat: Long,
|
||||
val nonce: String? = null,
|
||||
val name: String? = null,
|
||||
val picture: String? = null,
|
||||
val email: String? = null
|
||||
)
|
||||
@@ -1,10 +1,9 @@
|
||||
package kr.co.vividnext.sodalive.menu
|
||||
|
||||
import kr.co.vividnext.sodalive.common.ApiResponse
|
||||
import kr.co.vividnext.sodalive.common.SodaException
|
||||
import kr.co.vividnext.sodalive.member.Member
|
||||
import org.springframework.security.access.prepost.PreAuthorize
|
||||
import org.springframework.security.core.annotation.AuthenticationPrincipal
|
||||
import org.springframework.security.core.userdetails.User
|
||||
import org.springframework.web.bind.annotation.GetMapping
|
||||
import org.springframework.web.bind.annotation.RequestMapping
|
||||
import org.springframework.web.bind.annotation.RestController
|
||||
@@ -14,13 +13,5 @@ import org.springframework.web.bind.annotation.RestController
|
||||
class MenuController(private val service: MenuService) {
|
||||
@GetMapping
|
||||
@PreAuthorize("hasAnyRole('AGENT', 'ADMIN', 'CREATOR')")
|
||||
fun getMenus(
|
||||
@AuthenticationPrincipal(expression = "#this == 'anonymousUser' ? null : member") member: Member?
|
||||
) = run {
|
||||
if (member == null) {
|
||||
throw SodaException(messageKey = "common.error.bad_credentials")
|
||||
}
|
||||
|
||||
ApiResponse.ok(service.getMenus(member))
|
||||
}
|
||||
fun getMenus(@AuthenticationPrincipal user: User) = ApiResponse.ok(service.getMenus(user))
|
||||
}
|
||||
|
||||
@@ -1,13 +1,18 @@
|
||||
package kr.co.vividnext.sodalive.menu
|
||||
|
||||
import kr.co.vividnext.sodalive.member.Member
|
||||
import kr.co.vividnext.sodalive.common.SodaException
|
||||
import kr.co.vividnext.sodalive.member.MemberRepository
|
||||
import org.springframework.security.core.userdetails.User
|
||||
import org.springframework.stereotype.Service
|
||||
|
||||
@Service
|
||||
class MenuService(
|
||||
private val repository: MenuRepository
|
||||
private val repository: MenuRepository,
|
||||
private val memberRepository: MemberRepository
|
||||
) {
|
||||
fun getMenus(member: Member): List<GetMenuResponse> {
|
||||
fun getMenus(user: User): List<GetMenuResponse> {
|
||||
val member = memberRepository.findByEmail(user.username)
|
||||
?: throw SodaException(messageKey = "common.error.bad_credentials")
|
||||
return repository.getMenu(member.role)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -72,9 +72,11 @@ class MessageService(
|
||||
applicationEventPublisher.publishEvent(
|
||||
FcmEvent(
|
||||
type = FcmEventType.SEND_MESSAGE,
|
||||
titleKey = "message.fcm.title",
|
||||
messageKey = "message.fcm.text_received",
|
||||
args = listOf(sender.nickname),
|
||||
title = messageSource.getMessage("message.fcm.title", langContext.lang).orEmpty(),
|
||||
message = run {
|
||||
val messageTemplate = messageSource.getMessage("message.fcm.text_received", langContext.lang).orEmpty()
|
||||
String.format(messageTemplate, sender.nickname)
|
||||
},
|
||||
messageId = message.id
|
||||
)
|
||||
)
|
||||
@@ -145,9 +147,11 @@ class MessageService(
|
||||
applicationEventPublisher.publishEvent(
|
||||
FcmEvent(
|
||||
type = FcmEventType.SEND_MESSAGE,
|
||||
titleKey = "message.fcm.title",
|
||||
messageKey = "message.fcm.voice_received",
|
||||
args = listOf(sender.nickname),
|
||||
title = messageSource.getMessage("message.fcm.title", langContext.lang).orEmpty(),
|
||||
message = run {
|
||||
val messageTemplate = messageSource.getMessage("message.fcm.voice_received", langContext.lang).orEmpty()
|
||||
String.format(messageTemplate, sender.nickname)
|
||||
},
|
||||
messageId = message.id
|
||||
)
|
||||
)
|
||||
|
||||
@@ -33,10 +33,6 @@ bootpay:
|
||||
apple:
|
||||
iapVerifyUrl: https://buy.itunes.apple.com/verifyReceipt
|
||||
iapVerifySandboxUrl: https://sandbox.itunes.apple.com/verifyReceipt
|
||||
bundleId: ${APPLE_BUNDLE_ID}
|
||||
|
||||
line:
|
||||
channelId: ${LINE_CHANNEL_ID}
|
||||
|
||||
agora:
|
||||
appId: ${AGORA_APP_ID}
|
||||
|
||||
Reference in New Issue
Block a user