Skip to content
Open
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
3 changes: 3 additions & 0 deletions build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ dependencies {
implementation("org.springframework.boot:spring-boot-starter-data-jpa")
implementation("org.springframework.boot:spring-boot-starter-thymeleaf")
implementation("org.springframework.boot:spring-boot-starter-validation")
compileOnly("org.projectlombok:lombok")
annotationProcessor("org.projectlombok:lombok")
implementation("org.springframework.boot:spring-boot-starter-web")
implementation("com.fasterxml.jackson.module:jackson-module-kotlin")
implementation("org.flywaydb:flyway-core")
Expand All @@ -32,6 +34,7 @@ dependencies {
runtimeOnly("com.mysql:mysql-connector-j")
testImplementation("org.springframework.boot:spring-boot-starter-test")
testImplementation("org.jetbrains.kotlin:kotlin-test-junit5")
testImplementation("org.assertj:assertj-core")
testRuntimeOnly("org.junit.platform:junit-platform-launcher")
}

Expand Down
14 changes: 14 additions & 0 deletions src/main/java/sunshine/config/AppConfig.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package sunshine.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.client.RestTemplate;

@Configuration
public class AppConfig {

@Bean
public RestTemplate restTemplate() {
return new RestTemplate();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package sunshine.interfaces.api.controller;

import java.util.HashMap;
import java.util.Map;

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

import lombok.RequiredArgsConstructor;
import sunshine.interfaces.api.service.SearchService;

@RequestMapping("/api")
@RestController
@RequiredArgsConstructor
public class SearchController {

private final SearchService searchService;

@GetMapping("/search")
public Map<String, Object> searchWeather(@RequestParam String city) {

return searchService.searchWeather(city);
}
}
39 changes: 39 additions & 0 deletions src/main/java/sunshine/interfaces/api/service/SearchService.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package sunshine.interfaces.api.service;

import java.util.List;
import java.util.Map;

import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;

import lombok.RequiredArgsConstructor;

@Service
@RequiredArgsConstructor
public class SearchService {

private final RestTemplate restTemplate;

public Map<String, Object> searchWeather(String city) {

Map<String,Object> matrix = getMatrix(city);

String url = String.format("https://api.open-meteo.com/v1/forecast?latitude=%s&longitude=%s"
+ "&current=temperature_2m,weather_code,wind_speed_10m,relative_humidity_2m,apparent_temperature&wind_speed_unit=ms", matrix.get("latitude"), matrix.get("longitude"));

Map<String,Object> response = restTemplate.getForObject(url,Map.class);

return response;
}

public Map<String, Object> getMatrix(String city) {

String url = String.format("https://geocoding-api.open-meteo.com/v1/search?name=%s&count=10&language=en&format=json", city);

List<Map<String,Object>> response = (List<Map<String,Object>>) restTemplate.getForObject(url,Map.class).get("results");

Map<String, Object> matrix = (Map<String,Object>) response.get(0);

return matrix;
}
}
83 changes: 83 additions & 0 deletions src/main/resources/templates/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<h1>날씨 조회할 도시를 영어로 입력해주세요.</h1>
<input id="cityInput">
<button id="searchWeather">조회</button>

<p id="result"></p>

<script src="https://code.jquery.com/jquery-3.7.1.min.js"></script>

<script th:inline="javascript">

const weatherCodeMap = {
0: "맑음",
1: "거의 맑음",
2: "부분적으로 흐림",
3: "흐림",
45: "안개",
48: "어는 안개",
51: "이슬비(약)",
53: "이슬비(보통)",
55: "이슬비(강)",
56: "빙결 이슬비(약)",
57: "빙결 이슬비(강)",
61: "비(약)",
63: "비(보통)",
65: "비(강)",
66: "빙결 비(약)",
67: "빙결 비(강)",
71: "눈(약)",
73: "눈(보통)",
75: "눈(강)",
77: "눈 알갱이",
80: "소나기(약)",
81: "소나기(보통)",
82: "소나기(강)",
85: "눈 소나기(약)",
86: "눈 소나기(강)",
95: "천둥번개",
96: "우박 동반 천둥번개(약)",
99: "우박 동반 천둥번개(강)"
}

$("#searchWeather").on("click", function (e){
searchWeather();
});

function searchWeather() {
const city = document.getElementById("cityInput").value;

fetch('/api/search?city=' + city, {
method: "GET",
headers: {
"Content-Type": "application/json",
},
}).then((response) => {
if(!response.ok){
throw new Error("에러 발생");
}
return response.json();
}).then((data) => {

const current = data.current;

const temperatureUnit = data.current_units.temperature_2m;
const windSpeedUnit = data.current_units.wind_speed_10m;
const humidityUnit = data.current_units.relative_humidity_2m;

$("#result").text("현재 " + city + "의 기온은 " + current.temperature_2m + temperatureUnit +
"이며, 체감 온도는 " + current.apparent_temperature + temperatureUnit + "입니다. 습도는 " +
current.relative_humidity_2m + humidityUnit + "이고, 풍속은 " + current.wind_speed_10m + windSpeedUnit + "입니다. 날씨는 " +
weatherCodeMap[current.weather_code] + "입니다.");
})
}

</script>
</body>
</html>
72 changes: 72 additions & 0 deletions src/test/java/sunshine/ApiTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
package sunshine;

import static org.assertj.core.api.Assertions.*;
import static org.mockito.Mockito.*;

import java.util.List;
import java.util.Map;

import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import org.springframework.web.client.RestTemplate;

import sunshine.interfaces.api.service.SearchService;

@ExtendWith(MockitoExtension.class)
public class ApiTest {

@Mock
RestTemplate restTemplate;

@InjectMocks
SearchService searchService;

@Test
void searchWeatherTest() {

//given
Map<String, Object> geocode = Map.of("results", List.of(Map.of("latitude", "37.5665", "longitude", "126.9780")));

Map<String, Object> weather = Map.of("current", Map.of("temperature_2m", 12.3, "apparent_temperature", 10.0, "weather_code", 3, "wind_speed_10m", 5.2, "relative_humidity_2m", 70));


when(restTemplate.getForObject(contains("geocoding-api"), eq(Map.class))).thenReturn(geocode);

when(restTemplate.getForObject(contains("forecast"), eq(Map.class))).thenReturn(weather);

//when
Map<String, Object> result = searchService.searchWeather("Seoul");

//then
assertThat(result).isNotNull();
assertThat(result.get("current")).isInstanceOf(Map.class);

Map current = (Map) result.get("current");

assertThat(current.get("temperature_2m")).isEqualTo(12.3);
assertThat(current.get("apparent_temperature")).isEqualTo(10.0);
assertThat(current.get("weather_code")).isEqualTo(3);
assertThat(current.get("wind_speed_10m")).isEqualTo(5.2);
assertThat(current.get("relative_humidity_2m")).isEqualTo(70);
}

@Test
void getMatrixTest() {
//given
Map<String, Object> geocode = Map.of("results", List.of(Map.of("latitude", "51.50853", "longitude", "-0.12574")));

when(restTemplate.getForObject(anyString(), eq(Map.class))).thenReturn(geocode);

//when
Map<String, Object> matrix = searchService.getMatrix("London");

//then
assertThat(matrix.get("latitude")).isEqualTo("51.50853");
assertThat(matrix.get("longitude")).isEqualTo("-0.12574");


}
}