diff --git a/src/app/api/index.ts b/src/app/api/index.ts new file mode 100644 index 00000000..43187c69 --- /dev/null +++ b/src/app/api/index.ts @@ -0,0 +1,52 @@ +import { paramsSerializer } from '@/lib/apis'; + +const BATCH_SIZE = 10; +const DELAY = 1000; + +export const fetchNaverSearchBlog = async ({ + query, includePost, +}: { query: string; includePost: boolean; }) => { + const response = await fetch(`${process.env.NEXT_PUBLIC_ORIGIN}/api/naver/search?${paramsSerializer({ + query, + include_post: includePost, + })}`); + + if (response.ok) { + const searchResult = await response.json(); + + return searchResult; + } + + return null; +}; + +export const fetchAllSettledSearchNaverBlogs = async ({ + placeName, +}: { + placeName: string[]; +}) => { + const copyPlaceName = [...placeName]; + const firstPlaceName = copyPlaceName.splice(0, BATCH_SIZE); + + const firstResponse = await Promise + .allSettled([...firstPlaceName.map((query) => fetchNaverSearchBlog({ + query, + includePost: false, + }))]); + + if (placeName.length <= 10) { + return firstResponse; + } + + await new Promise((resolve) => { + setTimeout(resolve, DELAY); + }); + + const secondResponse = await Promise + .allSettled([...copyPlaceName.map((query) => fetchNaverSearchBlog({ + query, + includePost: false, + }))]); + + return [...firstResponse, ...secondResponse]; +}; diff --git a/src/app/api/search/places/detail/route.ts b/src/app/api/search/places/detail/route.ts new file mode 100644 index 00000000..86f3b84c --- /dev/null +++ b/src/app/api/search/places/detail/route.ts @@ -0,0 +1,77 @@ +import { NextRequest, NextResponse } from 'next/server'; + +import { PlaceDetailsResponseData } from '@googlemaps/google-maps-services-js'; + +import { fetchNaverSearchBlog } from '@/app/api'; +import { paramsSerializer } from '@/lib/apis'; +import { PlaceDetail } from '@/lib/types/google.maps'; + +export const runtime = 'edge'; + +const TEN_MINUTES = 600; + +const hasPlaceId = ( + placeDetail: PlaceDetailsResponseData, +): placeDetail is PlaceDetail => { + if ( + !placeDetail?.result + || !placeDetail.result?.geometry?.location + || !placeDetail.result?.place_id + || !placeDetail.result?.name + ) { + return false; + } + + return true; +}; + +export async function GET(request: NextRequest) { + const { searchParams } = new URL(request.url); + const requestHeaders = new Headers(request.headers); + + const placeId = searchParams.get('placeId'); + + const searchResponse = await fetch(`${process.env.NEXT_PUBLIC_ORIGIN}/api/google/search/detail?${paramsSerializer({ + placeId, + })}`, { + method: 'GET', + next: { + revalidate: TEN_MINUTES, + }, + headers: requestHeaders, + }); + + if (!searchResponse.ok) { + return NextResponse.json(null, searchResponse); + } + + const placeDetail = await searchResponse.json() as PlaceDetailsResponseData; + + if (!hasPlaceId(placeDetail)) { + return NextResponse.json(null, { + headers: requestHeaders, + status: 400, + statusText: 'not found place name', + }); + } + + const searchBlogPost = await fetchNaverSearchBlog({ + query: placeDetail.result.name, includePost: true, + }); + + return NextResponse.json({ + ...placeDetail, + result: { + ...placeDetail.result, + searchBlogPost, + }, + }, { + ...searchResponse, + headers: { + ...searchResponse.headers, + 'Cache-Control': 'public, s-maxage=1', + 'CDN-Cache-Control': 'public, s-maxage=60', + 'Vercel-CDN-Cache-Control': `public, s-maxage=${TEN_MINUTES}`, + }, + }); +} diff --git a/src/app/api/search/places/route.ts b/src/app/api/search/places/route.ts index 80fe7f4a..0f9d0884 100644 --- a/src/app/api/search/places/route.ts +++ b/src/app/api/search/places/route.ts @@ -6,59 +6,11 @@ import { checkEmpty } from '@nf-team/core'; import { paramsSerializer } from '@/lib/apis'; import { filteredPlaces } from '@/utils'; +import { fetchAllSettledSearchNaverBlogs } from '../..'; + export const runtime = 'edge'; const TEN_MINUTES = 600; -const BATCH_SIZE = 10; -const DELAY = 1000; - -const fetchNaverSearchBlog = async ({ - query, includePost, -}: { query: string; includePost: boolean; }) => { - const response = await fetch(`${process.env.NEXT_PUBLIC_ORIGIN}/api/naver/search?${paramsSerializer({ - query, - include_post: includePost, - })}`); - - if (response.ok) { - const searchResult = await response.json(); - - return searchResult; - } - - return null; -}; - -const fetchAllSettledSearchNaverBlogs = async ({ - placeName, -}: { - placeName: string[]; -}) => { - const copyPlaceName = [...placeName]; - const firstPlaceName = copyPlaceName.splice(0, BATCH_SIZE); - - const firstResponse = await Promise - .allSettled([...firstPlaceName.map((query) => fetchNaverSearchBlog({ - query, - includePost: false, - }))]); - - if (placeName.length <= 10) { - return firstResponse; - } - - await new Promise((resolve) => { - setTimeout(resolve, DELAY); - }); - - const secondResponse = await Promise - .allSettled([...copyPlaceName.map((query) => fetchNaverSearchBlog({ - query, - includePost: false, - }))]); - - return [...firstResponse, ...secondResponse]; -}; export async function GET(request: NextRequest) { const { searchParams } = new URL(request.url); diff --git a/src/lib/apis/search/index.ts b/src/lib/apis/search/index.ts index dab66f3f..525b3a62 100644 --- a/src/lib/apis/search/index.ts +++ b/src/lib/apis/search/index.ts @@ -1,7 +1,7 @@ import { api, paramsSerializer } from '..'; import { - NaverSearchBlogResponse, PlaceDetailResponse, SearchPlacesResponse, TextSearchPlaceResponse, + NaverSearchBlogResponse, PlaceDetailsResponse, SearchPlacesResponse, TextSearchPlaceResponse, } from './model'; const BATCH_SIZE = 10; @@ -92,7 +92,7 @@ export const fetchSearchPlaces = async ({ export const fetchPlaceDetail = async ({ placeId, sessionToken, }: { placeId: string; sessionToken?: string; }) => { - const response = await api({ + const response = await api({ method: 'GET', url: '/google/search/detail', params: { diff --git a/src/lib/apis/search/model.ts b/src/lib/apis/search/model.ts index 258d94b8..b26cda61 100644 --- a/src/lib/apis/search/model.ts +++ b/src/lib/apis/search/model.ts @@ -1,11 +1,12 @@ -import { PlaceDetailsResponseData, TextSearchResponseData } from '@googlemaps/google-maps-services-js'; +import { TextSearchResponseData } from '@googlemaps/google-maps-services-js'; +import { PlaceDetail } from '@/lib/types/google.maps'; import { NaverSearchBlog, SearchPlaces } from '@/lib/types/search'; export type NaverSearchBlogResponse = NaverSearchBlog; export type TextSearchPlaceResponse = TextSearchResponseData; -export type PlaceDetailResponse = PlaceDetailsResponseData; +export type PlaceDetailsResponse = PlaceDetail; export type SearchPlacesResponse = SearchPlaces;