From 88fd2d8b890c7e2c65ded9e6ddda40792fda1e70 Mon Sep 17 00:00:00 2001 From: h-yoshikawa44 <43331308+h-yoshikawa44@users.noreply.github.com> Date: Sun, 22 Oct 2023 13:59:50 +0900 Subject: [PATCH 01/25] =?UTF-8?q?settings:=20OpenWeatherMap=20API=E5=90=91?= =?UTF-8?q?=E3=81=91=E3=81=AE=E7=92=B0=E5=A2=83=E5=A4=89=E6=95=B0=E8=BF=BD?= =?UTF-8?q?=E5=8A=A0(#14)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .env.example | 1 + .gitignore | 1 + 2 files changed, 2 insertions(+) create mode 100644 .env.example diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..1455054 --- /dev/null +++ b/.env.example @@ -0,0 +1 @@ +NEXT_PUBLIC_OPEN_WEATHER_MAP_API_KEY= diff --git a/.gitignore b/.gitignore index 1437c53..74b7586 100644 --- a/.gitignore +++ b/.gitignore @@ -25,6 +25,7 @@ yarn-debug.log* yarn-error.log* # local env files +.env .env.local .env.development.local .env.test.local From f9ed7079db69a1c21d3655caa1047daa5f69bddb Mon Sep 17 00:00:00 2001 From: h-yoshikawa44 <43331308+h-yoshikawa44@users.noreply.github.com> Date: Sun, 22 Oct 2023 14:02:26 +0900 Subject: [PATCH 02/25] =?UTF-8?q?feat:=20ky=E3=81=AE=E3=83=99=E3=83=BC?= =?UTF-8?q?=E3=82=B9=E3=82=A4=E3=83=B3=E3=82=B9=E3=82=BF=E3=83=B3=E3=82=B9?= =?UTF-8?q?=E3=82=92=E7=B6=99=E6=89=BF=E3=81=97=E3=81=A6=E4=BD=BF=E3=81=86?= =?UTF-8?q?=E3=82=88=E3=81=86=E3=81=AA=E5=BD=A2=E5=BC=8F=E3=81=AB=E3=81=97?= =?UTF-8?q?=E3=81=9F(#14)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/config/ky.ts | 21 ++++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/src/config/ky.ts b/src/config/ky.ts index 5d2a381..fa68675 100644 --- a/src/config/ky.ts +++ b/src/config/ky.ts @@ -1,15 +1,22 @@ -import { Options } from 'ky-universal'; +import ky, { Options } from 'ky-universal'; export type ApiType = 'inner' | 'outer'; -const prefixUrls = { +const prefixUrls: { [key in ApiType]: string } = { inner: 'api', - outer: 'https://www.metaweather.com/api', + outer: 'https://api.openweathermap.org', }; -export const getDefaultApiOptions = (apiType: ApiType) => { - return { +const api = ky.create({ + retry: 0, + searchParams: { + appid: process.env.NEXT_PUBLIC_OPEN_WEATHER_MAP_API_KEY ?? '', + }, +}); + +export const getExtendKy = (apiType: ApiType, options?: Options) => { + return api.extend({ prefixUrl: prefixUrls[apiType], - retry: 0, - } as Options; + ...options, + }); }; From fa32ce47a4d680e3b890a15542c102a679039442 Mon Sep 17 00:00:00 2001 From: h-yoshikawa44 <43331308+h-yoshikawa44@users.noreply.github.com> Date: Sun, 22 Oct 2023 14:29:26 +0900 Subject: [PATCH 03/25] =?UTF-8?q?feat:=20=E3=83=AD=E3=82=B1=E3=83=BC?= =?UTF-8?q?=E3=82=B7=E3=83=A7=E3=83=B3=E6=A4=9C=E7=B4=A2API=E3=81=AE?= =?UTF-8?q?=E3=81=86=E3=81=A1API=E3=83=AB=E3=83=BC=E3=83=88->=E5=A4=96?= =?UTF-8?q?=E9=83=A8API=E7=94=A8=E3=81=AE=E5=87=A6=E7=90=86=E3=82=92?= =?UTF-8?q?=E5=88=86=E9=9B=A2(#14)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/pages/api/location/search.ts | 15 ++++--- src/server/location/Location.ts | 50 ++++++++++++++++++++++ src/server/location/getLocationsToOuter.ts | 40 +++++++++++++++++ src/server/location/index.ts | 8 ++++ 4 files changed, 106 insertions(+), 7 deletions(-) create mode 100644 src/server/location/Location.ts create mode 100644 src/server/location/getLocationsToOuter.ts create mode 100644 src/server/location/index.ts diff --git a/src/pages/api/location/search.ts b/src/pages/api/location/search.ts index 5d798cb..041788e 100644 --- a/src/pages/api/location/search.ts +++ b/src/pages/api/location/search.ts @@ -1,16 +1,17 @@ -// Next.js API route support: https://nextjs.org/docs/api-routes/introduction import { NextApiRequest, NextApiResponse } from 'next'; import { HTTPError } from 'ky-universal'; -import getLocations, { +import { + getLocationsToOuter, QueryParams, isQueryParams, -} from '@/domains/getLocations'; +} from '@/server/location'; /** * ロケーション検索 API - * クエリパラメータ(どちらかは必須) - * - query: string - 検索ワード - * - lattlong: string(書式:小数,小数)- 緯度経度 + * + * クエリパラメータ(q は必須) + * - q: string - 都市名、州コード (米国のみ)、および国コードをカンマで区切ったもの。 ISO 3166 国コード + * - limit: number - 取得する都市の数(最大5つ) * @param {NextApiRequest} req リクエストミドルウェア * @param {NextApiResponse} res レスポンスヘルパー */ @@ -26,7 +27,7 @@ export default async function handler( res.status(403).send('Invalid query parameter.'); } - getLocations('outer', { + await getLocationsToOuter({ searchParams: queryParams as QueryParams, }) .then((data) => { diff --git a/src/server/location/Location.ts b/src/server/location/Location.ts new file mode 100644 index 0000000..553b122 --- /dev/null +++ b/src/server/location/Location.ts @@ -0,0 +1,50 @@ +export type LocationResponse = { + /** 都市名 */ + name: string; + /** + * 言語ごとの都市名 + * 都市によってどの言語が入るか不定 + * (数が多いので定義は最低限だけ) + */ + local_names?: { + ja?: string; + en?: string; + }; + /** 緯度 */ + lat: number; + /** 経度 */ + lon: number; + /** 都市が属する国 */ + country: string; + /** 都市の状態 */ + state?: string; +}; + +/** + * 外部 API の方のレスポンスモデル + */ +export type LocationsResponse = LocationResponse[]; + +export const isLocationResponse = (arg: unknown): arg is LocationResponse => { + const l = arg as LocationResponse; + + return ( + typeof l.name === 'string' && + (typeof l.local_names?.en === 'undefined' || + typeof l.local_names.en === 'string') && + (typeof l.local_names?.ja === 'undefined' || + typeof l.local_names.ja === 'string') && + typeof l.lat === 'number' && + typeof l.lon === 'number' && + typeof l.country === 'string' && + (typeof l.state === 'undefined' || typeof l.state === 'string') + ); +}; + +export const isLocationsResponse = ( + args: unknown, +): args is LocationsResponse => { + const ls = args as LocationsResponse; + + return ls.every((l) => isLocationResponse(l)); +}; diff --git a/src/server/location/getLocationsToOuter.ts b/src/server/location/getLocationsToOuter.ts new file mode 100644 index 0000000..d55adb4 --- /dev/null +++ b/src/server/location/getLocationsToOuter.ts @@ -0,0 +1,40 @@ +import { Options } from 'ky-universal'; +import { getExtendKy } from '@/config/ky'; +import { isLocationsResponse } from './Location'; + +export type QueryParams = { + /** + * 都市名、州コード (米国のみ)、および国コードをカンマで区切ったもの。 ISO 3166 国コード + * - {city name} + * - {city name},{state code},{country code} + */ + q: string; + /** + * 取得する都市の数(最大5つ) + * + * 外部 API としては任意パラメータであるが、指定を強制したいので必須にしている + */ + limit: number; +}; + +export const isQueryParams = (query: unknown): query is QueryParams => { + const q = query as QueryParams; + const qRegex = new RegExp(/^[a-zA-Z]+$/); + + return ( + typeof q.q === 'string' && qRegex.test(q.q) && typeof q.limit === 'string' + ); +}; + +export const getLocationsToOuter = async ( + options: Options & { searchParams: QueryParams }, +) => { + const response = await getExtendKy('outer', options).get('geo/1.0/direct'); + const locations = (await response.json()) as unknown[]; + + if (!isLocationsResponse(locations)) { + throw Error('API type error'); + } + + return locations; +}; diff --git a/src/server/location/index.ts b/src/server/location/index.ts new file mode 100644 index 0000000..3bf7d1e --- /dev/null +++ b/src/server/location/index.ts @@ -0,0 +1,8 @@ +import { + getLocationsToOuter, + QueryParams, + isQueryParams, +} from './getLocationsToOuter'; + +export type { QueryParams }; +export { getLocationsToOuter, isQueryParams }; From 4714286cb53b265cc9d9c44f5784b043b69d6663 Mon Sep 17 00:00:00 2001 From: h-yoshikawa44 <43331308+h-yoshikawa44@users.noreply.github.com> Date: Sun, 22 Oct 2023 14:59:12 +0900 Subject: [PATCH 04/25] =?UTF-8?q?feat:=20=E3=83=AD=E3=82=B1=E3=83=BC?= =?UTF-8?q?=E3=82=B7=E3=83=A7=E3=83=B3=E6=A4=9C=E7=B4=A2=E6=A9=9F=E8=83=BD?= =?UTF-8?q?=E3=81=AE=E3=81=86=E3=81=A1=E3=80=81FE->API=E3=83=AB=E3=83=BC?= =?UTF-8?q?=E3=83=88=E9=83=A8=E5=88=86=E3=81=AE=E5=87=A6=E7=90=86=E3=82=92?= =?UTF-8?q?viewModel=E3=82=92=E4=BD=BF=E3=81=86=E6=96=B9=E5=BC=8F=E3=81=AB?= =?UTF-8?q?=E7=A7=BB=E8=A1=8C(#14)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../WeatherLocationMenu.tsx | 6 ++- src/domains/getLocations/getLocations.ts | 39 ++----------------- src/domains/getLocations/index.ts | 4 +- src/hooks/useLocationMenu/useLocationMenu.ts | 2 +- src/models/Location.ts | 33 +++++----------- src/pages/api/location/search.ts | 3 +- .../location/createLocationsViewModel.ts | 15 +++++++ src/server/location/index.ts | 3 +- 8 files changed, 39 insertions(+), 66 deletions(-) create mode 100644 src/server/location/createLocationsViewModel.ts diff --git a/src/components/model/weather/WeatherLocationMenu/WeatherLocationMenu.tsx b/src/components/model/weather/WeatherLocationMenu/WeatherLocationMenu.tsx index aaa118d..7a7ed72 100644 --- a/src/components/model/weather/WeatherLocationMenu/WeatherLocationMenu.tsx +++ b/src/components/model/weather/WeatherLocationMenu/WeatherLocationMenu.tsx @@ -66,7 +66,7 @@ const WeatherLocationMenu: FC = ({ )}