feat: 장소 검색 API 추가 (카카오/구글)
- 국내: 카카오맵 API (/api/admin/kakao/places) - 해외: 구글 Places API (/api/admin/google/places) - YOUTUBE_API_KEY를 GOOGLE_API_KEY로 통합 Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
65b1d931f3
commit
48f41c6db0
5 changed files with 107 additions and 5 deletions
6
.env
6
.env
|
|
@ -20,6 +20,8 @@ RUSTFS_BUCKET=fromis-9
|
|||
# Kakao API
|
||||
KAKAO_REST_KEY=e7a5516bf6cb1b398857789ee2ea6eea
|
||||
|
||||
# YouTube API
|
||||
YOUTUBE_API_KEY=AIzaSyC6l3nFlcHgLc0d1Q9WPyYQjVKTv21ZqFs
|
||||
# Google API
|
||||
GOOGLE_API_KEY=AIzaSyC6l3nFlcHgLc0d1Q9WPyYQjVKTv21ZqFs
|
||||
|
||||
# Meilisearch
|
||||
MEILI_MASTER_KEY=xMLNzlGX4xYji494JOb5IMlLHULcYw91
|
||||
|
|
|
|||
|
|
@ -47,8 +47,8 @@ export default {
|
|||
host: process.env.REDIS_HOST || 'fromis9-redis',
|
||||
port: parseInt(process.env.REDIS_PORT) || 6379,
|
||||
},
|
||||
youtube: {
|
||||
apiKey: process.env.YOUTUBE_API_KEY,
|
||||
google: {
|
||||
apiKey: process.env.GOOGLE_API_KEY,
|
||||
},
|
||||
jwt: {
|
||||
secret: process.env.JWT_SECRET,
|
||||
|
|
|
|||
96
backend/src/routes/admin/places.js
Normal file
96
backend/src/routes/admin/places.js
Normal file
|
|
@ -0,0 +1,96 @@
|
|||
import config from '../../config/index.js';
|
||||
import { badRequest, serverError } from '../../utils/error.js';
|
||||
|
||||
const KAKAO_REST_KEY = process.env.KAKAO_REST_KEY;
|
||||
const GOOGLE_API_KEY = config.google.apiKey;
|
||||
|
||||
/**
|
||||
* 장소 검색 관리자 라우트
|
||||
* - 국내: 카카오맵 API
|
||||
* - 해외: 구글 Places API
|
||||
*/
|
||||
export default async function placesRoutes(fastify) {
|
||||
/**
|
||||
* GET /api/admin/kakao/places
|
||||
* 카카오맵 장소 검색 (국내)
|
||||
*/
|
||||
fastify.get('/kakao/places', {
|
||||
preHandler: [fastify.authenticate],
|
||||
}, async (request, reply) => {
|
||||
const { query } = request.query;
|
||||
|
||||
if (!query || !query.trim()) {
|
||||
return badRequest(reply, '검색어를 입력해주세요.');
|
||||
}
|
||||
|
||||
if (!KAKAO_REST_KEY) {
|
||||
return serverError(reply, '카카오 API 키가 설정되지 않았습니다.');
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await fetch(
|
||||
`https://dapi.kakao.com/v2/local/search/keyword.json?query=${encodeURIComponent(query)}&size=15`,
|
||||
{
|
||||
headers: {
|
||||
Authorization: `KakaoAK ${KAKAO_REST_KEY}`,
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
if (!response.ok) {
|
||||
const errorText = await response.text();
|
||||
fastify.log.error(`카카오 API 오류: ${response.status} ${errorText}`);
|
||||
return serverError(reply, '카카오 API 호출 실패');
|
||||
}
|
||||
|
||||
const data = await response.json();
|
||||
return data;
|
||||
} catch (err) {
|
||||
fastify.log.error(`카카오 장소 검색 오류: ${err.message}`);
|
||||
return serverError(reply, err.message);
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* GET /api/admin/google/places
|
||||
* 구글 Places API 장소 검색 (해외)
|
||||
*/
|
||||
fastify.get('/google/places', {
|
||||
preHandler: [fastify.authenticate],
|
||||
}, async (request, reply) => {
|
||||
const { query } = request.query;
|
||||
|
||||
if (!query || !query.trim()) {
|
||||
return badRequest(reply, '검색어를 입력해주세요.');
|
||||
}
|
||||
|
||||
if (!GOOGLE_API_KEY) {
|
||||
return serverError(reply, 'Google API 키가 설정되지 않았습니다.');
|
||||
}
|
||||
|
||||
try {
|
||||
// Places API (New) - Text Search
|
||||
const response = await fetch(
|
||||
`https://maps.googleapis.com/maps/api/place/textsearch/json?query=${encodeURIComponent(query)}&key=${GOOGLE_API_KEY}`
|
||||
);
|
||||
|
||||
if (!response.ok) {
|
||||
const errorText = await response.text();
|
||||
fastify.log.error(`Google Places API 오류: ${response.status} ${errorText}`);
|
||||
return serverError(reply, 'Google API 호출 실패');
|
||||
}
|
||||
|
||||
const data = await response.json();
|
||||
|
||||
if (data.status !== 'OK' && data.status !== 'ZERO_RESULTS') {
|
||||
fastify.log.error(`Google Places API 상태: ${data.status} - ${data.error_message || ''}`);
|
||||
return serverError(reply, `Google API 오류: ${data.status}`);
|
||||
}
|
||||
|
||||
return data;
|
||||
} catch (err) {
|
||||
fastify.log.error(`구글 장소 검색 오류: ${err.message}`);
|
||||
return serverError(reply, err.message);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
@ -7,6 +7,7 @@ import botsRoutes from './admin/bots.js';
|
|||
import youtubeAdminRoutes from './admin/youtube.js';
|
||||
import xAdminRoutes from './admin/x.js';
|
||||
import concertAdminRoutes from './admin/concert.js';
|
||||
import placesAdminRoutes from './admin/places.js';
|
||||
|
||||
/**
|
||||
* 라우트 통합
|
||||
|
|
@ -39,4 +40,7 @@ export default async function routes(fastify) {
|
|||
|
||||
// 관리자 - 콘서트 라우트
|
||||
fastify.register(concertAdminRoutes, { prefix: '/admin/concert' });
|
||||
|
||||
// 관리자 - 장소 검색 라우트
|
||||
fastify.register(placesAdminRoutes, { prefix: '/admin' });
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import config from '../../config/index.js';
|
||||
import { formatDate, formatTime } from '../../utils/date.js';
|
||||
|
||||
const API_KEY = config.youtube.apiKey;
|
||||
const API_KEY = config.google.apiKey;
|
||||
const API_BASE = 'https://www.googleapis.com/youtube/v3';
|
||||
|
||||
/**
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue