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
4 changes: 2 additions & 2 deletions .github/workflows/deploy-backend.yml
Original file line number Diff line number Diff line change
Expand Up @@ -67,8 +67,8 @@ jobs:
- name: Verify production migration 004 used market schema
working-directory: ${{ github.workspace }}
run: |
chmod +x scripts/run-production-migrations.sh scripts/verify-production-migration-004.sh
scripts/verify-production-migration-004.sh
chmod +x scripts/run-production-migrations.sh scripts/verify-production-migration-005.sh
scripts/verify-production-migration-005.sh
- run: ./gradlew spotlessCheck
- run: ./gradlew test build -x spotlessCheck

Expand Down
4 changes: 2 additions & 2 deletions .github/workflows/gradle.yml
Original file line number Diff line number Diff line change
Expand Up @@ -103,8 +103,8 @@ jobs:
- name: Verify production migration 004 used market schema
working-directory: ${{ github.workspace }}
run: |
chmod +x scripts/run-production-migrations.sh scripts/verify-production-migration-004.sh
scripts/verify-production-migration-004.sh
chmod +x scripts/run-production-migrations.sh scripts/verify-production-migration-005.sh
scripts/verify-production-migration-005.sh

- name: Build and Test with Gradle
run: ./gradlew test build -x spotlessCheck
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,27 @@ public class Resort {
@Column(name = "region", nullable = false, length = 50)
private String regionName;

@Column(name = "code", nullable = false, length = 30, unique = true)
private String code;

@Column(name = "display_order", nullable = false)
private int displayOrder;

@Column(name = "is_active", nullable = false)
private boolean active;

@Builder
public Resort(String name, String regionName) {
public Resort(String name, String regionName, String code, int displayOrder, boolean active) {
this.name = name;
this.regionName = regionName;
this.code = code;
this.displayOrder = displayOrder;
this.active = active;
}

public void updateMetadata(String code, int displayOrder, boolean active) {
this.code = code;
this.displayOrder = displayOrder;
this.active = active;
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.ikae.snowthing.domain.member.repository;

import java.util.List;
import java.util.Optional;

import org.springframework.data.jpa.repository.JpaRepository;
Expand All @@ -8,4 +9,8 @@

public interface ResortRepository extends JpaRepository<Resort, Long> {
Optional<Resort> findByName(String name);

Optional<Resort> findByCode(String code);

List<Resort> findAllByActiveTrueOrderByDisplayOrderAscIdAsc();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package com.ikae.snowthing.domain.resortcam.controller;

import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import com.ikae.snowthing.domain.resortcam.dto.ResortCameraListResponse;
import com.ikae.snowthing.domain.resortcam.service.ResortCameraQueryService;

import lombok.RequiredArgsConstructor;

@RestController
@RequestMapping("/api/v1/resort-cams")
@RequiredArgsConstructor
public class ResortCameraController {

private final ResortCameraQueryService resortCameraQueryService;

@GetMapping
public ResponseEntity<ResortCameraListResponse> getResortCameras() {
return ResponseEntity.ok(resortCameraQueryService.getActiveResortCameras());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package com.ikae.snowthing.domain.resortcam.dto;

import java.util.List;

public record ResortCameraGroupResponse(
String code,
String name,
String regionName,
int displayOrder,
List<ResortCameraItemResponse> cameras) {

public ResortCameraGroupResponse {
cameras = List.copyOf(cameras);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package com.ikae.snowthing.domain.resortcam.dto;

import com.ikae.snowthing.domain.resortcam.entity.CameraSourceType;
import com.ikae.snowthing.domain.resortcam.entity.ResortCamera;

public record ResortCameraItemResponse(
String code,
String name,
CameraSourceType sourceType,
String sourceUrl,
String externalPageUrl,
int displayOrder) {

public static ResortCameraItemResponse from(ResortCamera camera) {
return new ResortCameraItemResponse(
camera.getCode(),
camera.getName(),
camera.getSourceType(),
camera.getSourceUrl(),
camera.getExternalPageUrl(),
camera.getDisplayOrder());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package com.ikae.snowthing.domain.resortcam.dto;

import java.util.List;

public record ResortCameraListResponse(List<ResortCameraGroupResponse> resorts) {

public ResortCameraListResponse {
resorts = List.copyOf(resorts);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package com.ikae.snowthing.domain.resortcam.entity;

public enum CameraSourceType {
HLS,
YOUTUBE,
IFRAME,
EXTERNAL_LINK
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
package com.ikae.snowthing.domain.resortcam.entity;

import jakarta.persistence.*;

import com.ikae.snowthing.domain.member.entity.Resort;
import com.ikae.snowthing.global.common.BaseTimeEntity;

import lombok.AccessLevel;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;

@Entity
@Table(
name = "resort_camera",
uniqueConstraints =
@UniqueConstraint(
name = "uk_resort_camera_resort_code",
columnNames = {"resort_id", "code"}),
indexes =
@Index(
name = "idx_resort_camera_active_order",
columnList = "resort_id,is_active,display_order,resort_camera_id"))
@Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class ResortCamera extends BaseTimeEntity {

@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "resort_camera_id")
private Long id;

@ManyToOne(fetch = FetchType.LAZY, optional = false)
@JoinColumn(name = "resort_id", nullable = false)
private Resort resort;

@Column(name = "code", nullable = false, length = 50)
private String code;

@Column(name = "name", nullable = false, length = 100)
private String name;

@Enumerated(EnumType.STRING)
@Column(name = "source_type", nullable = false, length = 20)
private CameraSourceType sourceType;

@Column(name = "source_url", nullable = false, length = 1000)
private String sourceUrl;

@Column(name = "external_page_url", length = 1000)
private String externalPageUrl;

@Column(name = "display_order", nullable = false)
private int displayOrder;

@Column(name = "is_active", nullable = false)
private boolean active;

@Builder
public ResortCamera(
Resort resort,
String code,
String name,
CameraSourceType sourceType,
String sourceUrl,
String externalPageUrl,
int displayOrder,
boolean active) {
this.resort = resort;
this.code = code;
this.name = name;
this.sourceType = sourceType;
this.sourceUrl = sourceUrl;
this.externalPageUrl = externalPageUrl;
this.displayOrder = displayOrder;
this.active = active;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package com.ikae.snowthing.domain.resortcam.repository;

import java.util.List;

import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;

import com.ikae.snowthing.domain.resortcam.entity.ResortCamera;

public interface ResortCameraRepository extends JpaRepository<ResortCamera, Long> {

@Query(
"""
select camera
from ResortCamera camera
join fetch camera.resort resort
where camera.active = true and resort.active = true
order by resort.displayOrder asc, resort.id asc,
camera.displayOrder asc, camera.id asc
""")
List<ResortCamera> findAllActiveOrdered();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
package com.ikae.snowthing.domain.resortcam.service;

import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import com.ikae.snowthing.domain.member.entity.Resort;
import com.ikae.snowthing.domain.member.repository.ResortRepository;
import com.ikae.snowthing.domain.resortcam.dto.ResortCameraGroupResponse;
import com.ikae.snowthing.domain.resortcam.dto.ResortCameraItemResponse;
import com.ikae.snowthing.domain.resortcam.dto.ResortCameraListResponse;
import com.ikae.snowthing.domain.resortcam.repository.ResortCameraRepository;

import lombok.RequiredArgsConstructor;

@Service
@RequiredArgsConstructor
@Transactional(readOnly = true)
public class ResortCameraQueryService {

private final ResortRepository resortRepository;
private final ResortCameraRepository resortCameraRepository;

public ResortCameraListResponse getActiveResortCameras() {
Map<String, List<ResortCameraItemResponse>> camerasByResort =
resortCameraRepository.findAllActiveOrdered().stream()
.collect(
Collectors.groupingBy(
camera -> camera.getResort().getCode(),
Collectors.mapping(
ResortCameraItemResponse::from,
Collectors.toList())));

List<ResortCameraGroupResponse> resorts =
resortRepository.findAllByActiveTrueOrderByDisplayOrderAscIdAsc().stream()
.filter(resort -> camerasByResort.containsKey(resort.getCode()))
.map(resort -> toResponse(resort, camerasByResort))
.toList();
return new ResortCameraListResponse(resorts);
}

private ResortCameraGroupResponse toResponse(
Resort resort, Map<String, List<ResortCameraItemResponse>> camerasByResort) {
return new ResortCameraGroupResponse(
resort.getCode(),
resort.getName(),
resort.getRegionName(),
resort.getDisplayOrder(),
camerasByResort.getOrDefault(resort.getCode(), List.of()));
}
}
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
package com.ikae.snowthing.global.config;

import java.util.List;
import java.util.Optional;

import org.springframework.boot.CommandLineRunner;
import org.springframework.context.annotation.Profile;
import org.springframework.core.annotation.Order;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Component;

Expand All @@ -25,6 +27,7 @@

@Component
@Profile("local")
@Order(1)
@RequiredArgsConstructor
public class DataInitializer implements CommandLineRunner {

Expand Down Expand Up @@ -60,15 +63,36 @@ public void run(String... args) {
.build());
}

if (resortRepository.count() == 0) {
resortRepository.saveAll(
List.of(
Resort.builder().name("휘닉스파크").regionName("강원 평창").build(),
Resort.builder().name("하이원리조트").regionName("강원 정선").build(),
Resort.builder().name("모나용평").regionName("강원 평창").build(),
Resort.builder().name("비발디파크").regionName("강원 홍천").build(),
Resort.builder().name("웰리힐리파크").regionName("강원 횡성").build(),
Resort.builder().name("지산리조트").regionName("경기 이천").build()));
List<Resort> targetResorts =
List.of(
resort("PHOENIX", "휘닉스파크", "강원 평창", 1),
resort("VIVALDI", "비발디파크", "강원 홍천", 2),
resort("HIGH1", "하이원리조트", "강원 정선", 3),
resort("YONGPYONG", "모나용평", "강원 평창", 4),
resort("WELLI_HILLI", "웰리힐리파크", "강원 횡성", 5),
resort("JISAN", "지산리조트", "경기 이천", 6),
resort("KONJIAM", "곤지암리조트", "경기 광주", 7),
resort("MUJU", "무주덕유산리조트", "전북 무주", 8),
resort("EDEN_VALLEY", "에덴밸리리조트", "경남 양산", 9),
resort("ELYSIAN", "엘리시안 강촌", "강원 춘천", 10),
resort("ALPENSIA", "알펜시아리조트", "강원 평창", 11),
resort("OAK_VALLEY", "오크밸리", "강원 원주", 12),
resort("O2_RESORT", "오투리조트", "강원 태백", 13));

for (Resort target : targetResorts) {
Optional<Resort> byCode = resortRepository.findByCode(target.getCode());
if (byCode.isPresent()) {
continue;
}
Optional<Resort> byName = resortRepository.findByName(target.getName());
if (byName.isPresent()) {
Resort existing = byName.get();
existing.updateMetadata(
target.getCode(), target.getDisplayOrder(), target.isActive());
resortRepository.save(existing);
} else {
resortRepository.save(target);
}
}

if (ridingStyleRepository.count() == 0) {
Expand Down Expand Up @@ -172,4 +196,14 @@ private MarketCategory marketCategory(String code, String name, int sortOrder) {
.active(true)
.build();
}

private Resort resort(String code, String name, String regionName, int displayOrder) {
return Resort.builder()
.code(code)
.name(name)
.regionName(regionName)
.displayOrder(displayOrder)
.active(true)
.build();
}
}
Loading
Loading