Compare commits
400 Commits
96513eef6a
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| de32b537f4 | |||
| 37d2e0de73 | |||
| 9779c1b50b | |||
| 23c219c672 | |||
| 4a2a3cbbf8 | |||
| d1512f418f | |||
| 9c271fc1f6 | |||
| d90a872e79 | |||
| 328be036f7 | |||
| 3e41e763e3 | |||
| be6f7971c6 | |||
| 2ddbfbccd6 | |||
| e0024a52ab | |||
| 3cabc9de95 | |||
| f1f80ae386 | |||
| 5eca3f770c | |||
| ac5741b9af | |||
| 04a4b362da | |||
| 80786deb72 | |||
| 8ca2e185ac | |||
| 484711ad1b | |||
| e80ceca0c5 | |||
| 33293a6533 | |||
| f0c1d4e32a | |||
| 6cd319ec76 | |||
| 6557ec2aed | |||
| f2f8a34319 | |||
| c50ac6ed2c | |||
| 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 |
@@ -2,13 +2,15 @@ package kr.co.vividnext.sodalive.admin.content
|
|||||||
|
|
||||||
import kr.co.vividnext.sodalive.common.ApiResponse
|
import kr.co.vividnext.sodalive.common.ApiResponse
|
||||||
import org.springframework.data.domain.Pageable
|
import org.springframework.data.domain.Pageable
|
||||||
|
import org.springframework.http.MediaType
|
||||||
import org.springframework.security.access.prepost.PreAuthorize
|
import org.springframework.security.access.prepost.PreAuthorize
|
||||||
import org.springframework.web.bind.annotation.GetMapping
|
import org.springframework.web.bind.annotation.GetMapping
|
||||||
import org.springframework.web.bind.annotation.PutMapping
|
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.RequestMapping
|
||||||
import org.springframework.web.bind.annotation.RequestParam
|
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.bind.annotation.RestController
|
||||||
|
import org.springframework.web.multipart.MultipartFile
|
||||||
|
|
||||||
@RestController
|
@RestController
|
||||||
@PreAuthorize("hasRole('ADMIN')")
|
@PreAuthorize("hasRole('ADMIN')")
|
||||||
@@ -38,10 +40,11 @@ class AdminContentController(private val service: AdminContentService) {
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
@PutMapping
|
@PutMapping(consumes = [MediaType.MULTIPART_FORM_DATA_VALUE])
|
||||||
fun modifyAudioContent(
|
fun modifyAudioContent(
|
||||||
@RequestBody request: UpdateAdminContentRequest
|
@RequestPart("request") requestString: String,
|
||||||
) = ApiResponse.ok(service.updateAudioContent(request))
|
@RequestPart("coverImage", required = false) coverImage: MultipartFile? = null
|
||||||
|
) = ApiResponse.ok(service.updateAudioContent(coverImage, requestString))
|
||||||
|
|
||||||
@GetMapping("/main/tab")
|
@GetMapping("/main/tab")
|
||||||
fun getContentMainTabList() = ApiResponse.ok(service.getContentMainTabList())
|
fun getContentMainTabList() = ApiResponse.ok(service.getContentMainTabList())
|
||||||
|
|||||||
@@ -1,15 +1,21 @@
|
|||||||
package kr.co.vividnext.sodalive.admin.content
|
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.curation.AdminContentCurationRepository
|
||||||
import kr.co.vividnext.sodalive.admin.content.tab.AdminContentMainTabRepository
|
import kr.co.vividnext.sodalive.admin.content.tab.AdminContentMainTabRepository
|
||||||
import kr.co.vividnext.sodalive.admin.content.theme.AdminContentThemeRepository
|
import kr.co.vividnext.sodalive.admin.content.theme.AdminContentThemeRepository
|
||||||
import kr.co.vividnext.sodalive.aws.cloudfront.AudioContentCloudFront
|
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.common.SodaException
|
||||||
import kr.co.vividnext.sodalive.content.main.tab.GetContentMainTabItem
|
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.domain.Pageable
|
||||||
import org.springframework.data.repository.findByIdOrNull
|
import org.springframework.data.repository.findByIdOrNull
|
||||||
import org.springframework.stereotype.Service
|
import org.springframework.stereotype.Service
|
||||||
import org.springframework.transaction.annotation.Transactional
|
import org.springframework.transaction.annotation.Transactional
|
||||||
|
import org.springframework.web.multipart.MultipartFile
|
||||||
|
|
||||||
@Service
|
@Service
|
||||||
class AdminContentService(
|
class AdminContentService(
|
||||||
@@ -17,7 +23,11 @@ class AdminContentService(
|
|||||||
private val themeRepository: AdminContentThemeRepository,
|
private val themeRepository: AdminContentThemeRepository,
|
||||||
private val audioContentCloudFront: AudioContentCloudFront,
|
private val audioContentCloudFront: AudioContentCloudFront,
|
||||||
private val curationRepository: AdminContentCurationRepository,
|
private val curationRepository: AdminContentCurationRepository,
|
||||||
private val contentMainTabRepository: AdminContentMainTabRepository
|
private val contentMainTabRepository: AdminContentMainTabRepository,
|
||||||
|
private val objectMapper: ObjectMapper,
|
||||||
|
private val s3Uploader: S3Uploader,
|
||||||
|
@Value("\${cloud.aws.s3.bucket}")
|
||||||
|
private val bucket: String
|
||||||
) {
|
) {
|
||||||
fun getAudioContentList(status: ContentReleaseStatus, pageable: Pageable): GetAdminContentListResponse {
|
fun getAudioContentList(status: ContentReleaseStatus, pageable: Pageable): GetAdminContentListResponse {
|
||||||
val totalCount = repository.getAudioContentTotalCount(status = status)
|
val totalCount = repository.getAudioContentTotalCount(status = status)
|
||||||
@@ -82,12 +92,25 @@ class AdminContentService(
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Transactional
|
@Transactional
|
||||||
fun updateAudioContent(request: UpdateAdminContentRequest) {
|
fun updateAudioContent(coverImage: MultipartFile?, requestString: String) {
|
||||||
|
val request = objectMapper.readValue(requestString, UpdateAdminContentRequest::class.java)
|
||||||
val audioContent = repository.findByIdOrNull(id = request.id)
|
val audioContent = repository.findByIdOrNull(id = request.id)
|
||||||
?: throw SodaException(messageKey = "admin.content.not_found")
|
?: throw SodaException(messageKey = "admin.content.not_found")
|
||||||
|
|
||||||
if (request.isDefaultCoverImage) {
|
if (coverImage != null) {
|
||||||
audioContent.coverImage = "`profile/default_profile.png`"
|
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.isActive != null) {
|
if (request.isActive != null) {
|
||||||
|
|||||||
@@ -68,6 +68,19 @@ class AdminMemberStatisticsRepository(private val queryFactory: JPAQueryFactory)
|
|||||||
.size
|
.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 {
|
fun getTotalSignUpLineCount(startDate: LocalDateTime, endDate: LocalDateTime): Int {
|
||||||
return queryFactory
|
return queryFactory
|
||||||
.select(member.id)
|
.select(member.id)
|
||||||
@@ -202,6 +215,25 @@ class AdminMemberStatisticsRepository(private val queryFactory: JPAQueryFactory)
|
|||||||
.fetch()
|
.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> {
|
fun getSignUpLineCountInRange(startDate: LocalDateTime, endDate: LocalDateTime): List<DateAndMemberCount> {
|
||||||
return queryFactory
|
return queryFactory
|
||||||
.select(
|
.select(
|
||||||
|
|||||||
@@ -58,6 +58,10 @@ class AdminMemberStatisticsService(private val repository: AdminMemberStatistics
|
|||||||
startDate = startDateTime,
|
startDate = startDateTime,
|
||||||
endDate = endDateTime
|
endDate = endDateTime
|
||||||
)
|
)
|
||||||
|
val totalSignUpAppleCount = repository.getTotalSignUpAppleCount(
|
||||||
|
startDate = startDateTime,
|
||||||
|
endDate = endDateTime
|
||||||
|
)
|
||||||
val totalSignUpLineCount = repository.getTotalSignUpLineCount(
|
val totalSignUpLineCount = repository.getTotalSignUpLineCount(
|
||||||
startDate = startDateTime,
|
startDate = startDateTime,
|
||||||
endDate = endDateTime
|
endDate = endDateTime
|
||||||
@@ -96,6 +100,11 @@ class AdminMemberStatisticsService(private val repository: AdminMemberStatistics
|
|||||||
endDate = endDateTime
|
endDate = endDateTime
|
||||||
).associateBy({ it.date }, { it.memberCount })
|
).associateBy({ it.date }, { it.memberCount })
|
||||||
|
|
||||||
|
val signUpAppleCountInRange = repository.getSignUpAppleCountInRange(
|
||||||
|
startDate = startDateTime,
|
||||||
|
endDate = endDateTime
|
||||||
|
).associateBy({ it.date }, { it.memberCount })
|
||||||
|
|
||||||
val signUpLineCountInRange = repository.getSignUpLineCountInRange(
|
val signUpLineCountInRange = repository.getSignUpLineCountInRange(
|
||||||
startDate = startDateTime,
|
startDate = startDateTime,
|
||||||
endDate = endDateTime
|
endDate = endDateTime
|
||||||
@@ -130,6 +139,7 @@ class AdminMemberStatisticsService(private val repository: AdminMemberStatistics
|
|||||||
signUpEmailCount = signUpEmailCountInRange[date] ?: 0,
|
signUpEmailCount = signUpEmailCountInRange[date] ?: 0,
|
||||||
signUpKakaoCount = signUpKakaoCountInRange[date] ?: 0,
|
signUpKakaoCount = signUpKakaoCountInRange[date] ?: 0,
|
||||||
signUpGoogleCount = signUpGoogleCountInRange[date] ?: 0,
|
signUpGoogleCount = signUpGoogleCountInRange[date] ?: 0,
|
||||||
|
signUpAppleCount = signUpAppleCountInRange[date] ?: 0,
|
||||||
signUpLineCount = signUpLineCountInRange[date] ?: 0,
|
signUpLineCount = signUpLineCountInRange[date] ?: 0,
|
||||||
signOutCount = signOutCountInRange[date] ?: 0,
|
signOutCount = signOutCountInRange[date] ?: 0,
|
||||||
paymentMemberCount = paymentMemberCountInRangeMap[date] ?: 0
|
paymentMemberCount = paymentMemberCountInRangeMap[date] ?: 0
|
||||||
@@ -144,6 +154,7 @@ class AdminMemberStatisticsService(private val repository: AdminMemberStatistics
|
|||||||
totalSignUpEmailCount = totalSignUpEmailCount,
|
totalSignUpEmailCount = totalSignUpEmailCount,
|
||||||
totalSignUpKakaoCount = totalSignUpKakaoCount,
|
totalSignUpKakaoCount = totalSignUpKakaoCount,
|
||||||
totalSignUpGoogleCount = totalSignUpGoogleCount,
|
totalSignUpGoogleCount = totalSignUpGoogleCount,
|
||||||
|
totalSignUpAppleCount = totalSignUpAppleCount,
|
||||||
totalSignUpLineCount = totalSignUpLineCount,
|
totalSignUpLineCount = totalSignUpLineCount,
|
||||||
totalSignOutCount = totalSignOutCount,
|
totalSignOutCount = totalSignOutCount,
|
||||||
totalPaymentMemberCount = totalPaymentMemberCount,
|
totalPaymentMemberCount = totalPaymentMemberCount,
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ data class GetMemberStatisticsResponse(
|
|||||||
val totalSignUpEmailCount: Int,
|
val totalSignUpEmailCount: Int,
|
||||||
val totalSignUpKakaoCount: Int,
|
val totalSignUpKakaoCount: Int,
|
||||||
val totalSignUpGoogleCount: Int,
|
val totalSignUpGoogleCount: Int,
|
||||||
|
val totalSignUpAppleCount: Int,
|
||||||
val totalSignUpLineCount: Int,
|
val totalSignUpLineCount: Int,
|
||||||
val totalSignOutCount: Int,
|
val totalSignOutCount: Int,
|
||||||
val totalPaymentMemberCount: Int,
|
val totalPaymentMemberCount: Int,
|
||||||
@@ -20,6 +21,7 @@ data class GetMemberStatisticsItem(
|
|||||||
val signUpEmailCount: Int,
|
val signUpEmailCount: Int,
|
||||||
val signUpKakaoCount: Int,
|
val signUpKakaoCount: Int,
|
||||||
val signUpGoogleCount: Int,
|
val signUpGoogleCount: Int,
|
||||||
|
val signUpAppleCount: Int,
|
||||||
val signUpLineCount: Int,
|
val signUpLineCount: Int,
|
||||||
val signOutCount: Int,
|
val signOutCount: Int,
|
||||||
val paymentMemberCount: Int
|
val paymentMemberCount: Int
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import kr.co.vividnext.sodalive.explorer.profile.PostWriteCheersRequest
|
|||||||
import kr.co.vividnext.sodalive.explorer.profile.PutWriteCheersRequest
|
import kr.co.vividnext.sodalive.explorer.profile.PutWriteCheersRequest
|
||||||
import kr.co.vividnext.sodalive.i18n.LangContext
|
import kr.co.vividnext.sodalive.i18n.LangContext
|
||||||
import kr.co.vividnext.sodalive.i18n.SodaMessageSource
|
import kr.co.vividnext.sodalive.i18n.SodaMessageSource
|
||||||
|
import kr.co.vividnext.sodalive.member.DonationRankingPeriod
|
||||||
import kr.co.vividnext.sodalive.member.Member
|
import kr.co.vividnext.sodalive.member.Member
|
||||||
import org.springframework.data.domain.Pageable
|
import org.springframework.data.domain.Pageable
|
||||||
import org.springframework.security.access.prepost.PreAuthorize
|
import org.springframework.security.access.prepost.PreAuthorize
|
||||||
@@ -75,11 +76,12 @@ class ExplorerController(
|
|||||||
@GetMapping("/profile/{id}/donation-rank")
|
@GetMapping("/profile/{id}/donation-rank")
|
||||||
fun getCreatorProfileDonationRanking(
|
fun getCreatorProfileDonationRanking(
|
||||||
@PathVariable("id") creatorId: Long,
|
@PathVariable("id") creatorId: Long,
|
||||||
|
@RequestParam("period", required = false) period: DonationRankingPeriod? = null,
|
||||||
@AuthenticationPrincipal(expression = "#this == 'anonymousUser' ? null : member") member: Member?,
|
@AuthenticationPrincipal(expression = "#this == 'anonymousUser' ? null : member") member: Member?,
|
||||||
pageable: Pageable
|
pageable: Pageable
|
||||||
) = run {
|
) = run {
|
||||||
if (member == null) throw SodaException(messageKey = "common.error.bad_credentials")
|
if (member == null) throw SodaException(messageKey = "common.error.bad_credentials")
|
||||||
ApiResponse.ok(service.getCreatorProfileDonationRanking(creatorId, pageable, member))
|
ApiResponse.ok(service.getCreatorProfileDonationRanking(creatorId, period, pageable, member))
|
||||||
}
|
}
|
||||||
|
|
||||||
@PostMapping("/profile/cheers")
|
@PostMapping("/profile/cheers")
|
||||||
|
|||||||
@@ -22,11 +22,13 @@ import kr.co.vividnext.sodalive.explorer.profile.TimeDifferenceResult
|
|||||||
import kr.co.vividnext.sodalive.i18n.Lang
|
import kr.co.vividnext.sodalive.i18n.Lang
|
||||||
import kr.co.vividnext.sodalive.i18n.LangContext
|
import kr.co.vividnext.sodalive.i18n.LangContext
|
||||||
import kr.co.vividnext.sodalive.i18n.SodaMessageSource
|
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.LiveRoom
|
||||||
import kr.co.vividnext.sodalive.live.room.LiveRoomType
|
import kr.co.vividnext.sodalive.live.room.LiveRoomType
|
||||||
import kr.co.vividnext.sodalive.live.room.QLiveRoom.liveRoom
|
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.cancel.QLiveRoomCancel.liveRoomCancel
|
||||||
import kr.co.vividnext.sodalive.live.room.visit.QLiveRoomVisit.liveRoomVisit
|
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.Member
|
||||||
import kr.co.vividnext.sodalive.member.MemberRole
|
import kr.co.vividnext.sodalive.member.MemberRole
|
||||||
import kr.co.vividnext.sodalive.member.QMember
|
import kr.co.vividnext.sodalive.member.QMember
|
||||||
@@ -342,6 +344,21 @@ class ExplorerQueryRepository(
|
|||||||
.and(liveRoom.cancel.id.isNull)
|
.and(liveRoom.cancel.id.isNull)
|
||||||
.and(liveRoom.isActive.isTrue)
|
.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) {
|
if (userMember.auth == null) {
|
||||||
where = where.and(liveRoom.isAdult.isFalse)
|
where = where.and(liveRoom.isAdult.isFalse)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ import kr.co.vividnext.sodalive.i18n.Lang
|
|||||||
import kr.co.vividnext.sodalive.i18n.LangContext
|
import kr.co.vividnext.sodalive.i18n.LangContext
|
||||||
import kr.co.vividnext.sodalive.i18n.SodaMessageSource
|
import kr.co.vividnext.sodalive.i18n.SodaMessageSource
|
||||||
import kr.co.vividnext.sodalive.live.room.detail.GetRoomDetailUser
|
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.Member
|
||||||
import kr.co.vividnext.sodalive.member.MemberRole
|
import kr.co.vividnext.sodalive.member.MemberRole
|
||||||
import kr.co.vividnext.sodalive.member.MemberService
|
import kr.co.vividnext.sodalive.member.MemberService
|
||||||
@@ -215,6 +216,7 @@ class ExplorerService(
|
|||||||
val notificationUserIds = queryRepository.getNotificationUserIds(creatorId)
|
val notificationUserIds = queryRepository.getNotificationUserIds(creatorId)
|
||||||
val creatorFollowing = queryRepository.getCreatorFollowing(creatorId = creatorId, memberId = member.id!!)
|
val creatorFollowing = queryRepository.getCreatorFollowing(creatorId = creatorId, memberId = member.id!!)
|
||||||
val notificationRecipientCount = notificationUserIds.size
|
val notificationRecipientCount = notificationUserIds.size
|
||||||
|
val donationRankingPeriod = creatorAccount.donationRankingPeriod ?: DonationRankingPeriod.CUMULATIVE
|
||||||
|
|
||||||
// 후원랭킹
|
// 후원랭킹
|
||||||
val memberDonationRanking = if (
|
val memberDonationRanking = if (
|
||||||
@@ -223,7 +225,8 @@ class ExplorerService(
|
|||||||
donationRankingService.getMemberDonationRanking(
|
donationRankingService.getMemberDonationRanking(
|
||||||
creatorId = creatorId,
|
creatorId = creatorId,
|
||||||
limit = 10,
|
limit = 10,
|
||||||
withDonationCan = creatorId == member.id!!
|
withDonationCan = creatorId == member.id!!,
|
||||||
|
period = donationRankingPeriod
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
listOf()
|
listOf()
|
||||||
@@ -396,23 +399,37 @@ class ExplorerService(
|
|||||||
|
|
||||||
fun getCreatorProfileDonationRanking(
|
fun getCreatorProfileDonationRanking(
|
||||||
creatorId: Long,
|
creatorId: Long,
|
||||||
|
period: DonationRankingPeriod?,
|
||||||
pageable: Pageable,
|
pageable: Pageable,
|
||||||
member: Member
|
member: Member
|
||||||
): GetDonationAllResponse {
|
): 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 currentDate = LocalDate.now().atTime(0, 0, 0)
|
||||||
val firstDayOfLastWeek = currentDate.with(TemporalAdjusters.previousOrSame(DayOfWeek.MONDAY)).minusDays(7)
|
val firstDayOfLastWeek = currentDate.with(TemporalAdjusters.previousOrSame(DayOfWeek.MONDAY)).minusDays(7)
|
||||||
val firstDayOfMonth = currentDate.with(TemporalAdjusters.firstDayOfMonth())
|
val firstDayOfMonth = currentDate.with(TemporalAdjusters.firstDayOfMonth())
|
||||||
|
|
||||||
val donationMemberTotal = donationRankingService.getMemberDonationRankingTotal(creatorId)
|
val donationMemberTotal = donationRankingService.getMemberDonationRankingTotal(
|
||||||
|
creatorId,
|
||||||
|
effectivePeriod
|
||||||
|
)
|
||||||
val donationRanking = donationRankingService.getMemberDonationRanking(
|
val donationRanking = donationRankingService.getMemberDonationRanking(
|
||||||
creatorId = creatorId,
|
creatorId = creatorId,
|
||||||
offset = pageable.offset,
|
offset = pageable.offset,
|
||||||
limit = pageable.pageSize.toLong(),
|
limit = pageable.pageSize.toLong(),
|
||||||
withDonationCan = creatorId == member.id!!
|
withDonationCan = isCreatorSelf,
|
||||||
|
period = effectivePeriod
|
||||||
)
|
)
|
||||||
|
|
||||||
return GetDonationAllResponse(
|
return GetDonationAllResponse(
|
||||||
accumulatedCansToday = if (creatorId == member.id!!) {
|
accumulatedCansToday = if (isCreatorSelf) {
|
||||||
queryRepository.getDonationCoinsDateRange(
|
queryRepository.getDonationCoinsDateRange(
|
||||||
creatorId,
|
creatorId,
|
||||||
currentDate,
|
currentDate,
|
||||||
@@ -421,7 +438,7 @@ class ExplorerService(
|
|||||||
} else {
|
} else {
|
||||||
0
|
0
|
||||||
},
|
},
|
||||||
accumulatedCansLastWeek = if (creatorId == member.id!!) {
|
accumulatedCansLastWeek = if (isCreatorSelf) {
|
||||||
queryRepository.getDonationCoinsDateRange(
|
queryRepository.getDonationCoinsDateRange(
|
||||||
creatorId,
|
creatorId,
|
||||||
firstDayOfLastWeek,
|
firstDayOfLastWeek,
|
||||||
@@ -430,7 +447,7 @@ class ExplorerService(
|
|||||||
} else {
|
} else {
|
||||||
0
|
0
|
||||||
},
|
},
|
||||||
accumulatedCansThisMonth = if (creatorId == member.id!!) {
|
accumulatedCansThisMonth = if (isCreatorSelf) {
|
||||||
queryRepository.getDonationCoinsDateRange(
|
queryRepository.getDonationCoinsDateRange(
|
||||||
creatorId,
|
creatorId,
|
||||||
firstDayOfMonth,
|
firstDayOfMonth,
|
||||||
@@ -439,11 +456,16 @@ class ExplorerService(
|
|||||||
} else {
|
} else {
|
||||||
0
|
0
|
||||||
},
|
},
|
||||||
isVisibleDonationRank = if (creatorId == member.id!!) {
|
isVisibleDonationRank = if (isCreatorSelf) {
|
||||||
queryRepository.getVisibleDonationRank(creatorId)
|
queryRepository.getVisibleDonationRank(creatorId)
|
||||||
} else {
|
} else {
|
||||||
false
|
false
|
||||||
},
|
},
|
||||||
|
donationRankingPeriod = if (isCreatorSelf) {
|
||||||
|
donationRankingPeriod
|
||||||
|
} else {
|
||||||
|
null
|
||||||
|
},
|
||||||
totalCount = donationMemberTotal,
|
totalCount = donationMemberTotal,
|
||||||
userDonationRanking = donationRanking
|
userDonationRanking = donationRanking
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package kr.co.vividnext.sodalive.explorer
|
package kr.co.vividnext.sodalive.explorer
|
||||||
|
|
||||||
import com.fasterxml.jackson.annotation.JsonProperty
|
import com.fasterxml.jackson.annotation.JsonProperty
|
||||||
|
import kr.co.vividnext.sodalive.member.DonationRankingPeriod
|
||||||
import java.io.Serializable
|
import java.io.Serializable
|
||||||
|
|
||||||
data class GetDonationAllResponse(
|
data class GetDonationAllResponse(
|
||||||
@@ -8,6 +9,7 @@ data class GetDonationAllResponse(
|
|||||||
val accumulatedCansLastWeek: Int,
|
val accumulatedCansLastWeek: Int,
|
||||||
val accumulatedCansThisMonth: Int,
|
val accumulatedCansThisMonth: Int,
|
||||||
val isVisibleDonationRank: Boolean,
|
val isVisibleDonationRank: Boolean,
|
||||||
|
val donationRankingPeriod: DonationRankingPeriod?,
|
||||||
val totalCount: Int,
|
val totalCount: Int,
|
||||||
val userDonationRanking: List<MemberDonationRankingResponse>
|
val userDonationRanking: List<MemberDonationRankingResponse>
|
||||||
) : Serializable
|
) : Serializable
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package kr.co.vividnext.sodalive.explorer.profile
|
package kr.co.vividnext.sodalive.explorer.profile
|
||||||
|
|
||||||
import com.fasterxml.jackson.annotation.JsonProperty
|
import com.fasterxml.jackson.annotation.JsonProperty
|
||||||
|
import com.querydsl.core.BooleanBuilder
|
||||||
import com.querydsl.core.annotations.QueryProjection
|
import com.querydsl.core.annotations.QueryProjection
|
||||||
import com.querydsl.jpa.impl.JPAQueryFactory
|
import com.querydsl.jpa.impl.JPAQueryFactory
|
||||||
import kr.co.vividnext.sodalive.can.use.CanUsage
|
import kr.co.vividnext.sodalive.can.use.CanUsage
|
||||||
@@ -8,13 +9,17 @@ import kr.co.vividnext.sodalive.can.use.QUseCan.useCan
|
|||||||
import kr.co.vividnext.sodalive.can.use.QUseCanCalculate.useCanCalculate
|
import kr.co.vividnext.sodalive.can.use.QUseCanCalculate.useCanCalculate
|
||||||
import kr.co.vividnext.sodalive.member.QMember.member
|
import kr.co.vividnext.sodalive.member.QMember.member
|
||||||
import org.springframework.stereotype.Repository
|
import org.springframework.stereotype.Repository
|
||||||
|
import java.time.LocalDateTime
|
||||||
|
import java.time.ZoneId
|
||||||
|
|
||||||
@Repository
|
@Repository
|
||||||
class CreatorDonationRankingQueryRepository(private val queryFactory: JPAQueryFactory) {
|
class CreatorDonationRankingQueryRepository(private val queryFactory: JPAQueryFactory) {
|
||||||
fun getMemberDonationRanking(
|
fun getMemberDonationRanking(
|
||||||
creatorId: Long,
|
creatorId: Long,
|
||||||
offset: Long,
|
offset: Long,
|
||||||
limit: Long
|
limit: Long,
|
||||||
|
startDate: LocalDateTime? = null,
|
||||||
|
endDate: LocalDateTime? = null
|
||||||
): List<DonationRankingProjection> {
|
): List<DonationRankingProjection> {
|
||||||
val donationCan = useCan.rewardCan.add(useCan.can).sum()
|
val donationCan = useCan.rewardCan.add(useCan.can).sum()
|
||||||
return queryFactory
|
return queryFactory
|
||||||
@@ -38,6 +43,7 @@ class CreatorDonationRankingQueryRepository(private val queryFactory: JPAQueryFa
|
|||||||
.or(useCan.canUsage.eq(CanUsage.SPIN_ROULETTE))
|
.or(useCan.canUsage.eq(CanUsage.SPIN_ROULETTE))
|
||||||
.or(useCan.canUsage.eq(CanUsage.LIVE))
|
.or(useCan.canUsage.eq(CanUsage.LIVE))
|
||||||
)
|
)
|
||||||
|
.and(buildDateRangeCondition(startDate, endDate))
|
||||||
)
|
)
|
||||||
.offset(offset)
|
.offset(offset)
|
||||||
.limit(limit)
|
.limit(limit)
|
||||||
@@ -46,7 +52,11 @@ class CreatorDonationRankingQueryRepository(private val queryFactory: JPAQueryFa
|
|||||||
.fetch()
|
.fetch()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getMemberDonationRankingTotal(creatorId: Long): Int {
|
fun getMemberDonationRankingTotal(
|
||||||
|
creatorId: Long,
|
||||||
|
startDate: LocalDateTime? = null,
|
||||||
|
endDate: LocalDateTime? = null
|
||||||
|
): Int {
|
||||||
return queryFactory
|
return queryFactory
|
||||||
.select(member.id)
|
.select(member.id)
|
||||||
.from(useCanCalculate)
|
.from(useCanCalculate)
|
||||||
@@ -61,11 +71,32 @@ class CreatorDonationRankingQueryRepository(private val queryFactory: JPAQueryFa
|
|||||||
.or(useCan.canUsage.eq(CanUsage.SPIN_ROULETTE))
|
.or(useCan.canUsage.eq(CanUsage.SPIN_ROULETTE))
|
||||||
.or(useCan.canUsage.eq(CanUsage.LIVE))
|
.or(useCan.canUsage.eq(CanUsage.LIVE))
|
||||||
)
|
)
|
||||||
|
.and(buildDateRangeCondition(startDate, endDate))
|
||||||
)
|
)
|
||||||
.groupBy(member.id)
|
.groupBy(member.id)
|
||||||
.fetch()
|
.fetch()
|
||||||
.size
|
.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(
|
data class DonationRankingProjection @QueryProjection constructor(
|
||||||
|
|||||||
@@ -2,11 +2,13 @@ package kr.co.vividnext.sodalive.explorer.profile
|
|||||||
|
|
||||||
import kr.co.vividnext.sodalive.explorer.MemberDonationRankingListResponse
|
import kr.co.vividnext.sodalive.explorer.MemberDonationRankingListResponse
|
||||||
import kr.co.vividnext.sodalive.explorer.MemberDonationRankingResponse
|
import kr.co.vividnext.sodalive.explorer.MemberDonationRankingResponse
|
||||||
|
import kr.co.vividnext.sodalive.member.DonationRankingPeriod
|
||||||
import org.springframework.beans.factory.annotation.Value
|
import org.springframework.beans.factory.annotation.Value
|
||||||
import org.springframework.data.redis.core.RedisTemplate
|
import org.springframework.data.redis.core.RedisTemplate
|
||||||
import org.springframework.stereotype.Service
|
import org.springframework.stereotype.Service
|
||||||
import java.time.DayOfWeek
|
import java.time.DayOfWeek
|
||||||
import java.time.Duration
|
import java.time.Duration
|
||||||
|
import java.time.LocalDate
|
||||||
import java.time.LocalDateTime
|
import java.time.LocalDateTime
|
||||||
import java.time.LocalTime
|
import java.time.LocalTime
|
||||||
import java.time.temporal.ChronoUnit
|
import java.time.temporal.ChronoUnit
|
||||||
@@ -20,14 +22,22 @@ class CreatorDonationRankingService(
|
|||||||
@Value("\${cloud.aws.cloud-front.host}")
|
@Value("\${cloud.aws.cloud-front.host}")
|
||||||
private val imageHost: String
|
private val imageHost: String
|
||||||
) {
|
) {
|
||||||
fun getMemberDonationRankingTotal(creatorId: Long): Int {
|
fun getMemberDonationRankingTotal(
|
||||||
val cacheKey = "creator_donation_ranking_member_total_v2:$creatorId"
|
creatorId: Long,
|
||||||
|
period: DonationRankingPeriod = DonationRankingPeriod.CUMULATIVE
|
||||||
|
): Int {
|
||||||
|
val cacheKey = "creator_donation_ranking_member_total_v2:$creatorId:$period"
|
||||||
val cachedTotal = redisTemplate.opsForValue().get(cacheKey) as? Int
|
val cachedTotal = redisTemplate.opsForValue().get(cacheKey) as? Int
|
||||||
if (cachedTotal != null) {
|
if (cachedTotal != null) {
|
||||||
return cachedTotal
|
return cachedTotal
|
||||||
}
|
}
|
||||||
|
|
||||||
val total = repository.getMemberDonationRankingTotal(creatorId)
|
val weeklyDateRange = getWeeklyDateRange(period)
|
||||||
|
val total = if (weeklyDateRange == null) {
|
||||||
|
repository.getMemberDonationRankingTotal(creatorId)
|
||||||
|
} else {
|
||||||
|
repository.getMemberDonationRankingTotal(creatorId, weeklyDateRange.first, weeklyDateRange.second)
|
||||||
|
}
|
||||||
|
|
||||||
val now = LocalDateTime.now()
|
val now = LocalDateTime.now()
|
||||||
val nextMonday = now.with(TemporalAdjusters.next(DayOfWeek.MONDAY)).with(LocalTime.MIN)
|
val nextMonday = now.with(TemporalAdjusters.next(DayOfWeek.MONDAY)).with(LocalTime.MIN)
|
||||||
@@ -46,15 +56,27 @@ class CreatorDonationRankingService(
|
|||||||
creatorId: Long,
|
creatorId: Long,
|
||||||
offset: Long = 0,
|
offset: Long = 0,
|
||||||
limit: Long = 10,
|
limit: Long = 10,
|
||||||
withDonationCan: Boolean
|
withDonationCan: Boolean,
|
||||||
|
period: DonationRankingPeriod = DonationRankingPeriod.CUMULATIVE
|
||||||
): List<MemberDonationRankingResponse> {
|
): List<MemberDonationRankingResponse> {
|
||||||
val cacheKey = "creator_donation_ranking_v2:$creatorId:$offset:$limit:$withDonationCan"
|
val cacheKey = "creator_donation_ranking_v2:$creatorId:$period:$offset:$limit:$withDonationCan"
|
||||||
val cachedData = redisTemplate.opsForValue().get(cacheKey) as? MemberDonationRankingListResponse
|
val cachedData = redisTemplate.opsForValue().get(cacheKey) as? MemberDonationRankingListResponse
|
||||||
if (cachedData != null) {
|
if (cachedData != null) {
|
||||||
return cachedData.rankings
|
return cachedData.rankings
|
||||||
}
|
}
|
||||||
|
|
||||||
val memberDonationRanking = repository.getMemberDonationRanking(creatorId, offset, limit)
|
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 result = memberDonationRanking.map {
|
val result = memberDonationRanking.map {
|
||||||
MemberDonationRankingResponse(
|
MemberDonationRankingResponse(
|
||||||
@@ -77,4 +99,17 @@ class CreatorDonationRankingService(
|
|||||||
|
|
||||||
return result
|
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
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,5 +5,6 @@ data class GetRecentRoomInfoResponse(
|
|||||||
val notice: String,
|
val notice: String,
|
||||||
var coverImageUrl: String,
|
var coverImageUrl: String,
|
||||||
val coverImagePath: String,
|
val coverImagePath: String,
|
||||||
val numberOfPeople: Int
|
val numberOfPeople: Int,
|
||||||
|
val genderRestriction: GenderRestriction
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -89,11 +89,13 @@ class LiveRoomQueryRepositoryImpl(
|
|||||||
.and(liveRoom.isActive.isTrue)
|
.and(liveRoom.isActive.isTrue)
|
||||||
.and(liveRoom.member.isNotNull)
|
.and(liveRoom.member.isNotNull)
|
||||||
|
|
||||||
if (!isAdult) {
|
val isAdultRestricted = !isAdult || memberId == 17L || memberId == 16L
|
||||||
|
if (isAdultRestricted) {
|
||||||
where = where.and(liveRoom.isAdult.isFalse)
|
where = where.and(liveRoom.isAdult.isFalse)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isCreator && memberId != null) {
|
val hasMemberId = memberId != null
|
||||||
|
if (isCreator && hasMemberId) {
|
||||||
where = where.and(
|
where = where.and(
|
||||||
liveRoom.isAvailableJoinCreator.isTrue
|
liveRoom.isAvailableJoinCreator.isTrue
|
||||||
.or(liveRoom.member.id.eq(memberId))
|
.or(liveRoom.member.id.eq(memberId))
|
||||||
@@ -101,14 +103,15 @@ class LiveRoomQueryRepositoryImpl(
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (effectiveGender != null && effectiveGender != Gender.NONE) {
|
if (effectiveGender != null && effectiveGender != Gender.NONE) {
|
||||||
where = when (effectiveGender) {
|
val genderCondition = when (effectiveGender) {
|
||||||
Gender.MALE -> where.and(
|
Gender.MALE -> liveRoom.genderRestriction.`in`(GenderRestriction.ALL, GenderRestriction.MALE_ONLY)
|
||||||
liveRoom.genderRestriction.`in`(GenderRestriction.ALL, GenderRestriction.MALE_ONLY)
|
Gender.FEMALE -> liveRoom.genderRestriction.`in`(GenderRestriction.ALL, GenderRestriction.FEMALE_ONLY)
|
||||||
)
|
Gender.NONE -> liveRoom.genderRestriction.isNotNull
|
||||||
Gender.FEMALE -> where.and(
|
}
|
||||||
liveRoom.genderRestriction.`in`(GenderRestriction.ALL, GenderRestriction.FEMALE_ONLY)
|
where = if (hasMemberId) {
|
||||||
)
|
where.and(genderCondition.or(liveRoom.member.id.eq(memberId)))
|
||||||
Gender.NONE -> where
|
} else {
|
||||||
|
where.and(genderCondition)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -117,7 +120,7 @@ class LiveRoomQueryRepositoryImpl(
|
|||||||
.innerJoin(liveRoom.member, member)
|
.innerJoin(liveRoom.member, member)
|
||||||
.leftJoin(quarterLiveRankings).on(liveRoom.id.eq(quarterLiveRankings.roomId))
|
.leftJoin(quarterLiveRankings).on(liveRoom.id.eq(quarterLiveRankings.roomId))
|
||||||
|
|
||||||
if (memberId != null) {
|
if (hasMemberId) {
|
||||||
val blockMemberCondition = blockMember.member.id.eq(member.id)
|
val blockMemberCondition = blockMember.member.id.eq(member.id)
|
||||||
.and(blockMember.blockedMember.id.eq(memberId))
|
.and(blockMember.blockedMember.id.eq(memberId))
|
||||||
.and(blockMember.isActive.isTrue)
|
.and(blockMember.isActive.isTrue)
|
||||||
@@ -157,7 +160,8 @@ class LiveRoomQueryRepositoryImpl(
|
|||||||
.and(liveRoom.isActive.isTrue)
|
.and(liveRoom.isActive.isTrue)
|
||||||
.and(liveRoom.member.isNotNull)
|
.and(liveRoom.member.isNotNull)
|
||||||
|
|
||||||
if (!isAdult) {
|
val isAdultRestricted = !isAdult || memberId == 17L || memberId == 16L
|
||||||
|
if (isAdultRestricted) {
|
||||||
where = where.and(liveRoom.isAdult.isFalse)
|
where = where.and(liveRoom.isAdult.isFalse)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -169,14 +173,15 @@ class LiveRoomQueryRepositoryImpl(
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (effectiveGender != null && effectiveGender != Gender.NONE) {
|
if (effectiveGender != null && effectiveGender != Gender.NONE) {
|
||||||
where = when (effectiveGender) {
|
val genderCondition = when (effectiveGender) {
|
||||||
Gender.MALE -> where.and(
|
Gender.MALE -> liveRoom.genderRestriction.`in`(GenderRestriction.ALL, GenderRestriction.MALE_ONLY)
|
||||||
liveRoom.genderRestriction.`in`(GenderRestriction.ALL, GenderRestriction.MALE_ONLY)
|
Gender.FEMALE -> liveRoom.genderRestriction.`in`(GenderRestriction.ALL, GenderRestriction.FEMALE_ONLY)
|
||||||
)
|
Gender.NONE -> liveRoom.genderRestriction.isNotNull
|
||||||
Gender.FEMALE -> where.and(
|
}
|
||||||
liveRoom.genderRestriction.`in`(GenderRestriction.ALL, GenderRestriction.FEMALE_ONLY)
|
where = if (memberId != null) {
|
||||||
)
|
where.and(genderCondition.or(liveRoom.member.id.eq(memberId)))
|
||||||
Gender.NONE -> where
|
} else {
|
||||||
|
where.and(genderCondition)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -221,7 +226,8 @@ class LiveRoomQueryRepositoryImpl(
|
|||||||
.and(liveRoom.isActive.isTrue)
|
.and(liveRoom.isActive.isTrue)
|
||||||
.and(liveRoom.member.isNotNull)
|
.and(liveRoom.member.isNotNull)
|
||||||
|
|
||||||
if (!isAdult) {
|
val isAdultRestricted = !isAdult || memberId == 17L || memberId == 16L
|
||||||
|
if (isAdultRestricted) {
|
||||||
where = where.and(liveRoom.isAdult.isFalse)
|
where = where.and(liveRoom.isAdult.isFalse)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -233,14 +239,15 @@ class LiveRoomQueryRepositoryImpl(
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (effectiveGender != null && effectiveGender != Gender.NONE) {
|
if (effectiveGender != null && effectiveGender != Gender.NONE) {
|
||||||
where = when (effectiveGender) {
|
val genderCondition = when (effectiveGender) {
|
||||||
Gender.MALE -> where.and(
|
Gender.MALE -> liveRoom.genderRestriction.`in`(GenderRestriction.ALL, GenderRestriction.MALE_ONLY)
|
||||||
liveRoom.genderRestriction.`in`(GenderRestriction.ALL, GenderRestriction.MALE_ONLY)
|
Gender.FEMALE -> liveRoom.genderRestriction.`in`(GenderRestriction.ALL, GenderRestriction.FEMALE_ONLY)
|
||||||
)
|
Gender.NONE -> liveRoom.genderRestriction.isNotNull
|
||||||
Gender.FEMALE -> where.and(
|
}
|
||||||
liveRoom.genderRestriction.`in`(GenderRestriction.ALL, GenderRestriction.FEMALE_ONLY)
|
where = if (memberId != null) {
|
||||||
)
|
where.and(genderCondition.or(liveRoom.member.id.eq(memberId)))
|
||||||
Gender.NONE -> where
|
} else {
|
||||||
|
where.and(genderCondition)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -308,7 +315,8 @@ class LiveRoomQueryRepositoryImpl(
|
|||||||
liveRoom.notice,
|
liveRoom.notice,
|
||||||
liveRoom.coverImage.prepend("/").prepend(cloudFrontHost),
|
liveRoom.coverImage.prepend("/").prepend(cloudFrontHost),
|
||||||
liveRoom.coverImage,
|
liveRoom.coverImage,
|
||||||
liveRoom.numberOfPeople
|
liveRoom.numberOfPeople,
|
||||||
|
liveRoom.genderRestriction
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
.from(liveRoom)
|
.from(liveRoom)
|
||||||
|
|||||||
@@ -206,7 +206,7 @@ class LiveRoomService(
|
|||||||
timezone,
|
timezone,
|
||||||
memberId = member?.id,
|
memberId = member?.id,
|
||||||
isCreator = member?.role == MemberRole.CREATOR,
|
isCreator = member?.role == MemberRole.CREATOR,
|
||||||
isAdult = member?.auth != null && isAdultContentVisible,
|
isAdult = true,
|
||||||
effectiveGender = effectiveGender
|
effectiveGender = effectiveGender
|
||||||
)
|
)
|
||||||
} else if (dateString != null) {
|
} else if (dateString != null) {
|
||||||
@@ -522,10 +522,9 @@ class LiveRoomService(
|
|||||||
title = room.title,
|
title = room.title,
|
||||||
notice = room.notice,
|
notice = room.notice,
|
||||||
price = room.price,
|
price = room.price,
|
||||||
tags = room.tags.asSequence()
|
tags = room.tags
|
||||||
.filter { it.tag.isActive }
|
.filter { it.tag.isActive }
|
||||||
.map { it.tag.tag }
|
.map { it.tag.tag }
|
||||||
.toList()
|
|
||||||
.let { tags -> applyLanguageTagToRoomTags(room.member?.id, tags, languageTagByMemberId) },
|
.let { tags -> applyLanguageTagToRoomTags(room.member?.id, tags, languageTagByMemberId) },
|
||||||
numberOfParticipantsTotal = room.numberOfPeople,
|
numberOfParticipantsTotal = room.numberOfPeople,
|
||||||
numberOfParticipants = 0,
|
numberOfParticipants = 0,
|
||||||
@@ -929,6 +928,13 @@ class LiveRoomService(
|
|||||||
expireTimestamp.toInt()
|
expireTimestamp.toInt()
|
||||||
)
|
)
|
||||||
|
|
||||||
|
val v2vWorkerRtmToken = rtmTokenBuilder.buildToken(
|
||||||
|
agoraAppId,
|
||||||
|
agoraAppCertificate,
|
||||||
|
"v2v-agent-${member.id!!}",
|
||||||
|
expireTimestamp.toInt()
|
||||||
|
)
|
||||||
|
|
||||||
val isFollowing = explorerQueryRepository
|
val isFollowing = explorerQueryRepository
|
||||||
.getNotificationUserIds(room.member!!.id!!)
|
.getNotificationUserIds(room.member!!.id!!)
|
||||||
.contains(member.id)
|
.contains(member.id)
|
||||||
@@ -956,6 +962,12 @@ class LiveRoomService(
|
|||||||
}
|
}
|
||||||
|
|
||||||
val menuPan = menuService.getLiveMenu(creatorId = room.member!!.id!!)
|
val menuPan = menuService.getLiveMenu(creatorId = room.member!!.id!!)
|
||||||
|
val languageTagByMemberId = buildLanguageTagMap(listOfNotNull(room.member?.id))
|
||||||
|
|
||||||
|
val tags = room.tags
|
||||||
|
.filter { it.tag.isActive }
|
||||||
|
.map { it.tag.tag }
|
||||||
|
.let { tags -> applyLanguageTagToRoomTags(room.member?.id, tags, languageTagByMemberId) }
|
||||||
|
|
||||||
return GetRoomInfoResponse(
|
return GetRoomInfoResponse(
|
||||||
roomId = roomId,
|
roomId = roomId,
|
||||||
@@ -977,6 +989,7 @@ class LiveRoomService(
|
|||||||
channelName = room.channelName!!,
|
channelName = room.channelName!!,
|
||||||
rtcToken = rtcToken,
|
rtcToken = rtcToken,
|
||||||
rtmToken = rtmToken,
|
rtmToken = rtmToken,
|
||||||
|
v2vWorkerRtmToken = v2vWorkerRtmToken,
|
||||||
creatorId = room.member!!.id!!,
|
creatorId = room.member!!.id!!,
|
||||||
creatorNickname = room.member!!.nickname,
|
creatorNickname = room.member!!.nickname,
|
||||||
creatorProfileUrl = if (room.member!!.profileImage != null) {
|
creatorProfileUrl = if (room.member!!.profileImage != null) {
|
||||||
@@ -993,6 +1006,7 @@ class LiveRoomService(
|
|||||||
managerList = roomInfo.managerList,
|
managerList = roomInfo.managerList,
|
||||||
donationRankingTop3UserIds = donationRankingTop3UserIds,
|
donationRankingTop3UserIds = donationRankingTop3UserIds,
|
||||||
menuPan = menuPan?.menu ?: "",
|
menuPan = menuPan?.menu ?: "",
|
||||||
|
tags = tags,
|
||||||
isPrivateRoom = room.type == LiveRoomType.PRIVATE,
|
isPrivateRoom = room.type == LiveRoomType.PRIVATE,
|
||||||
password = room.password,
|
password = room.password,
|
||||||
isActiveRoulette = isActiveRoulette
|
isActiveRoulette = isActiveRoulette
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ data class GetRoomInfoResponse(
|
|||||||
val channelName: String,
|
val channelName: String,
|
||||||
val rtcToken: String,
|
val rtcToken: String,
|
||||||
val rtmToken: String,
|
val rtmToken: String,
|
||||||
|
val v2vWorkerRtmToken: String,
|
||||||
val creatorId: Long,
|
val creatorId: Long,
|
||||||
val creatorNickname: String,
|
val creatorNickname: String,
|
||||||
val creatorProfileUrl: String,
|
val creatorProfileUrl: String,
|
||||||
@@ -20,6 +21,7 @@ data class GetRoomInfoResponse(
|
|||||||
val managerList: List<LiveRoomMember>,
|
val managerList: List<LiveRoomMember>,
|
||||||
val donationRankingTop3UserIds: List<Long>,
|
val donationRankingTop3UserIds: List<Long>,
|
||||||
val menuPan: String,
|
val menuPan: String,
|
||||||
|
val tags: List<String>,
|
||||||
val isPrivateRoom: Boolean = false,
|
val isPrivateRoom: Boolean = false,
|
||||||
val password: String? = null,
|
val password: String? = null,
|
||||||
val isActiveRoulette: Boolean = false
|
val isActiveRoulette: Boolean = false
|
||||||
|
|||||||
@@ -47,6 +47,9 @@ data class Member(
|
|||||||
|
|
||||||
var isVisibleDonationRank: Boolean = true,
|
var isVisibleDonationRank: Boolean = true,
|
||||||
|
|
||||||
|
@Enumerated(value = EnumType.STRING)
|
||||||
|
var donationRankingPeriod: DonationRankingPeriod? = DonationRankingPeriod.CUMULATIVE,
|
||||||
|
|
||||||
var isActive: Boolean = true,
|
var isActive: Boolean = true,
|
||||||
|
|
||||||
var container: String = "web",
|
var container: String = "web",
|
||||||
@@ -178,3 +181,7 @@ enum class MemberRole {
|
|||||||
enum class MemberProvider {
|
enum class MemberProvider {
|
||||||
EMAIL, KAKAO, GOOGLE, APPLE, LINE
|
EMAIL, KAKAO, GOOGLE, APPLE, LINE
|
||||||
}
|
}
|
||||||
|
|
||||||
|
enum class DonationRankingPeriod {
|
||||||
|
WEEKLY, CUMULATIVE
|
||||||
|
}
|
||||||
|
|||||||
@@ -130,7 +130,7 @@ class MemberService(
|
|||||||
duplicateCheckEmail(request.email)
|
duplicateCheckEmail(request.email)
|
||||||
validatePassword(request.password)
|
validatePassword(request.password)
|
||||||
|
|
||||||
val nickname = nicknameGenerateService.generateUniqueNickname()
|
val nickname = nicknameGenerateService.generateUniqueNickname(langContext.lang)
|
||||||
val member = Member(
|
val member = Member(
|
||||||
email = request.email,
|
email = request.email,
|
||||||
password = passwordEncoder.encode(request.password),
|
password = passwordEncoder.encode(request.password),
|
||||||
@@ -726,6 +726,10 @@ class MemberService(
|
|||||||
member.isVisibleDonationRank = profileUpdateRequest.isVisibleDonationRank
|
member.isVisibleDonationRank = profileUpdateRequest.isVisibleDonationRank
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (profileUpdateRequest.donationRankingPeriod != null) {
|
||||||
|
member.donationRankingPeriod = profileUpdateRequest.donationRankingPeriod
|
||||||
|
}
|
||||||
|
|
||||||
return ProfileResponse(member, cloudFrontHost, profileUpdateRequest.container)
|
return ProfileResponse(member, cloudFrontHost, profileUpdateRequest.container)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -846,7 +850,7 @@ class MemberService(
|
|||||||
val email = googleUserInfo.email
|
val email = googleUserInfo.email
|
||||||
checkEmail(email)
|
checkEmail(email)
|
||||||
|
|
||||||
val nickname = nicknameGenerateService.generateUniqueNickname()
|
val nickname = nicknameGenerateService.generateUniqueNickname(langContext.lang)
|
||||||
val member = Member(
|
val member = Member(
|
||||||
googleId = googleUserInfo.sub,
|
googleId = googleUserInfo.sub,
|
||||||
email = email,
|
email = email,
|
||||||
@@ -903,7 +907,7 @@ class MemberService(
|
|||||||
val email = kakaoUserInfo.email
|
val email = kakaoUserInfo.email
|
||||||
checkEmail(email)
|
checkEmail(email)
|
||||||
|
|
||||||
val nickname = nicknameGenerateService.generateUniqueNickname()
|
val nickname = nicknameGenerateService.generateUniqueNickname(langContext.lang)
|
||||||
val member = Member(
|
val member = Member(
|
||||||
kakaoId = kakaoUserInfo.id,
|
kakaoId = kakaoUserInfo.id,
|
||||||
email = email,
|
email = email,
|
||||||
@@ -960,7 +964,7 @@ class MemberService(
|
|||||||
val email = appleUserInfo.email
|
val email = appleUserInfo.email
|
||||||
checkEmail(email)
|
checkEmail(email)
|
||||||
|
|
||||||
val nickname = nicknameGenerateService.generateUniqueNickname()
|
val nickname = nicknameGenerateService.generateUniqueNickname(langContext.lang)
|
||||||
val member = Member(
|
val member = Member(
|
||||||
appleId = appleUserInfo.sub,
|
appleId = appleUserInfo.sub,
|
||||||
email = email,
|
email = email,
|
||||||
@@ -1017,7 +1021,7 @@ class MemberService(
|
|||||||
val email = lineUserInfo.email
|
val email = lineUserInfo.email
|
||||||
checkEmail(email)
|
checkEmail(email)
|
||||||
|
|
||||||
val nickname = nicknameGenerateService.generateUniqueNickname()
|
val nickname = nicknameGenerateService.generateUniqueNickname(langContext.lang)
|
||||||
val member = Member(
|
val member = Member(
|
||||||
lineId = lineUserInfo.sub,
|
lineId = lineUserInfo.sub,
|
||||||
email = email,
|
email = email,
|
||||||
|
|||||||
@@ -14,5 +14,6 @@ data class ProfileUpdateRequest(
|
|||||||
val websiteUrl: String? = null,
|
val websiteUrl: String? = null,
|
||||||
val blogUrl: String? = null,
|
val blogUrl: String? = null,
|
||||||
val isVisibleDonationRank: Boolean? = null,
|
val isVisibleDonationRank: Boolean? = null,
|
||||||
|
val donationRankingPeriod: DonationRankingPeriod? = null,
|
||||||
val container: String
|
val container: String
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package kr.co.vividnext.sodalive.member.nickname
|
package kr.co.vividnext.sodalive.member.nickname
|
||||||
|
|
||||||
import kr.co.vividnext.sodalive.common.SodaException
|
import kr.co.vividnext.sodalive.common.SodaException
|
||||||
|
import kr.co.vividnext.sodalive.i18n.Lang
|
||||||
import kr.co.vividnext.sodalive.member.MemberRepository
|
import kr.co.vividnext.sodalive.member.MemberRepository
|
||||||
import org.springframework.stereotype.Service
|
import org.springframework.stereotype.Service
|
||||||
import kotlin.random.Random
|
import kotlin.random.Random
|
||||||
@@ -8,59 +9,121 @@ import kotlin.random.Random
|
|||||||
@Service
|
@Service
|
||||||
class NicknameGenerateService(private val repository: MemberRepository) {
|
class NicknameGenerateService(private val repository: MemberRepository) {
|
||||||
private val adjectives = listOf(
|
private val adjectives = listOf(
|
||||||
"따뜻한", "은은한", "고요한", "푸른", "맑은", "강한", "평온한", "깊은", "고독한",
|
"활기찬", "명랑한", "씩씩한", "용감한", "지혜론", "슬기론", "넉넉한", "든든한", "알찬듯",
|
||||||
"거친", "빛바랜", "차가운", "꿈꾸는", "숨겨진", "고귀한", "깨어난", "끝없는", "청명한",
|
"빠른듯", "느긋한", "당당한", "솔직한", "진실한", "겸손한", "성실한", "꼼꼼한", "야무진",
|
||||||
"어두운", "희미한", "선명한", "눈부신", "불타는", "차분한", "아련한", "선선한", "상쾌한", "온화한",
|
"재빠른", "영리한", "총명한", "현명한", "착실한", "올곧은", "바른듯", "곧은듯", "힘찬듯", "굳센듯",
|
||||||
"포근한", "황금빛", "청량한", "시원한", "서늘한", "우아한", "단단한", "투명한", "가벼운", "조용한",
|
"의젓한", "점잖은", "듬직한", "너그런", "관대한", "인자한", "자애론", "헌신적", "열정적", "적극적",
|
||||||
"화려한", "찬란한", "순수한", "흐릿한", "고결한", "달콤한", "무한한", "아득한", "화사한", "평안한",
|
"능숙한", "탁월한", "뛰어난", "출중한", "비범한", "특별한", "독특한", "개성적", "창의적", "혁신적",
|
||||||
"웅장한", "황홀한", "빛나는", "쓸쓸한", "청순한", "흐르는", "미묘한", "그윽한", "몽롱한", "청아한",
|
"진취적", "도전적", "패기찬", "호쾌한", "시원찬", "통쾌한", "유능한", "민첩한", "기민한", "재치론",
|
||||||
"섬세한", "촉촉한", "강렬한", "싱싱한", "부드런", "아늑한", "매운듯", "고운듯", "느린듯", "밝은듯",
|
"센스찬", "감각적", "세련된", "품격찬", "격조찬", "기품찬", "위풍찬", "늠름한", "씩씩찬", "호탕한",
|
||||||
"짧은듯", "달큰한", "깊은듯", "기쁜듯", "쌀쌀한", "무거운", "연한듯", "편안한", "깨끗한", "말끔한",
|
"대범한", "거뜬한", "가뿐한", "홀가분", "산뜻찬", "깔끔찬", "정갈한", "단정한", "반짝찬", "영롱한",
|
||||||
"뽀얀듯", "푸르른", "붉은듯", "노오란", "무딘듯", "살짝한", "상큼한", "시큰한", "고소한", "나긋한",
|
"찬란찬", "눈부찬", "환한듯", "밝은찬", "빛깔찬", "색다른", "새로운", "신선찬", "풋풋한", "싱그런",
|
||||||
"화끈한", "중후한", "정겨운", "날렵한", "기묘한", "참신한", "담백한", "퉁명한", "꾸밈없", "소박한",
|
"생기찬", "발랄한", "경쾌한", "리듬찬", "율동적", "역동적", "활발한", "생동찬", "약동찬", "힘있는",
|
||||||
"뾰족한", "무심한", "도도한", "따끔한", "무난한", "단호한", "냉정한", "따스한", "유연한", "묵직한",
|
"건장한", "튼튼한", "건강한", "탄탄한", "단련된", "숙련된", "노련한", "원숙한", "성숙한", "완숙한",
|
||||||
"나른한", "몽환적", "정돈된", "쾌활한", "날카론", "묘한듯", "예쁜듯", "뽀얗게", "다정한", "푸근한",
|
"정확한", "치밀한", "정밀한", "철저한", "완벽찬", "흠없는", "나무랄", "빈틈없", "알뜰한", "꾸준한",
|
||||||
"애틋한", "낭만적", "건조한", "훈훈한", "섹시한", "정적인", "유쾌한", "멍한듯", "혼란한", "상냥한",
|
"한결찬", "변함없", "굳건한", "확고한", "견고한", "탄탄찬", "안정적", "평화론", "온유한", "자비론",
|
||||||
"뚜렷한", "신비한", "허전한", "그리운", "들뜬듯", "절실한", "반듯한", "반가운", "새하얀", "흐린듯",
|
"배려찬", "사려찬", "깊숙한", "심오한", "오묘한", "현묘한", "신묘한", "경이론", "놀라운", "대단한",
|
||||||
"엄숙한", "깊잖은", "산뜻한", "낯선듯"
|
"훌륭한", "멋스런", "근사한", "기특한"
|
||||||
)
|
)
|
||||||
|
|
||||||
private val nouns = listOf(
|
private val nouns = listOf(
|
||||||
"소리", "울림", "공명", "음색", "감성", "리듬", "바람", "늑대", "태양", "대지",
|
"다람쥐", "청설모", "두루미", "기러기", "올빼미", "부엉이", "딱따구", "꾀꼬리", "직박구", "동박새",
|
||||||
"강", "하늘", "불꽃", "별빛", "나무", "산", "달빛", "폭풍", "눈", "밤",
|
"참새", "종달새", "제비", "뻐꾸기", "앵무새", "공작새", "원앙", "두더지", "고슴도", "족제비",
|
||||||
"노을", "물결", "노래", "파도", "구름", "사슴", "신비", "영혼", "선율", "평원",
|
"오소리", "수리부", "해오라", "갈매기", "펭귄", "코알라", "알파카", "카멜레", "이구아", "플라밍",
|
||||||
"빛", "고래", "모래", "사자", "표범", "여우", "곰", "수달", "판다", "들소",
|
"돌고래", "해달", "라쿤", "미어캣", "친칠라", "햄스터", "기니피", "토끼", "강아지", "고양이",
|
||||||
"까치", "매", "솔개", "물총새", "철새", "황새", "은어", "붕어", "산양", "담비",
|
"망아지", "송아지", "병아리", "올챙이", "개구리", "도롱뇽", "거북이", "앵무", "카나리", "비둘기",
|
||||||
"설표", "물개", "자라", "나비", "노루", "해마", "백조", "청어", "호수", "샘물",
|
"참매", "독수리", "콘도르", "벌새", "홍학", "타조", "키위새", "투칸", "앵콩이", "물까치",
|
||||||
"쿼카", "상어", "무드", "나노", "루프", "네온", "모아", "아토", "플로", "루미",
|
"루비", "사파이", "에메랄", "자수정", "진주", "산호", "호박", "비취", "오팔", "토파즈",
|
||||||
"도트", "비트", "토브", "온기", "클리", "위드", "제로", "베이", "미오", "시그",
|
"다이아", "크리스", "아쿠아", "코발트", "인디고", "라벤더", "마젠타", "터콰이", "세룰리", "버밀리",
|
||||||
"쿠나", "오로", "폴라", "바움", "포잇", "누아", "오브", "파인", "조이", "아뜰",
|
"카푸치", "에스프", "아메리", "마키아", "바닐라", "캐러멜", "시나몬", "민트", "자스민", "캐모마",
|
||||||
"티노", "소마", "하루", "밀크", "아린", "토로", "벨로", "위시", "뮤즈", "노블",
|
"히비스", "라일락", "프리지", "튤립", "수선화", "동백", "매화", "목련", "벚꽃", "진달래",
|
||||||
"카노", "미카", "하라", "엘로", "피오", "라임", "노이", "루다", "이브", "마리",
|
"철쭉", "개나리", "무궁화", "해바라", "코스모", "달리아", "작약", "모란", "연꽃", "수련",
|
||||||
"블루", "시온", "레아", "도르", "하노", "네리", "키노", "쿠키", "라노", "수이",
|
"클로버", "민들레", "제비꽃", "은방울", "안개꽃", "라넌큘", "아네모", "델피니", "글라디", "프로테",
|
||||||
"우노", "파루", "크리", "포유", "코코", "아라", "토리", "누리", "보노", "페어",
|
"유칼립", "로즈마", "바질", "타임", "오레가", "세이지", "딜", "파슬리", "고수", "루꼴라",
|
||||||
"리아", "모리", "세리", "리브", "헤이", "모카", "아이", "르네", "이로", "미노",
|
"아보카", "블루베", "라즈베", "크랜베", "아사이", "망고", "파파야", "리치", "패션후", "구아바",
|
||||||
"다라", "노바", "디노", "오미", "카라", "니아", "루아", "네오", "하이", "레인",
|
"석류", "무화과", "살구", "자두", "체리", "복숭아", "포도", "감귤", "유자", "한라봉",
|
||||||
"피카", "유카", "제니", "이든", "라비", "아벨", "솔라", "쿠로", "시라", "리코"
|
"천혜향", "레드향", "금귤", "모과", "비파", "대추", "밤", "호두", "잣", "은행"
|
||||||
)
|
)
|
||||||
|
|
||||||
private fun generateRandomNickname(): String {
|
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)
|
||||||
val formatType = Random.nextInt(3)
|
val formatType = Random.nextInt(3)
|
||||||
return when (formatType) {
|
return when (formatType) {
|
||||||
0 -> "${adjectives.random()}${nouns.random()}"
|
0 -> "${adj.random()}${noun.random()}"
|
||||||
1 -> "${nouns.random()}의${nouns.random()}"
|
1 -> "${noun.random()}${particle}${noun.random()}"
|
||||||
else -> "${adjectives.random()}${nouns.random()}의${nouns.random()}"
|
else -> "${adj.random()}${noun.random()}${particle}${noun.random()}"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun generateNonConflictingNickname(usedNicknames: Set<String>): String {
|
private fun generateNonConflictingNickname(usedNicknames: Set<String>, lang: Lang): String {
|
||||||
val usedNicknameSet = HashSet(usedNicknames) // 해시셋으로 변환 (O(1) 조회 가능)
|
val usedNicknameSet = HashSet(usedNicknames)
|
||||||
val availableNumbers = (1000..9999).shuffled()
|
val availableNumbers = (1000..9999).shuffled()
|
||||||
|
val adj = getAdjectives(lang)
|
||||||
|
val noun = getNouns(lang)
|
||||||
|
|
||||||
for (num in availableNumbers) { // 숫자를 먼저 결정 (무작위)
|
for (num in availableNumbers) {
|
||||||
for (adj in adjectives.shuffled()) { // 형용사 순서 랜덤화
|
for (a in adj.shuffled()) {
|
||||||
for (noun in nouns.shuffled()) { // 명사 순서 랜덤화
|
for (n in noun.shuffled()) {
|
||||||
val candidate = "$adj$noun$num"
|
val candidate = "$a$n$num"
|
||||||
if (!usedNicknameSet.contains(candidate)) {
|
if (!usedNicknameSet.contains(candidate)) {
|
||||||
return candidate
|
return candidate
|
||||||
}
|
}
|
||||||
@@ -70,13 +133,13 @@ class NicknameGenerateService(private val repository: MemberRepository) {
|
|||||||
throw SodaException(messageKey = "member.signup.failed_retry")
|
throw SodaException(messageKey = "member.signup.failed_retry")
|
||||||
}
|
}
|
||||||
|
|
||||||
fun generateUniqueNickname(): String {
|
fun generateUniqueNickname(lang: Lang = Lang.KO): String {
|
||||||
repeat(5) {
|
repeat(5) {
|
||||||
val candidates = (1..10).map { generateRandomNickname() }
|
val candidates = (1..10).map { generateRandomNickname(lang) }
|
||||||
val available = candidates.firstOrNull { !repository.existsByNickname(it) }
|
val available = candidates.firstOrNull { !repository.existsByNickname(it) }
|
||||||
if (available != null) return available
|
if (available != null) return available
|
||||||
}
|
}
|
||||||
|
|
||||||
return generateNonConflictingNickname(repository.findNicknamesWithPrefix("").toSet())
|
return generateNonConflictingNickname(repository.findNicknamesWithPrefix("").toSet(), lang)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user