API ๊ถํ ๋งคํธ๋ฆญ์ค (2๋จ๊ณ ์๋ฃ ์์ ๊ธฐ์ค)
์์ฑ์ผ: 2026-08-11
๋ฒ์: honeyRest_user(์ฌ์ฉ์ ๋ฐฑ์๋), honeyRest_host(ํธ์คํธ ๋ฐฑ์๋)
๊ธฐ์ค ์ปค๋ฐ: ๊ฐ ์ ์ฅ์ main ์ต์ (1๋จ๊ณ + 2๋จ๊ณ ๋ณํฉ ํ)
์ด ๋ฌธ์๋ ROADMAP.md 2๋จ๊ณ ํญ๋ชฉ 1ยท์๋ฃ ๊ฒ์ดํธ G2์ ์ฐ์ถ๋ฌผ์ด๋ค.
"์ธ๊ฐ ๊ทผ๊ฑฐ"๋ ๊ฐ ์๋ํฌ์ธํธ๊ฐ ์ค์ ๋ก ๋ฌด์์ ๊ธฐ์ค์ผ๋ก ์ ๊ทผ์ ํ์ฉ/๊ฑฐ๋ถํ๋์ง๋ฅผ ๊ฐ๋ฆฌํค๋ฉฐ,
์์ฒญ ๋ณธ๋ฌธยท์ฟผ๋ฆฌ์ id๋ฅผ ์ ๋ขฐํ์ง ์๋๋ค ๋ ์์น์ด ์ง์ผ์ง๋์ง๊ฐ ํต์ฌ ์ ๊ฒ ๋์์ด๋ค.
์ ์ฅ์
์ญํ
์๋ฏธ
์ฌ์ฉ์ ๋ฐฑ์๋
(์ต๋ช
)
๋ก๊ทธ์ธํ์ง ์์ ๋ฐฉ๋ฌธ์
์ฌ์ฉ์ ๋ฐฑ์๋
USER
๋ก๊ทธ์ธํ ์ผ๋ฐ ํ์ (JWT์ role ํด๋ ์์ด ์๋๋ผ ๋งค ์์ฒญ๋ง๋ค DB์์ ์ฌ์กฐํํ user.role)
์ฌ์ฉ์ ๋ฐฑ์๋
ADMIN
๋ฐฐ๋/์ด๋ฒคํธ/์์ ํ๊ทธ ์ฐ๊ธฐ ์ ์ฉ ์ญํ . ์ด ๋ฐฑ์๋์๋ ADMIN์ ์ค์ ๋ก ๋ถ์ฌํ๋ ๊ฐ์
๊ฒฝ๋ก๊ฐ ์๋ค โ ์ฆ ํ์ฌ๋ ์ฌ์ค์ ์ ๋ฉด ์ฐจ๋จ(fail-closed) ์ํ์ด๋ฉฐ, ํธ์คํธ ๋ฐฑ์๋๋ก์ ์ด๊ด ๋๋ ๋ณ๋ ๋ฐ๊ธ ์ ์ฐจ๊ฐ 3๋จ๊ณ ์ดํ ๊ณผ์ ๋ค
ํธ์คํธ ๋ฐฑ์๋
(์ต๋ช
)
๋ก๊ทธ์ธํ์ง ์์ ๋ฐฉ๋ฌธ์
ํธ์คํธ ๋ฐฑ์๋
COMPANY_ADMIN
์
์ ์
์ฒด ๊ด๋ฆฌ์. ์๊ธฐ ํ์ฌ ์์ ๋ฆฌ์์ค๋ง ์ ๊ทผ ๊ฐ๋ฅ
ํธ์คํธ ๋ฐฑ์๋
SUPER_ADMIN
์ด๊ด๋ฆฌ์. ์ ์
์ฒด ๋ฐ์ดํฐ ์ ๊ทผ ๋ฐ ์น์ธ ๊ถํ
์ ์ฅ์
์ ์ ํ๋ณ ์์ค
๋น๊ณ
์ฌ์ฉ์ ๋ฐฑ์๋
@AuthenticationPrincipal CustomUserPrincipal โ principal.getUserId()
JwtTokenProvider.getAuthentication()๊ฐ ๋งค ์์ฒญ๋ง๋ค userRepository.findById()๋ก DB ์ฌ์กฐํํ๋ฉฐ, tokenValidAfter(๋ก๊ทธ์์/๋น๋ฒ๋ณ๊ฒฝ ์ดํ ๋ฐ๊ธ ์ฌ๋ถ)์ status != DELETED๋ฅผ ํจ๊ป ๊ฒ์ฌํ๋ค
ํธ์คํธ ๋ฐฑ์๋
Authentication.getName() (JWT์ email ํด๋ ์) โ CompanyResourceAccessService.currentCompanyId(authentication)
์ปจํธ๋กค๋ฌ๋ง๋ค ํฉ์ด์ ธ ์๋ ์ ์ ํด์์ 1๋จ๊ณยท2๋จ๊ณ์์ ์ด ์๋น์ค๋ก ์ ์ง์ ์ผ๋ก ํตํฉํจ. @AuthenticationPrincipal User๋ ์ด ์ฑ์ principal ํ์
์ด String์ด๋ผ ํญ์ null์ด ๋๋ ๋ฒ๊ทธ๊ฐ ์์๊ณ (P0-3), ๋ฐ๊ฒฌ๋ ์ง์ ์ ๋ชจ๋ ์์ ํจ
3. ์ฌ์ฉ์ ๋ฐฑ์๋ โ ์๋ํฌ์ธํธ๋ณ ์ธ๊ฐ ๊ทผ๊ฑฐ
์๋ํฌ์ธํธ
๊ณต๊ฐ ๋ฒ์
์ธ๊ฐ ๊ทผ๊ฑฐ
์ํ
GET /api/accommodations/**, /api/event/**, /api/banner/**
์ต๋ช
ํฌํจ ์ ์ฒด ๊ณต๊ฐ
์์(์๋๋ ๊ณต๊ฐ ์กฐํ)
์์
POST/PUT/DELETE /api/accommodations/**, /api/event/**, /api/banner/**
ADMIN๋ง
hasRole("ADMIN") (๋ฐ๊ธ ๊ฒฝ๋ก ์์ด ์ฌ์ค์ ์ ๋ฉด ์ฐจ๋จ)
์์
GET /api/reserve/form-info
์ต๋ช
ํ์ฉ(๋นํ์ ์์ฝ ์ง์)
userId๋ฅผ ์ฟผ๋ฆฌ์์ ๋ฐ์ง ์๊ณ principal์์ ๋์ถ, ๋น๋ก๊ทธ์ธ ์ ๊ฐ์ธ์ ๋ณด ํ๋๋ ๋น์
์์ (P0-4 ์์ )
POST /api/payment/toss/confirm
USER
reservationInfo.userId๋ฅผ ์์ฒญ ๋ฐ๋๊ฐ์ผ๋ก ๋์ง ์๊ณ ํญ์ principal.getUserId()๋ก ๋ฎ์ด์
์์ (P0-7 ์์ )
POST /api/wishList/toggle
USER
dto.userId ๋์ principal.getUserId()
์์ (P1-4 ์์ )
POST /api/review/write
USER
reservation.user == principal && status == COMPLETED ๊ฒ์ฆ
์์ (P0-6 ์์ )
DELETE/POST /api/user/reviews/**
USER
์๋น์ค ๊ณ์ธต์์ review.user.userId == principal.userId
์์
GET/POST /api/user/reservations/**
USER
findByReservationIdAndUser_UserId๋ก ์กฐํ ์์ฒด๊ฐ ์ค์ฝํ๋จ
์์
GET /api/user/coupons, /point-history
USER
principal.getUserId() ๊ณ ์ , ์ฟผ๋ฆฌ ํ๋ผ๋ฏธํฐ๋ก ํ์ธ id ์ง์ ๋ถ๊ฐ
์์
POST /api/files/upload, DELETE /api/files
USER
folder ํ์ดํธ๋ฆฌ์คํธ(reviews,profile) + DB ์กฐํ๋ก ์ค์ ์์ ๋ฆฌ์์ค์ธ์ง ์ฌ๊ฒ์ฆ
์์ (P1-5 ์์ )
POST /api/user/email/verify-change
USER (2๋จ๊ณ์์ authenticated๋ก ์ ํ)
ํ ํฐ์ tokenType == EMAIL_CHANGE, pendingEmail == newEmail, ํธ์ถ์ == token.user ๋ชจ๋ ๊ฒ์ฆ
์์ (P0-8 ์์ )
POST /api/auth/logout
USER
๋ก๊ทธ์์ ์ user.tokenValidAfter๋ฅผ ๊ฐฑ์ ํด ๊ธฐ์กด access token์ ์ ๋ถ ๋ฌดํจํ
์์ (P1-10 ์์ )
4. ํธ์คํธ ๋ฐฑ์๋ โ ์๋ํฌ์ธํธ๋ณ ์ธ๊ฐ ๊ทผ๊ฑฐ
์๋ํฌ์ธํธ
๊ณต๊ฐ ๋ฒ์
์ธ๊ฐ ๊ทผ๊ฑฐ
์ํ
/admin/**
COMPANY_ADMIN
hasRole("COMPANY_ADMIN")
์์
/owner/**
SUPER_ADMIN
hasRole("SUPER_ADMIN")
์์
/api/owner/auth/signup
SUPER_ADMIN
/api/owner/**๋ฅผ hasRole("SUPER_ADMIN")๋ก ๋ช
์ ์ ๊ธ + ์๋น์ค๊ฐ ์์ฒญ role์ ๊ทธ๋๋ก ์ ๋ขฐํ์ง ์๊ณ ๊ฒ์ฆ๋ ํธ์ถ์ ์ปจํ
์คํธ์์๋ง role ์ง์ ํ์ฉ
์์ (P0-2 ์์ , ๊ฐ์ฅ ์ฌ๊ฐํ๋ ๊ถํ ์์น)
/api/admin/auth/signup, /login
์ต๋ช
(์ ๊ท ์
์ฒด ์๊ฐ ๊ฐ์
๋ชฉ์ )
์๋น์ค๊ฐ role์ ํญ์ COMPANY_ADMIN์ผ๋ก ๊ณ ์ , ์ด๋ฉ์ผ ์ค๋ณต ์ฒดํฌ ํ์ฑํ
์์
/admin/price/**(๊ฐ๊ฒฉ ์บ๋ฆฐ๋)
COMPANY_ADMIN
ํด๋ผ์ด์ธํธ๊ฐ ๋ณด๋ธ companyId๋ฅผ ์ ๋ขฐํ์ง ์๊ณ CompanyResourceAccessService.currentCompanyId(auth)๋ก ์ฌ๊ณ์ฐ, roomId/accommodationId๋ ์์ ๊ถ ๊ฒ์ฆ
์์ (P0-4 ์์ )
/admin/reviews/**, /admin/inquiries/** ๋จ๊ฑด ์กฐ์
COMPANY_ADMIN
ownsReview/ownsInquiry๋ก accommodation ์์ ๊ถ ์ญ์ถ์ ๊ฒ์ฆ
์์ (P0-5 ์์ )
/admin/point/refunds(๊ฒฐ์ /ํ๋ถ)
COMPANY_ADMIN
@AuthenticationPrincipal User(ํญ์ null์ด๋ ๋ฒ๊ทธ) ์ ๊ฑฐ, Authentication.getName() ์ฌ์ฉ. companyId ๋ชป ๊ตฌํ๋ฉด fail-closed ๋น ๋ชฉ๋ก
์์ (P0-3 ์์ )
/admin/accommodations/{id}/request
COMPANY_ADMIN
ํ์ฌ ์์ ๊ถ ํ์ธ + changeStatus๊ฐ PENDING ์ธ ๊ฐ์ ๊ฑฐ๋ถ(์๊ฐ ์น์ธ ์ฐจ๋จ)
์์ (P1-1 ์์ )
/owner/accommodation/{id}/approve, /reject
SUPER_ADMIN
/owner/** ์ ๊ธ + updateStatusIfCurrent๋ก PENDING โ ACTIVE/REJECTED ์์์ ์ ์ด๋ง ํ์ฉ
์์ (P1-1/P1-2 ์์ , ์ ์ค)
POST /admin/rooms/{roomId}
COMPANY_ADMIN
roomId ์์ ๊ถ + ํผ์ accommodationId๋ ๊ฐ์ ํ์ฌ ์์์ธ์ง ๊ฒ์ฆ
์์ (P1-3 ์์ )
์์ ์ด๋ฏธ์ง ์ญ์ (deleteSubImageIds)
COMPANY_ADMIN
accommodationId์ imageId๊ฐ ์ค์ ๋ก ์ฐ๊ฒฐ๋ ๊ฒฝ์ฐ๋ง ์ญ์ (deleteByAccommodation_AccommodationIdAndImageIdIn)
์์ (P1-4 ์์ )
JWT ์ธ์ฆ ์ ๋ฐ
-
typ ํด๋ ์์ด access์ธ ํ ํฐ๋ง ์ธ์ฆ์ ์ฌ์ฉ, refresh ํ ํฐ์ผ๋ก๋ ์ธ์ฆ ๋ถ๊ฐ
์์ (P1-7 ์์ )
ํผ ๊ธฐ๋ฐ ๊ด๋ฆฌ์ ์ฝ์ ์ ์ฒด (/admin/**, /owner/**์ POST)
COMPANY_ADMIN/SUPER_ADMIN
CookieCsrfTokenRepository + ์กฐ๊ธฐ ๋ก๋ฉ ํํฐ + MultipartFilter ์์ ์กฐ์ ์ผ๋ก ์ฌํ์ฑํ
์์ (2026-08-11 ์๋ฃ)
5. ์๋ ค์ง ์์ฌ ์ํ (3๋จ๊ณ ์ดํ๋ก ์ด์ฐ)
์์ฝ ์ฌ๊ณ /๋์์ฑ : ๋ ์ง๋ณ ์ฌ๊ณ ํ
์ด๋ธ์ด ์์ด ๋์ผ ๊ฐ์ค ์ค๋ณต ์์ฝ(์ค๋ฒ๋ถํน)์ด ๊ฐ๋ฅ. 3๋จ๊ณ ํญ๋ชฉ.
๊ฒฐ์ ๋ณด์ ํธ๋์ญ์
: Toss ์น์ธ ํ DB ์คํจ ์ ์๋ ์ทจ์ยท์ฌ์ฒ๋ฆฌ๊ฐ ์์. 4๋จ๊ณ ํญ๋ชฉ.
CSRF ์ฌํ์ฑํ : ์ ํ ์ฐธ๊ณ , ๋ณ๋ ์์
์ผ๋ก ๋ถ๋ฆฌ๋จ(task_1cdef24a).
principal resolver ์์ ํตํฉ : ํธ์คํธ ๋ฐฑ์๋๋ ์ด๋ฒ ๋จ๊ณ์์ ์๋ ์ปจํธ๋กค๋ฌ ์์ฃผ๋ก CompanyResourceAccessService์ ์๋ ด์์ผฐ์ผ๋, ์ ์ฒด ์ปจํธ๋กค๋ฌ๋ฅผ ๋์์ผ๋ก ํ ์ ์ ๊ฐ์ฌ๋ ์์ง ํ์ง ์์๋ค.
6. Gate G2 ์ฒดํฌ๋ฆฌ์คํธ