From bbafb3ed4d852b2126bd5028fc59bb3ebedd424b Mon Sep 17 00:00:00 2001 From: sb Date: Sat, 11 Jul 2026 08:07:53 +0900 Subject: [PATCH] =?UTF-8?q?feat:=20=EB=B0=B1=EC=97=94=EB=93=9C=20API=20?= =?UTF-8?q?=EC=97=B0=EB=8F=99=20(HTTP=20=ED=81=B4=EB=9D=BC=EC=9D=B4?= =?UTF-8?q?=EC=96=B8=ED=8A=B8=20+=20=ED=99=88=20=ED=99=94=EB=A9=B4=20?= =?UTF-8?q?=EC=8B=A4=EB=8D=B0=EC=9D=B4=ED=84=B0)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - VITE_API_BASE_URL 기반 fetch 클라이언트 및 ApiError 처리 - api/spots, api/matches 타입드 모듈 - HomePage: 스팟/한줄평/메이트 실제 로드, 체크인·매칭 신청 API 연동 - 임시 세션 스토어(인증 도입 전, userId/dogId) - 로딩/에러/빈 상태 처리 Co-Authored-By: Claude Opus 4.8 (1M context) --- src/api/http.ts | 37 +++++++++++++ src/api/matches.ts | 18 ++++++ src/api/spots.ts | 39 +++++++++++++ src/pages/HomePage.vue | 122 +++++++++++++++++++++++++++++++---------- src/stores/session.ts | 13 +++++ 5 files changed, 200 insertions(+), 29 deletions(-) create mode 100644 src/api/http.ts create mode 100644 src/api/matches.ts create mode 100644 src/api/spots.ts create mode 100644 src/stores/session.ts diff --git a/src/api/http.ts b/src/api/http.ts new file mode 100644 index 0000000..3f5ed4c --- /dev/null +++ b/src/api/http.ts @@ -0,0 +1,37 @@ +// 공통 HTTP 클라이언트 — VITE_API_BASE_URL 기준 +const BASE_URL = import.meta.env.VITE_API_BASE_URL ?? 'http://localhost:8080' + +export class ApiError extends Error { + constructor( + public status: number, + public code: string, + message: string, + ) { + super(message) + this.name = 'ApiError' + } +} + +async function request(method: string, path: string, body?: unknown): Promise { + const res = await fetch(`${BASE_URL}${path}`, { + method, + headers: { 'Content-Type': 'application/json' }, + body: body !== undefined ? JSON.stringify(body) : undefined, + }) + + const text = await res.text() + const data = text ? JSON.parse(text) : null + + if (!res.ok) { + const code = (data && data.code) || 'ERROR' + const message = (data && data.message) || `요청 실패 (${res.status})` + throw new ApiError(res.status, code, message) + } + + return data as T +} + +export const http = { + get: (path: string) => request('GET', path), + post: (path: string, body?: unknown) => request('POST', path, body), +} diff --git a/src/api/matches.ts b/src/api/matches.ts new file mode 100644 index 0000000..57892d2 --- /dev/null +++ b/src/api/matches.ts @@ -0,0 +1,18 @@ +import { http } from './http' + +export interface Match { + id: number + requesterDogId: number + targetDogId: number + status: string + createdAt: string + respondedAt: string | null +} + +export const matchesApi = { + create: (requesterDogId: number, targetDogId: number) => + http.post('/api/matches', { requesterDogId, targetDogId }), + accept: (id: number) => http.post(`/api/matches/${id}/accept`), + reject: (id: number) => http.post(`/api/matches/${id}/reject`), + received: (dogId: number) => http.get(`/api/matches/received/${dogId}`), +} diff --git a/src/api/spots.ts b/src/api/spots.ts new file mode 100644 index 0000000..ebd3b69 --- /dev/null +++ b/src/api/spots.ts @@ -0,0 +1,39 @@ +import { http } from './http' + +export interface Spot { + id: number + name: string + type: string + latitude: number | null + longitude: number | null +} + +export interface Mate { + dogId: number + name: string + breed: string | null +} + +export interface Review { + id: number + tag: string | null + content: string | null + createdAt: string +} + +export interface CheckInResult { + id: number + spotId: number + dogId: number + expiresAt: string +} + +export const spotsApi = { + list: () => http.get('/api/spots'), + mates: (spotId: number) => http.get(`/api/spots/${spotId}/mates`), + reviews: (spotId: number) => http.get(`/api/spots/${spotId}/reviews`), + checkIn: (spotId: number, userId: number, dogId: number) => + http.post(`/api/spots/${spotId}/checkins`, { userId, dogId }), + addReview: (spotId: number, userId: number, tag: string) => + http.post(`/api/spots/${spotId}/reviews`, { userId, tag }), +} diff --git a/src/pages/HomePage.vue b/src/pages/HomePage.vue index d78f9bd..443869d 100644 --- a/src/pages/HomePage.vue +++ b/src/pages/HomePage.vue @@ -4,15 +4,25 @@
-
지도 영역 (스팟 앵커 표시 예정)
+
+ {{ currentSpot ? currentSpot.name : '스팟 불러오는 중…' }} +
+ + + + 백엔드에 연결하지 못했습니다. (서버 실행 여부를 확인하세요) + +
- 한강공원 산책로 · 오늘의 한줄평 + {{ currentSpot ? currentSpot.name : '스팟' }} · 오늘의 한줄평
-
+
- {{ review }} + {{ review.tag || review.content }}
+
아직 한줄평이 없어요.
지금 이 스팟의 산책 메이트
- +
체크인 중인 메이트가 없어요.
+ {{ mate.name }} - {{ mate.traits.join(' · ') }} + {{ mate.breed || '견종 미상' }} - + @@ -60,27 +74,77 @@ diff --git a/src/stores/session.ts b/src/stores/session.ts new file mode 100644 index 0000000..5257eef --- /dev/null +++ b/src/stores/session.ts @@ -0,0 +1,13 @@ +import { defineStore } from 'pinia' +import { ref } from 'vue' + +/** + * 임시 세션 스토어 (인증 도입 전까지 사용). + * local 시드 데이터 기준: 내 유저=1, 내 강아지(몽이)=1. + * 인증(4번) 도입 시 로그인 응답으로 대체. + */ +export const useSessionStore = defineStore('session', () => { + const userId = ref(1) + const dogId = ref(1) + return { userId, dogId } +})