모든 API는 아래 구조로 응답합니다.
{
"code": "SUCCESS",
"message": "성공",
"data": { }
}🔒 표시된 API는 JWT 인증이 필요합니다.
요청 헤더에 아래를 포함해야 합니다.
Authorization: Bearer {token}
POST /api/auth/google
Request Body
{
"idToken": "Google ID Token"
}Response Header
Authorization: Bearer {jwt_token}
Response Body
{
"code": "SUCCESS",
"message": "성공",
"data": {
"userId": 1,
"email": "user@example.com",
"nickname": "홍길동",
"profileImage": "https://...",
"followerCount": 0,
"followingCount": 0
}
}GET /api/users/{userId}
Path Variable
| 이름 | 타입 | 설명 |
|---|---|---|
| userId | Long | 조회할 유저 ID |
Response
{
"code": "SUCCESS",
"message": "성공",
"data": {
"userId": 1,
"email": "user@example.com",
"nickname": "홍길동",
"profileImage": "https://...",
"followerCount": 10,
"followingCount": 5
}
}GET /api/users/me 🔒
Response
{
"code": "SUCCESS",
"message": "성공",
"data": {
"userId": 1,
"email": "user@example.com",
"nickname": "홍길동",
"profileImage": "https://...",
"followerCount": 10,
"followingCount": 5
}
}PATCH /api/users/me/nickname 🔒
Request Body
{
"nickname": "새닉네임"
}Response
{
"code": "SUCCESS",
"message": "성공",
"data": {
"userId": 1,
"email": "user@example.com",
"nickname": "새닉네임",
"profileImage": "https://...",
"followerCount": 10,
"followingCount": 5
}
}PATCH /api/users/me/profile-image 🔒
multipart/form-data로 요청합니다.
Request Parts
| 이름 | 타입 | 필수 | 설명 |
|---|---|---|---|
| image | File | Y | 변경할 프로필 이미지 파일 |
Response
{
"code": "SUCCESS",
"message": "성공",
"data": {
"userId": 1,
"email": "user@example.com",
"nickname": "홍길동",
"profileImage": "https://bucket-name.s3.ap-northeast-2.amazonaws.com/profile-images/abc123.jpg",
"followerCount": 10,
"followingCount": 5
}
}GET /api/users/search?query={query} 🔒
닉네임 또는 이메일로 검색합니다. 최대 20건 반환됩니다.
Query Parameter
| 이름 | 타입 | 필수 | 설명 |
|---|---|---|---|
| query | String | Y | 검색어 (닉네임 또는 이메일) |
Response
{
"code": "SUCCESS",
"message": "성공",
"data": [
{
"userId": 1,
"email": "user@example.com",
"nickname": "홍길동",
"profileImage": "https://...",
"me": false,
"following": true
}
]
}POST /api/follows/{followingId} 🔒
Path Variable
| 이름 | 타입 | 설명 |
|---|---|---|
| followingId | Long | 팔로우할 유저 ID |
Response
{
"code": "SUCCESS",
"message": "성공",
"data": null
}DELETE /api/follows/{followingId} 🔒
Path Variable
| 이름 | 타입 | 설명 |
|---|---|---|
| followingId | Long | 팔로우 취소할 유저 ID |
Response
{
"code": "SUCCESS",
"message": "성공",
"data": null
}GET /api/follows/{userId}/followers 🔒
나를 팔로우하는 유저 목록입니다.
Path Variable
| 이름 | 타입 | 설명 |
|---|---|---|
| userId | Long | 조회할 유저 ID |
Response
{
"code": "SUCCESS",
"message": "성공",
"data": [
{
"userId": 2,
"email": "user2@example.com",
"nickname": "이순신",
"profileImage": "https://...",
"me": false,
"following": false
}
]
}GET /api/follows/{userId}/followings 🔒
유저가 팔로우하는 목록입니다.
Path Variable
| 이름 | 타입 | 설명 |
|---|---|---|
| userId | Long | 조회할 유저 ID |
Response
{
"code": "SUCCESS",
"message": "성공",
"data": [
{
"userId": 3,
"email": "user3@example.com",
"nickname": "강감찬",
"profileImage": "https://...",
"me": false,
"following": true
}
]
}GET /api/follows/{userId}/counts 🔒
Path Variable
| 이름 | 타입 | 설명 |
|---|---|---|
| userId | Long | 조회할 유저 ID |
Response
{
"code": "SUCCESS",
"message": "성공",
"data": {
"userId": 1,
"followerCount": 10,
"followingCount": 5
}
}POST /api/photos 🔒
multipart/form-data로 요청합니다.
request 파트는 Content-Type: application/json으로 전송합니다.
최대 업로드 크기는 파일 10MB, 요청 전체 12MB입니다.
Request Parts
| 이름 | 타입 | 필수 | 설명 |
|---|---|---|---|
| image | File | Y | 업로드할 이미지 파일 |
| request | JSON | Y | 사진 메타데이터 |
request JSON
{
"photoSpotId": 1,
"sourcePhotoId": 10,
"tip": "오후 2시에 빛이 가장 예뻐요",
"mood": "CALM",
"timeTag": "AFTERNOON",
"photoType": "LANDSCAPE",
"crowdLevel": "RELAXED",
"photoVisibility": "PUBLIC"
}| 필드 | 타입 | 필수 | 값 |
|---|---|---|---|
| photoSpotId | Long | N | 포토스팟 ID |
| sourcePhotoId | Long | N | 따오기 원본 사진 ID. 일반 업로드에서는 생략 |
| tip | String | N | 촬영 팁 |
| mood | Enum | Y | CALM COZY EMOTIONAL LONELY ROMANTIC DREAMY VINTAGE FRESH HEALING SENTIMENTAL MODERN CINEMATIC QUIET BRIGHT NIGHT NOSTALGIC |
| timeTag | Enum | Y | MORNING AFTERNOON SUNSET NIGHT |
| photoType | Enum | Y | FULL_BODY UPPER_BODY SELFIE FOOD LANDSCAPE ETC |
| crowdLevel | Enum | Y | RELAXED NORMAL CROWDED HARD_TO_SHOOT |
| photoVisibility | Enum | Y | PUBLIC FRIENDS PRIVATE |
Response
{
"code": "SUCCESS",
"message": "성공",
"data": {
"photoId": 1,
"userId": 1,
"photoSpotId": 1,
"photoSpotTitle": "Main gate bench",
"placeId": 1,
"placeName": "Inha University",
"photoUrl": "https://bucket-name.s3.ap-northeast-2.amazonaws.com/photos/abc123.jpg",
"tip": "오후 2시에 빛이 가장 예뻐요",
"mood": "CALM",
"timeTag": "AFTERNOON",
"photoType": "LANDSCAPE",
"crowdLevel": "RELAXED",
"photoVisibility": "PUBLIC",
"createdAt": "2026-06-09T12:00:00",
"ddaomCount": 0
}
}GET /api/photos/{photoId} 🔒
Path Variable
| 이름 | 타입 | 설명 |
|---|---|---|
| photoId | Long | 조회할 사진 ID |
Response
{
"code": "SUCCESS",
"message": "성공",
"data": {
"photoId": 1,
"userId": 1,
"photoSpotId": 1,
"photoSpotTitle": "Main gate bench",
"placeId": 1,
"placeName": "Inha University",
"photoUrl": "https://bucket-name.s3.ap-northeast-2.amazonaws.com/photos/abc123.jpg",
"tip": "오후 2시에 빛이 가장 예뻐요",
"mood": "CALM",
"timeTag": "AFTERNOON",
"photoType": "LANDSCAPE",
"crowdLevel": "RELAXED",
"photoVisibility": "PUBLIC",
"createdAt": "2026-06-09T12:00:00",
"liked": false,
"likeCount": 10,
"ddaomCount": 3
}
}GET /api/photos/me 🔒
Response
{
"code": "SUCCESS",
"message": "성공",
"data": [
{
"photoId": 1,
"userId": 1,
"photoSpotId": 1,
"photoSpotTitle": "Main gate bench",
"placeId": 1,
"placeName": "Inha University",
"photoUrl": "https://bucket-name.s3.ap-northeast-2.amazonaws.com/photos/abc123.jpg",
"tip": "오후 2시에 빛이 가장 예뻐요",
"mood": "CALM",
"timeTag": "AFTERNOON",
"photoType": "LANDSCAPE",
"crowdLevel": "RELAXED",
"photoVisibility": "PUBLIC",
"createdAt": "2026-06-09T12:00:00",
"liked": false,
"likeCount": 0
}
]
}GET /api/photos/users/{userId}
Path Variable
| 이름 | 타입 | 설명 |
|---|---|---|
| userId | Long | 조회할 유저 ID |
Response
{
"code": "SUCCESS",
"message": "성공",
"data": [
{
"photoId": 1,
"userId": 2,
"photoSpotId": 1,
"photoSpotTitle": "Main gate bench",
"placeId": 1,
"placeName": "Inha University",
"photoUrl": "https://bucket-name.s3.ap-northeast-2.amazonaws.com/photos/abc123.jpg",
"tip": "오후 2시에 빛이 가장 예뻐요",
"mood": "CALM",
"timeTag": "AFTERNOON",
"photoType": "LANDSCAPE",
"crowdLevel": "RELAXED",
"photoVisibility": "PUBLIC",
"createdAt": "2026-06-09T12:00:00",
"liked": false,
"likeCount": 0
}
]
}GET /api/photos/following 🔒
내가 팔로우하는 유저들이 최근 1주일간 올린 사진 목록을 최신순으로 반환합니다.
Response
{
"code": "SUCCESS",
"message": "성공",
"data": [
{
"photoId": 2,
"userId": 3,
"nickname": "이순신",
"profileImage": "https://...",
"photoSpotId": 1,
"photoSpotTitle": "Main gate bench",
"placeId": 1,
"placeName": "Inha University",
"photoUrl": "https://bucket-name.s3.ap-northeast-2.amazonaws.com/photos/abc456.jpg",
"tip": "해질 무렵이 가장 예뻐요",
"mood": "ROMANTIC",
"timeTag": "SUNSET",
"photoType": "LANDSCAPE",
"crowdLevel": "NORMAL",
"photoVisibility": "PUBLIC",
"createdAt": "2026-06-09T18:30:00",
"liked": false,
"likeCount": 0
}
]
}GET /api/photos?photoSpotId={photoSpotId} 🔒
Query Parameter
| 이름 | 타입 | 필수 | 설명 |
|---|---|---|---|
| photoSpotId | Long | Y | 조회할 포토스팟 ID |
Response
{
"code": "SUCCESS",
"message": "성공",
"data": [
{
"photoId": 1,
"userId": 1,
"photoSpotId": 1,
"photoSpotTitle": "Main gate bench",
"placeId": 1,
"placeName": "Inha University",
"photoUrl": "https://bucket-name.s3.ap-northeast-2.amazonaws.com/photos/abc123.jpg",
"tip": "오후 2시에 빛이 가장 예뻐요",
"mood": "CALM",
"timeTag": "AFTERNOON",
"photoType": "LANDSCAPE",
"crowdLevel": "RELAXED",
"photoVisibility": "PUBLIC",
"createdAt": "2026-06-09T12:00:00",
"liked": false,
"likeCount": 0
}
]
}GET /api/photos/likes/top
최근 1주일간 좋아요가 가장 많은 사진을 최대 12개 반환합니다.
Response
{
"code": "SUCCESS",
"message": "성공",
"data": [
{
"photoId": 1,
"userId": 1,
"nickname": "홍길동",
"profileImage": "https://...",
"photoSpotId": 1,
"photoSpotTitle": "Main gate bench",
"placeId": 1,
"placeName": "Inha University",
"photoUrl": "https://bucket-name.s3.ap-northeast-2.amazonaws.com/photos/abc123.jpg",
"tip": "오후 2시에 빛이 가장 예뻐요",
"mood": "CALM",
"timeTag": "AFTERNOON",
"photoType": "LANDSCAPE",
"crowdLevel": "RELAXED",
"photoVisibility": "PUBLIC",
"createdAt": "2026-06-09T12:00:00",
"liked": false,
"likeCount": 0
}
]
}POST /api/photos/{photoId}/likes 🔒
Path Variable
| 이름 | 타입 | 설명 |
|---|---|---|
| photoId | Long | 좋아요할 사진 ID |
Response
{
"code": "SUCCESS",
"message": "성공",
"data": {
"photoId": 1,
"liked": true,
"likeCount": 10
}
}DELETE /api/photos/{photoId}/likes 🔒
Path Variable
| 이름 | 타입 | 설명 |
|---|---|---|
| photoId | Long | 좋아요 취소할 사진 ID |
Response
{
"code": "SUCCESS",
"message": "성공",
"data": {
"photoId": 1,
"liked": false,
"likeCount": 9
}
}DELETE /api/photos/{photoId} 🔒
사진 작성자만 삭제할 수 있습니다. 삭제 시 사진 좋아요/저장 참조를 먼저 정리한 뒤 Photo 레코드를 삭제합니다. S3에 저장된 이미지 객체도 함께 삭제를 시도합니다.
Path Variable
| 이름 | 타입 | 설명 |
|---|---|---|
| photoId | Long | 삭제할 사진 ID |
Response
{
"code": "SUCCESS",
"message": "성공",
"data": null
}GET /api/photos/likes/me 🔒
Response
{
"code": "SUCCESS",
"message": "성공",
"data": [
{
"photoId": 1,
"userId": 1,
"photoSpotId": 1,
"photoSpotTitle": "Main gate bench",
"placeId": 1,
"placeName": "Inha University",
"photoUrl": "https://bucket-name.s3.ap-northeast-2.amazonaws.com/photos/abc123.jpg",
"tip": "오후 2시에 빛이 가장 예뻐요",
"mood": "CALM",
"timeTag": "AFTERNOON",
"photoType": "LANDSCAPE",
"crowdLevel": "RELAXED",
"photoVisibility": "PUBLIC",
"createdAt": "2026-06-09T12:00:00",
"liked": true,
"likeCount": 5
}
]
}POST /api/places 🔒
Request Body
{
"name": "경복궁",
"address": "서울 종로구 사직로 161",
"category": "문화유적",
"latitude": 37.579617,
"longitude": 126.977041
}| 필드 | 타입 | 필수 | 설명 |
|---|---|---|---|
| name | String | Y | 장소명 |
| address | String | Y | 주소 |
| category | String | Y | 카테고리 |
| latitude | BigDecimal | Y | 위도 |
| longitude | BigDecimal | Y | 경도 |
Response
{
"code": "SUCCESS",
"message": "성공",
"data": {
"placeId": 1,
"name": "경복궁",
"address": "서울 종로구 사직로 161",
"category": "문화유적",
"latitude": 37.579617,
"longitude": 126.977041,
"photoSpotCount": 0,
"photoSpots": []
}
}GET /api/places 🔒
지도 화면의 범위(위경도)와 카테고리로 필터링합니다. 파라미터를 생략하면 전체 조회됩니다.
Query Parameters
| 이름 | 타입 | 필수 | 설명 |
|---|---|---|---|
| minLatitude | BigDecimal | N | 최소 위도 |
| maxLatitude | BigDecimal | N | 최대 위도 |
| minLongitude | BigDecimal | N | 최소 경도 |
| maxLongitude | BigDecimal | N | 최대 경도 |
| category | String | N | 카테고리 |
Response
{
"code": "SUCCESS",
"message": "성공",
"data": [
{
"placeId": 1,
"name": "경복궁",
"address": "서울 종로구 사직로 161",
"category": "문화유적",
"latitude": 37.579617,
"longitude": 126.977041,
"photoSpotCount": 5,
"thumbnailUrl": "https://bucket-name.s3.ap-northeast-2.amazonaws.com/photo-spots/thumb.jpg"
}
]
}GET /api/places/{placeId} 🔒
Path Variable
| 이름 | 타입 | 설명 |
|---|---|---|
| placeId | Long | 조회할 장소 ID |
Response
{
"code": "SUCCESS",
"message": "성공",
"data": {
"placeId": 1,
"name": "경복궁",
"address": "서울 종로구 사직로 161",
"category": "문화유적",
"latitude": 37.579617,
"longitude": 126.977041,
"photoSpotCount": 5,
"photoSpots": [
{
"photoSpotId": 1,
"title": "근정전 앞",
"description": "정문에서 바라보는 근정전",
"imageUrl": "https://bucket-name.s3.ap-northeast-2.amazonaws.com/photo-spots/spot1.jpg",
"photoCount": 3
}
],
"saved": false
}
}PUT /api/places/{placeId} 🔒
Path Variable
| 이름 | 타입 | 설명 |
|---|---|---|
| placeId | Long | 수정할 장소 ID |
Request Body
{
"name": "경복궁",
"address": "서울 종로구 사직로 161",
"category": "문화유적",
"latitude": 37.579617,
"longitude": 126.977041
}| 필드 | 타입 | 필수 | 설명 |
|---|---|---|---|
| name | String | Y | 장소명 |
| address | String | Y | 주소 |
| category | String | Y | 카테고리 |
| latitude | BigDecimal | Y | 위도 |
| longitude | BigDecimal | Y | 경도 |
Response
{
"code": "SUCCESS",
"message": "성공",
"data": {
"placeId": 1,
"name": "경복궁",
"address": "서울 종로구 사직로 161",
"category": "문화유적",
"latitude": 37.579617,
"longitude": 126.977041,
"photoSpotCount": 5,
"photoSpots": [
{
"photoSpotId": 1,
"title": "근정전 앞",
"description": "정문에서 바라보는 근정전",
"imageUrl": "https://bucket-name.s3.ap-northeast-2.amazonaws.com/photo-spots/spot1.jpg"
}
]
}
}DELETE /api/places/{placeId} 🔒
Path Variable
| 이름 | 타입 | 설명 |
|---|---|---|
| placeId | Long | 삭제할 장소 ID |
Response
{
"code": "SUCCESS",
"message": "성공",
"data": null
}POST /api/places/{placeId}/likes 🔒
Path Variable
| 이름 | 타입 | 설명 |
|---|---|---|
| placeId | Long | 좋아요할 장소 ID |
Response
{
"code": "SUCCESS",
"message": "성공",
"data": null
}DELETE /api/places/{placeId}/likes 🔒
Path Variable
| 이름 | 타입 | 설명 |
|---|---|---|
| placeId | Long | 좋아요 취소할 장소 ID |
Response
{
"code": "SUCCESS",
"message": "성공",
"data": null
}GET /api/places/saves/me 🔒
저장한 최신순으로 반환합니다.
Response
{
"code": "SUCCESS",
"message": "성공",
"data": [
{
"placeId": 1,
"name": "경복궁",
"address": "서울 종로구 사직로 161",
"category": "문화유적",
"latitude": 37.579617,
"longitude": 126.977041,
"photoSpotCount": 5,
"thumbnailUrl": "https://bucket-name.s3.ap-northeast-2.amazonaws.com/photo-spots/thumb.jpg"
}
]
}POST /api/places/{placeId}/saves 🔒
Path Variable
| 이름 | 타입 | 설명 |
|---|---|---|
| placeId | Long | 저장할 장소 ID |
Response
{
"code": "SUCCESS",
"message": "성공",
"data": null
}DELETE /api/places/{placeId}/saves 🔒
Path Variable
| 이름 | 타입 | 설명 |
|---|---|---|
| placeId | Long | 저장 취소할 장소 ID |
Response
{
"code": "SUCCESS",
"message": "성공",
"data": null
}POST /api/places/{placeId}/photo-spots 🔒
특정 Place에 PhotoSpot을 추가합니다.
두 가지 요청 방식을 지원합니다.
application/json:POST /api/uploads/images로 먼저 S3 URL을 받은 뒤imageUrl로 전달합니다.multipart/form-data: 이미지 파일을 직접 함께 전송하면 S3에 업로드한 뒤 반환된 URL을 저장합니다.
multipart 방식의 최대 업로드 크기는 파일 10MB, 요청 전체 12MB입니다.
Path Variable
| 이름 | 타입 | 설명 |
|---|---|---|
| placeId | Long | PhotoSpot을 추가할 장소 ID |
Request Body (application/json)
{
"title": "근정전 앞",
"description": "정면 구도로 찍기 좋은 위치",
"imageUrl": "https://bucket-name.s3.ap-northeast-2.amazonaws.com/uploads/images/example.jpg"
}Request Parts (multipart/form-data)
| 이름 | 타입 | 필수 | 설명 |
|---|---|---|---|
| image | File | Y | PhotoSpot 대표 이미지 파일 |
| request | JSON | Y | PhotoSpot 메타데이터 |
request JSON
{
"title": "근정전 앞",
"description": "정면 구도로 찍기 좋은 위치"
}| 필드 | 타입 | 필수 | 설명 |
|---|---|---|---|
| title | String | Y | PhotoSpot 이름 |
| description | String | N | PhotoSpot 설명 |
| imageUrl | String | N | JSON 요청에서 사용하는 이미지 URL |
Response
{
"code": "SUCCESS",
"message": "성공",
"data": {
"photoSpotId": 1,
"title": "근정전 앞",
"description": "정면 구도로 찍기 좋은 위치",
"imageUrl": "https://bucket-name.s3.ap-northeast-2.amazonaws.com/photo-spots/spot1.jpg"
}
}DELETE /api/places/{placeId}/photo-spots/{photoSpotId} 🔒
Path Variables
| 이름 | 타입 | 설명 |
|---|---|---|
| placeId | Long | PhotoSpot이 속한 장소 ID |
| photoSpotId | Long | 삭제할 PhotoSpot ID |
Response
{
"code": "SUCCESS",
"message": "성공",
"data": null
}이미 사진이 등록된 PhotoSpot은 삭제할 수 없으며 400 응답을 반환합니다.
{
"code": "E400",
"message": "PhotoSpot has photos and cannot be deleted.",
"data": null
}POST /api/uploads/images 🔒
이미지 파일만 S3에 업로드하고 접근 가능한 URL을 반환합니다. 피드 게시물이나 PhotoSpot 레코드는 생성하지 않습니다.
multipart/form-data로 요청합니다.
최대 업로드 크기는 파일 10MB, 요청 전체 12MB입니다.
Request Parts
| 이름 | 타입 | 필수 | 설명 |
|---|---|---|---|
| image | File | Y | S3에 업로드할 이미지 파일 |
Response
{
"code": "SUCCESS",
"message": "성공",
"data": {
"imageUrl": "https://bucket-name.s3.ap-northeast-2.amazonaws.com/uploads/images/example.jpg"
}
}