Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 8 additions & 4 deletions .github/workflows/discord.yml
Original file line number Diff line number Diff line change
Expand Up @@ -28,23 +28,27 @@ jobs:

echo "$COMMITS_JSON" > commits.json

# 커밋 리스트 메시지 생성 (3개 제한, 링크 포함)
COMMITS_MSG=$(echo "$COMMITS_JSON" | jq -r '[.[] | {sha: .sha[:7], url: .html_url, message: .commit.message}] | .[:3] | map("- [\(.sha)](\(.url)) \(.message)") | join("\n")')
COMMITS_COUNT=$(echo "$COMMITS_JSON" | jq 'length')
COMMITS_MSG=$(echo "$COMMITS_JSON" | jq -r '[.[] | {sha: .sha[:7], url: .html_url, message: .commit.message}] | map("- [\(.sha)](\(.url)) \(.message)") | join("\n")')

echo "COMMITS_COUNT=$COMMITS_COUNT" >> $GITHUB_ENV
echo "COMMITS_MSG<<EOF" >> $GITHUB_ENV
echo "$COMMITS_MSG" >> $GITHUB_ENV
echo "EOF" >> $GITHUB_ENV


- name: Send Discord notification
run: |
USERNAME="${{ github.actor }}"
NOW=$(date '+%Y-%m-%d %H:%M')

CONTENT="🚀 **배포 완료** (main 브랜치)\n\n배포자: @$USERNAME\n커밋 ${#COMMITS_MSG[@]}개 포함\n배포 시간: $NOW\n\n커밋 내역:\n$COMMITS_MSG"
# JSON 문자열을 jq로 안전하게 생성
JSON_PAYLOAD=$(jq -n --arg content "🚀 **배포 완료** (main 브랜치)\n\n배포자: @$USERNAME\n커밋 $COMMITS_COUNT개 포함\n배포 시간: $NOW\n\n커밋 내역:\n$COMMITS_MSG" '{content: $content}')


curl -H "Content-Type: application/json" \
-X POST \
-d "{\"content\": \"$CONTENT\"}" \
-d "$JSON_PAYLOAD" \
"$DISCORD_WEBHOOK_URL"
env:
COMMITS_MSG: ${{ env.COMMITS_MSG }}
16 changes: 16 additions & 0 deletions src/main/kotlin/busanVibe/busan/domain/common/dto/InfoType.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package busanVibe.busan.domain.common.dto

enum class InfoType(
val kr: String,
val en: String,
) {
ALL("전체", "ALL"),
// 명소
SIGHT("명소", "SIGHT"),
RESTAURANT("식당", "RESTAURANT"),
CAFE("카페", "CAFE"),
// 축제
FESTIVAL("축제", "FESTIVAL"),
;

}
Original file line number Diff line number Diff line change
Expand Up @@ -23,5 +23,14 @@ interface FestivalRepository: JpaRepository<Festival, Long> {
""")
fun findByIdWithLikesAndImages(@Param("id") id: Long): Festival?

@Query(
"""
SELECT f FROM Festival f
LEFT JOIN FETCH f.festivalImages
LEFT JOIN FETCH f.festivalLikes fl
LEFT JOIN FETCH fl.user
"""
)
fun findAllWithFetch(): List<Festival>

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package busanVibe.busan.domain.home.controller

import busanVibe.busan.domain.home.dto.HomeResponseDTO
import busanVibe.busan.domain.home.service.HomeQueryService
import busanVibe.busan.global.apiPayload.exception.ApiResponse
import io.swagger.v3.oas.annotations.Operation
import io.swagger.v3.oas.annotations.tags.Tag
import org.springframework.web.bind.annotation.GetMapping
import org.springframework.web.bind.annotation.RequestMapping
import org.springframework.web.bind.annotation.RestController

@Tag(name = "홈화면 API")
@RestController
@RequestMapping("/api/home")
class HomeController(
private val homeQueryService: HomeQueryService
) {

@GetMapping
@Operation(summary = "홈화면 정보 조회 API", description = "지금 붐비는 곳과 추천 명소를 각각 5개씩 반환합니다.")
fun getHomeInfo(): ApiResponse<HomeResponseDTO.HomeResultDto> {

val homeInfo = homeQueryService.getHomeInfo()
return ApiResponse.onSuccess(homeInfo)
}

}
41 changes: 41 additions & 0 deletions src/main/kotlin/busanVibe/busan/domain/home/dto/HomeResponseDTO.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
package busanVibe.busan.domain.home.dto

import com.fasterxml.jackson.annotation.JsonProperty
import com.fasterxml.jackson.databind.PropertyNamingStrategies
import com.fasterxml.jackson.databind.annotation.JsonNaming

class HomeResponseDTO {

@JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy::class)
data class HomeResultDto(
val mostCrowded: List<MostCongestion>,
val recommendPlace: List<RecommendPlace>
)

@JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy::class)
data class MostCongestion(
val placeId: Long?,
val name: String,
val latitude: Double,
val longitude: Double,
val type: String,
val image: String?,
val congestionLevel: Int,
val region: String
)

@JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy::class)
data class RecommendPlace(
val placeId: Long?,
val name: String,
val congestionLevel: Int,
val type: String,
val image: String?,
val latitude: Double,
val longitude: Double,
val region: String,
@get:JsonProperty("is_liked")
val isLiked: Boolean
)

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
package busanVibe.busan.domain.home.service

import busanVibe.busan.domain.home.dto.HomeResponseDTO
import busanVibe.busan.domain.place.repository.PlaceRepository
import busanVibe.busan.domain.place.util.PlaceRedisUtil
import busanVibe.busan.domain.user.service.login.AuthService
import org.springframework.stereotype.Service
import org.springframework.transaction.annotation.Transactional

@Service
class HomeQueryService(
private val placeRepository: PlaceRepository,
private val placeRedisUtil: PlaceRedisUtil
) {

@Transactional(readOnly = true)
fun getHomeInfo(): HomeResponseDTO.HomeResultDto{

// 조회 메서드 호출
val mostCongestions:List<HomeResponseDTO.MostCongestion> = getMostCongestions()
val recommendPlaces:List<HomeResponseDTO.RecommendPlace> = getRecommendPlaces()

// 반환
return HomeResponseDTO.HomeResultDto(
mostCongestions, recommendPlaces
)
}

// 가장 붐비는 곳 조회 하여 List<DTO> 반환 - 5개
private fun getMostCongestions(): List<HomeResponseDTO.MostCongestion>{

val places = placeRepository.findAllWithImages()
val placesWithCongestion = places.mapNotNull { place ->
val congestion = placeRedisUtil.getRedisCongestion(place.id)
if (congestion != null) {
place to congestion
} else {
null
}
}

val top5 = placesWithCongestion.sortedByDescending { it.second }.take(5)

return top5.map { (place, congestion) ->
HomeResponseDTO.MostCongestion(
placeId = place.id,
name = place.name,
latitude = place.latitude.toDouble(),
longitude = place.longitude.toDouble(),
type = place.type.korean,
image = place.placeImages.firstOrNull()?.imgUrl,
congestionLevel = congestion,
region = place.address
)
}

}

// 추천 명소 조회하여 List<DTO> 반환 - 5개
private fun getRecommendPlaces(): List<HomeResponseDTO.RecommendPlace> {
val currentUser = AuthService().getCurrentUser()
val places = placeRepository.findAllWithFetch()

return places.take(5).map { place ->

val congestion = placeRedisUtil.getRedisCongestion(place.id)

HomeResponseDTO.RecommendPlace(
placeId = place.id,
name = place.name,
congestionLevel = congestion,
type = place.type.korean,
image = place.placeImages.firstOrNull()?.imgUrl,
latitude = place.latitude.toDouble(),
longitude = place.longitude.toDouble(),
region = place.address,
isLiked = place.placeLikes.any { it.user == currentUser }
)
}
}


}
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,9 @@ class PlaceCongestionController (
fun map(
@RequestParam("type", required = false, defaultValue = "ALL") type: PlaceType,
@RequestParam("latitude")latitude: Double,
@RequestParam("longitude")longtitude: Double): ApiResponse<PlaceMapResponseDTO.MapListDto>{
@RequestParam("longitude")longitude: Double): ApiResponse<PlaceMapResponseDTO.MapListDto>{

val places = placeCongestionQueryService.getMap(type, latitude, longtitude)
val places = placeCongestionQueryService.getMap(type, latitude, longitude)
return ApiResponse.onSuccess(places);
}

Expand All @@ -47,9 +47,10 @@ class PlaceCongestionController (
}

@GetMapping("/place/{placeId}/distribution")
@Operation(summary = "명소 이용객 분포 조회")
@Operation(summary = "명소 이용객 분포 조회", description = "각 분포 항목의 백분율 정보를 반환합니다.")
fun placeUsesDistribution(@PathVariable("placeId") placeId: Long): ApiResponse<PlaceMapResponseDTO.PlaceUserDistributionDto>?{
return null;
val distribution = placeCongestionQueryService.getDistribution(placeId)
return ApiResponse.onSuccess(distribution)
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import busanVibe.busan.domain.place.domain.Place
import busanVibe.busan.domain.place.domain.PlaceImage
import busanVibe.busan.domain.place.domain.PlaceLike
import busanVibe.busan.domain.place.dto.PlaceResponseDTO
import busanVibe.busan.domain.place.service.PlaceRedisUtil
import busanVibe.busan.domain.place.util.PlaceRedisUtil
import busanVibe.busan.domain.review.domain.Review
import java.time.LocalTime
import java.time.format.DateTimeFormatter
Expand Down
10 changes: 8 additions & 2 deletions src/main/kotlin/busanVibe/busan/domain/place/domain/Place.kt
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package busanVibe.busan.domain.place.domain
import busanVibe.busan.domain.common.BaseEntity
import busanVibe.busan.domain.place.enums.PlaceType
import busanVibe.busan.domain.review.domain.Review
import jakarta.persistence.CascadeType
import jakarta.persistence.Column
import jakarta.persistence.Entity
import jakarta.persistence.EnumType
Expand All @@ -11,6 +12,7 @@ import jakarta.persistence.FetchType
import jakarta.persistence.GeneratedValue
import jakarta.persistence.GenerationType
import jakarta.persistence.Id
import jakarta.persistence.JoinColumn
import jakarta.persistence.OneToMany
import jakarta.persistence.OneToOne
import java.math.BigDecimal
Expand Down Expand Up @@ -53,10 +55,14 @@ class Place(
@OneToMany(mappedBy = "place", fetch = FetchType.LAZY)
val placeLikes: Set<PlaceLike>,

@OneToOne(mappedBy = "place", fetch = FetchType.LAZY)
@OneToOne(mappedBy = "place", fetch = FetchType.LAZY, optional = true)
val openTime: OpenTime,

@OneToMany(mappedBy="place", fetch = FetchType.LAZY)
val placeImages: Set<PlaceImage>
val placeImages: Set<PlaceImage>,

@OneToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "visitor_distribution_id")
val visitorDistribution: VisitorDistribution? = null,

) : BaseEntity()
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
package busanVibe.busan.domain.place.domain

import jakarta.persistence.Column
import jakarta.persistence.Entity
import jakarta.persistence.FetchType
import jakarta.persistence.GeneratedValue
import jakarta.persistence.GenerationType
import jakarta.persistence.Id
import jakarta.persistence.JoinColumn
import jakarta.persistence.OneToOne
import org.hibernate.annotations.ColumnDefault

@Entity
class VisitorDistribution(

@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
val id: Long? = null,

@Column(nullable = false)
@ColumnDefault("0")
var m1020: Int = 0,

@Column(nullable = false)
@ColumnDefault("0")
var f1020: Int = 0,

@Column(nullable = false)
@ColumnDefault("0")
var m3040: Int = 0,

@Column(nullable = false)
@ColumnDefault("0")
var f3040: Int = 0,

@Column(nullable = false)
@ColumnDefault("0")
var m5060: Int = 0,

@Column(nullable = false)
@ColumnDefault("0")
var f5060: Int = 0,

@Column(nullable = false)
@ColumnDefault("0")
var m70: Int = 0,

@Column(nullable = false)
@ColumnDefault("0")
var f70: Int = 0,

) {

fun getTotalVisitorCount(): Int {
return (m1020 ?: 0) +
(f1020 ?: 0) +
(m3040 ?: 0) +
(f3040 ?: 0) +
(m5060 ?: 0) +
(f5060 ?: 0) +
(m70 ?: 0) +
(f70 ?: 0)
}


}
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ package busanVibe.busan.domain.place.enums
enum class PlaceSortType {

DEFAULT,
LIKES,
LIKE,
CONGESTION
;

Expand Down
Loading