Skip to content
Closed
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
32 changes: 32 additions & 0 deletions apps/api/api/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
// Vercel Node Function entrypoint for the NestJS app.
//
// Local dev still uses apps/api/src/main.ts (`nx serve api`). This
// handler is only consumed by Vercel — it boots Nest once per cold
// start, caches the underlying express instance in a module-scope
// variable, and reuses it across warm invocations.

import { NestFactory } from '@nestjs/core';
import { ExpressAdapter } from '@nestjs/platform-express';
import serverlessExpress from '@codegenie/serverless-express';
import express from 'express';
import type { Handler } from 'aws-lambda';
import { AppModule } from '../src/app/app.module';

let cachedHandler: Handler | undefined;

async function bootstrap(): Promise<Handler> {
const expressApp = express();
const app = await NestFactory.create(AppModule, new ExpressAdapter(expressApp));
app.setGlobalPrefix('api');
await app.init();
return serverlessExpress({ app: expressApp });
}

const handler: Handler = async (event, context, callback) => {
if (!cachedHandler) {
cachedHandler = await bootstrap();
}
return cachedHandler(event, context, callback);
};

export default handler;
2 changes: 1 addition & 1 deletion apps/api/tsconfig.app.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,5 +8,5 @@
"target": "es2021"
},
"exclude": ["jest.config.ts", "src/**/*.spec.ts", "src/**/*.test.ts"],
"include": ["src/**/*.ts"]
"include": ["src/**/*.ts", "api/**/*.ts"]
}
10 changes: 5 additions & 5 deletions apps/web/.env.example
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
# Korean public-data-portal service key for the abandonmentPublicSrvc API.
# Get one at https://www.data.go.kr/ and copy this file to apps/web/.env.local
# (which is git-ignored).
# (git-ignored).
#
# NEXT_PUBLIC_ prefix = exposed to the browser. A server-side proxy via a
# Next Route Handler is tracked under #9 (Vercel deploy); that will let us
# drop the NEXT_PUBLIC_ prefix and keep the key server-only.
NEXT_PUBLIC_DATA_GO_KR_SERVICE_KEY=
# Server-only — no NEXT_PUBLIC_ prefix. Read by the Next Route Handler
# at apps/web/app/api/data-go-kr/[...path]/route.ts; never shipped to
# the browser bundle.
DATA_GO_KR_SERVICE_KEY=
45 changes: 45 additions & 0 deletions apps/web/app/api/data-go-kr/[...path]/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import { NextRequest, NextResponse } from 'next/server';

const UPSTREAM_BASE =
'https://apis.data.go.kr/1543061/abandonmentPublicService_v2';

export async function GET(
request: NextRequest,
context: { params: Promise<{ path: string[] }> }
Comment on lines +6 to +8

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

issue: The context.params type should not be a Promise; using Promise<{ path: string[] }> is misleading and unnecessary.

In Next.js route handlers, context.params is a plain object, not a promise. Typing it as Promise<{ path: string[] }> diverges from the framework’s types, forces unnecessary awaits, and may break when Next’s typings change.

Use a synchronous params type instead:

export async function GET(
  request: NextRequest,
  context: { params: { path: string[] } }
) {
  const { path } = context.params;
  // ...
}

) {
const serviceKey = process.env.DATA_GO_KR_SERVICE_KEY;
if (!serviceKey) {
return NextResponse.json(
{ error: 'DATA_GO_KR_SERVICE_KEY is not configured on the server.' },
{ status: 500 }
);
}

const { path } = await context.params;
const upstreamPath = path.join('/');

const params = new URLSearchParams(request.nextUrl.searchParams);
params.set('serviceKey', serviceKey);
params.set('_type', 'json');

const upstreamUrl = `${UPSTREAM_BASE}/${upstreamPath}?${params.toString()}`;

try {
const upstream = await fetch(upstreamUrl, {
headers: { Accept: 'application/json' },
});
const body = await upstream.text();
return new NextResponse(body, {
status: upstream.status,
headers: {
'content-type':
upstream.headers.get('content-type') ?? 'application/json',
},
});
} catch (err) {
return NextResponse.json(
{ error: 'Upstream fetch failed', message: String(err) },
{ status: 502 }
);
}
}
2 changes: 1 addition & 1 deletion apps/web/src/new-api/animalInfo/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,4 @@ import { AnimalInfoRequestType } from "@animal-project/shared-types";
import { getAPI } from "../service";
import { AnimalInfo } from "@animal-project/shared-types";

export const getAnimalInfo = (props : AnimalInfoRequestType) => getAPI<AnimalInfoRequestType, AnimalInfo>(props, '/abandonmentPublic')
export const getAnimalInfo = (props : AnimalInfoRequestType) => getAPI<AnimalInfoRequestType, AnimalInfo>(props, '/abandonmentPublic_v2')
2 changes: 1 addition & 1 deletion apps/web/src/new-api/kind/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,4 @@ import { KindRequestType } from "@animal-project/shared-types";
import { getAPI } from "../service";
import { Kind } from "@animal-project/shared-types";

export const getKind = (props : KindRequestType) => getAPI<KindRequestType, Kind>(props, '/kind')
export const getKind = (props : KindRequestType) => getAPI<KindRequestType, Kind>(props, '/kind_v2')
21 changes: 4 additions & 17 deletions apps/web/src/new-api/service.ts
Original file line number Diff line number Diff line change
@@ -1,21 +1,10 @@
import axios from 'axios';
import { APIResponse } from '@animal-project/shared-types';

const baseURL = 'http://apis.data.go.kr/1543061/abandonmentPublicSrvc';

// NEXT_PUBLIC_ because getAPI runs in the browser (TanStack Query
// client). The key is still visible in the Network tab; moving getAPI
// behind a Next Route Handler (apps/web/app/api/**) removes even that
// exposure and is tracked under #9 (Vercel deploy). This change only
// removes the literal key from git history so it can be rotated.
const serviceKey = process.env.NEXT_PUBLIC_DATA_GO_KR_SERVICE_KEY;

if (!serviceKey) {
// eslint-disable-next-line no-console
console.error(
'NEXT_PUBLIC_DATA_GO_KR_SERVICE_KEY is not set. See apps/web/.env.example.'
);
}
// Calls go to the Next Route Handler at app/api/data-go-kr/[...path],
// which proxies to the public-data-portal and injects the server-only
// DATA_GO_KR_SERVICE_KEY. The browser never sees the key.
const baseURL = '/api/data-go-kr';

export const serviceAPI = axios.create({
baseURL,
Expand All @@ -26,8 +15,6 @@ export const getAPI = async <T, K>(props: T, path: string) => {
const data = await serviceAPI.get<APIResponse<K>>(path, {
params: {
...props,
serviceKey,
_type: 'json',
},
});
return data.data;
Expand Down
2 changes: 1 addition & 1 deletion apps/web/src/new-api/shelter/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,4 @@ import { ShelterRequestType } from "@animal-project/shared-types";
import { getAPI } from "../service";
import { Shelter } from "@animal-project/shared-types";

export const getShelter = (props : ShelterRequestType) => getAPI<ShelterRequestType, Shelter>(props, '/shelter')
export const getShelter = (props : ShelterRequestType) => getAPI<ShelterRequestType, Shelter>(props, '/shelter_v2')
2 changes: 1 addition & 1 deletion apps/web/src/new-api/sido/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,5 @@ import { SidoRequestType } from "@animal-project/shared-types";
import { getAPI } from "../service";
import { Sido } from "@animal-project/shared-types";

export const getSido = (props : SidoRequestType) => getAPI<SidoRequestType, Sido>(props, '/sido')
export const getSido = (props : SidoRequestType) => getAPI<SidoRequestType, Sido>(props, '/sido_v2')

2 changes: 1 addition & 1 deletion apps/web/src/new-api/sigungu/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,4 @@ import { SigunguRequestType } from "@animal-project/shared-types";
import { getAPI } from "../service";
import { Sigungu } from "@animal-project/shared-types";

export const getSigungu = (props : SigunguRequestType) => getAPI<SigunguRequestType, Sigungu>(props, '/sigungu')
export const getSigungu = (props : SigunguRequestType) => getAPI<SigunguRequestType, Sigungu>(props, '/sigungu_v2')
2 changes: 1 addition & 1 deletion apps/web/src/new-site/search/card/AnimalCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ export const AnimalCard = (props: Props) => {
</motion.div>
</div>
<figure className="px-10 pt-10">
<img src={item.popfile} alt="pet" className="rounded-xl h-32" />
<img src={item.popfile1} alt="pet" className="rounded-xl h-32" />
</figure>
<CardContent className="flex flex-col items-center text-center">
<table className="text-sm">
Expand Down
9 changes: 8 additions & 1 deletion libs/shared-types/src/lib/responseAPI.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,14 @@ export interface APIResponse<T> {
noticeNo: string; // 공고번호
noticeSdt: string; // 공고시작일 (YYYYMMDD)
noticeEdt: string; // 공고종료일 (YYYYMMDD)
popfile: string; // Image URL
popfile1: string; // Image URL (v2 API: up to 8 images popfile1..popfile8)
popfile2?: string;
popfile3?: string;
popfile4?: string;
popfile5?: string;
popfile6?: string;
popfile7?: string;
popfile8?: string;
processState: string; // 상태 (예: 보호중)
sexCd: string; // 성별 (M: 수컷, F: 암컷, Q: 미상)
neuterYn: string; // 중성화 여부 (Y: 예, N: 아니오, U: 미상)
Expand Down
Loading
Loading