-
Notifications
You must be signed in to change notification settings - Fork 0
V2 Group API
모임 생성 또는 수정 전에 이미지를 사전 업로드하는 API입니다.
업로드된 파일은 즉시 저장소(S3)에 업로드되며, 서버는 업로드 결과로 imageKey + 리사이즈된 URL 세트(440x240, 100x100) 를 반환합니다.
이 API에서 발급된 imageKey는 이후 모임 생성 / 모임 수정에서만 1회 사용 가능(consumed) 합니다.
📌 역할 분리(중요)
- Pre-upload 단계의
sortOrder는 “업로드 요청 배열의 순서”를 그대로 반영한 값입니다.- 최종 정렬/대표 이미지 확정은 Create/Update 요청의
images.sortOrder규칙이 책임집니다.
Headers
| 이름 | 값 |
|---|---|
| Authorization | Bearer {JWT} |
| Content-Type | multipart/form-data |
Body (multipart/form-data)
| 필드명 | 타입 | 필수 | 설명 |
|---|---|---|---|
| images | File[] | O | 업로드할 이미지 파일 목록 (1~3장) |
📌 업로드 정책
- 이미지 개수는 최소 1장, 최대 3장
-
images필드가 없거나 빈 배열이면 오류 - 업로드된 이미지는 서버에서 WEBP 포맷으로 변환됩니다.
- 서버는 업로드된 이미지 1장당 아래 2가지 변형(Variant)을 생성합니다.
- CARD: 440x240 (WEBP)
- THUMBNAIL: 100x100 (WEBP)
- 업로드 요청 내 이미지 순서가 pre-upload 응답의 sortOrder(0부터) 로 고정됩니다.
📌 업로드 제한 (추가)
- 허용 MIME 타입:
image/jpeg,image/png,image/webp - 파일 최대 크기: 각 파일 5MB 이하
- 요청 총합 크기: 최대 15MB
- 이미지 최소 해상도(권장): 가로 440px 이상
- 최소 해상도 미만 파일은 오류로 처리될 수 있습니다.
📥 Example Request
POST /api/v2/groups/images/upload
Authorization: Bearer {JWT}
Content-Type: multipart/form-data
images: file1
images: file2{
"status": 201,
"success": true,
"data": {
"images": [
{
"imageKey": "8f982cf4-42a3-4bad-b1d8-e870026bd013",
"sortOrder": 0,
"imageUrl440x240": "https://we-go-bucket.s3.ap-northeast-2.amazonaws.com/20251222141747_0_8f982cf4-42a3-4bad-b1d8-e870026bd013_440x240.webp",
"imageUrl100x100": "https://we-go-bucket.s3.ap-northeast-2.amazonaws.com/20251222141747_0_8f982cf4-42a3-4bad-b1d8-e870026bd013_100x100.webp"
},
{
"imageKey": "e7e36318-b34d-41d4-aaed-c200e0db6fc4",
"sortOrder": 1,
"imageUrl440x240": "https://we-go-bucket.s3.ap-northeast-2.amazonaws.com/20251222141748_1_e7e36318-b34d-41d4-aaed-c200e0db6fc4_440x240.webp",
"imageUrl100x100": "https://we-go-bucket.s3.ap-northeast-2.amazonaws.com/20251222141748_1_e7e36318-b34d-41d4-aaed-c200e0db6fc4_100x100.webp"
}
]
}
}📌 Response 모델
PreUploadGroupImageV2Response
| 필드 | 타입 | 설명 |
|---|---|---|
| images | PreUploadGroupImageV2Item[] | 업로드 결과 이미지 목록 |
PreUploadGroupImageV2Item
| 필드 | 타입 | 설명 |
|---|---|---|
| imageKey | String | 이후 create/update에서 사용하는 키 |
| sortOrder | Integer | 업로드 요청 내 이미지 순서(0부터) |
| imageUrl440x240 | String | 카드용 이미지 URL |
| imageUrl100x100 | String | 썸네일 이미지 URL |
🧾 정책 및 주의사항 (필독)
- imageKey는 “사전 업로드 결과 키”입니다.
- 모임 생성/수정 시 서버는 imageKey를 통해 서버가 보증한 이미지 URL 세트를 확정 등록합니다.
- 유효기간(TTL)은 2시간입니다.
- 2시간이 지나면 해당 imageKey는 서버에서 찾을 수 없어 사용 불가합니다.
- 업로더만 사용할 수 있습니다.
- imageKey에는 uploaderId가 함께 저장됩니다.
- 모임 생성/수정 요청자와 uploaderId가 다르면 오류가 발생합니다.
- imageKey는 1회성(consumed)입니다.
- 모임 생성/수정에서 사용되는 순간 Redis에서 조회 후 즉시 삭제(getAndDelete) 됩니다.
- 한 번 사용되었거나 만료된 imageKey는 다시 사용할 수 없습니다.
- 최대 업로드 3장 제한
- 4장 이상 업로드 요청 시 오류로 처리됩니다.
❗ 오류 케이스 (대표)
에러 응답 포맷은 프로젝트 공통 규격을 따릅니다.
| HTTP | ErrorCode | 조건 |
|---|---|---|
| 401 | UNAUTHORIZED | JWT 없음 / 만료 / 위조 |
| 400 | INVALID_MULTIPART_REQUEST | multipart 형식 오류 / images 파트 누락 |
| 400 | INVALID_IMAGE_COUNT | images가 빈 배열(0장 업로드) |
| 400 | IMAGE_UPLOAD_EXCEED | 이미지 3장 초과 |
| 400 | IMAGE_UPLOAD_FAILED | 이미지 변환, 리사이즈, S3 업로드 실패 |
| 415 | UNSUPPORTED_IMAGE_TYPE | 허용되지 않은 MIME 타입 |
| 413 | IMAGE_FILE_TOO_LARGE | 파일 크기/요청 총합 크기 제한 초과 |
사전 업로드된 이미지(imageKey)를 사용하여 새로운 모임을 생성하는 API입니다.
모임 생성에 성공하면, 요청자는 자동으로 HOST 역할 + ATTEND 상태로 모임에 참여합니다.
또한 운영 안정성을 위해, 같은 사용자가 연속으로 모임을 생성하지 못하도록 생성 쿨다운 정책이 적용됩니다.
Headers
| 이름 | 값 |
|---|---|
| Authorization | Bearer {JWT} |
| Content-Type | application/json |
📥 Body – CreateGroupV2Request (Example)
⚠️ 반드시 사전 업로드 응답의images[].imageKey를 전역 변수로 저장한 뒤, 아래images배열을 구성합니다.⚠️ images: []를 “빈 배열”로 보내지 마세요. (이미지를 쓰지 않을 거면 필드를 생략하거나null로 처리)
{
"title": "강남에서 하는 자바 스터디",
"location": "서울 강남구",
"joinPolicy": "FREE",
"locationDetail": "강남역 2번 출구 근처 카페",
"startTime": "2026-12-10T19:00:00",
"endTime": "2026-12-10T21:00:00",
"tags": [
"자바",
"백엔드",
"스터디"
],
"description": "사전 업로드 imageKey를 사용하여 모임을 생성합니다.",
"maxParticipants": 12,
"images": [
{
"sortOrder": 0,
"imageKey": "{{img0_key}}"
},
{
"sortOrder": 1,
"imageKey": "{{img1_key}}"
}
]
}✅ 요청 필드 설명
| 필드 | 타입 | 필수 | 설명 |
|---|---|---|---|
| title | String | O | 모임 제목 (1~50자, 공백 불가) |
| location | String | O | 모임 위치 |
| joinPolicy | String | O | 모임 참여 정책 |
| locationDetail | String | X | 상세 위치 |
| startTime | LocalDateTime | O | 시작 시간(현재 이후) |
| endTime | LocalDateTime | X | 종료 시간(startTime 이후) |
| tags | String[] | X | 태그(최대 10개, 중복 불가) |
| description | String | O | 모임 설명 (1~300자) |
| maxParticipants | Integer | O | 최대 인원 (2~12) |
| images | CreateGroupImageV2Request[] | X | 사전 업로드 이미지 목록 (최대 3장) |
📌 CreateGroupImageV2Request
| 필드 | 타입 | 필수 | 설명 |
|---|---|---|---|
| imageKey | String | O | 사전 업로드로 발급된 imageKey |
| sortOrder | Integer | X | 이미지 순서(0 이상). 없으면 서버가 자동 부여 |
🧾 이미지 등록 정책 (생성 시)
-
images는 선택 항목입니다.- 생략 또는
null→ 이미지 없음 -
images = []→ 허용하지 않음(오류)- “이미지 없음”은 필드 생략 또는 null로 통일합니다.
- 생략 또는
- 이미지가 있다면 최대 3장까지 가능합니다.
- 각 이미지 항목에는 반드시
imageKey가 필요합니다. -
sortOrder정책- 모든 항목의
sortOrder가 없으면 → 요청 배열 순서대로0, 1, 2 …자동 부여 - 하나라도
sortOrder가 존재하면 →sortOrder오름차순으로 최종 정렬 - 요청 내
sortOrder중복은 허용되지 않습니다.
- 모든 항목의
-
imageKey는 반드시- 사전 업로드(pre-upload) API에서 발급된 값이어야 하며
- 업로더(uploaderId)와 요청자가 동일해야 하고
- 생성 과정에서 1회 사용 후 즉시 consume 됩니다.
-
imageKey는 요청 내 중복 불가입니다.
🧾 태그 정책 (생성 시)
-
tags는 선택 항목입니다. - 전달된 태그는 서버에서 findOrCreate 방식으로 처리됩니다.
- 정책
- 최대 10개
- 중복 불가
- 공백/빈 문자열 제거 후 처리
{
"status": 201,
"success": true,
"data": {
"id": 2,
"title": "강남에서 하는 자바 스터디",
"joinPolicy": "FREE",
"status": "RECRUITING",
"address": {
"location": "서울 강남구",
"locationDetail": "강남역 2번 출구 근처 카페"
},
"startTime": "2026-12-10T19:00:00",
"endTime": "2026-12-10T21:00:00",
"tags": [
"자바",
"백엔드",
"스터디"
],
"description": "사전 업로드 imageKey를 사용하여 모임을 생성합니다.",
"participantCount": 1,
"maxParticipants": 12,
"createdBy": {
"userId": 102,
"nickName": "Beemo",
"profileImage": null,
"profileMessage": null
},
"myMembership": {
"groupUserId": 2,
"userId": 102,
"role": "HOST",
"status": "ATTEND",
"joinedAt": "2025-12-23T17:07:35.269796",
"leftAt": null
},
"createdAt": "2025-12-23T17:07:35.2783251",
"updatedAt": "2025-12-23T17:07:35.2783251",
"images": [
{
"groupImageId": 5,
"imageKey": "1624e5ec-843d-477d-afb0-9bea75a4f9d3",
"sortOrder": 0,
"variants": [
{
"variantId": 9,
"type": "CARD_440_240",
"width": 440,
"height": 240,
"format": "WEBP",
"imageUrl": "https://we-go-bucket.s3.ap-northeast-2.amazonaws.com/20251223170731_0_1624e5ec-843d-477d-afb0-9bea75a4f9d3_440x240.webp"
},
{
"variantId": 10,
"type": "THUMBNAIL_100_100",
"width": 100,
"height": 100,
"format": "WEBP",
"imageUrl": "https://we-go-bucket.s3.ap-northeast-2.amazonaws.com/20251223170731_0_1624e5ec-843d-477d-afb0-9bea75a4f9d3_100x100.webp"
}
]
}
]
}
}🧾 특징 및 주의사항
- 생성 쿨다운 정책
- 같은 사용자는 연속으로 모임을 생성할 수 없습니다.
- 쿨다운 중 재시도하면 오류가 발생합니다.
- 생성 트랜잭션이 실패(롤백)되면 쿨다운도 적용되지 않습니다.
- 호스트 자동 참여
- 모임 생성자는 자동으로
HOST + ATTEND상태로 참여합니다.
- 이미지 pre-upload key는 생성 시 consume
- 사용된
imageKey는 즉시 삭제되며 재사용할 수 없습니다.
❗ 오류 케이스 (대표)
| HTTP | ErrorCode | 조건 |
|---|---|---|
| 401 | UNAUTHORIZED | 인증 실패 |
| 400 | VALIDATION_FAILED | 필드 유효성 검증 실패 |
| 404 | USER_NOT_FOUND | 생성자(호스트) 조회 실패 |
| 409 | GROUP_CREATE_COOLDOWN | 생성 쿨다운 활성화 |
| 400 | IMAGE_KEY_NOT_FOUND | pre-upload key 만료/이미 사용됨 |
| 403 | IMAGE_KEY_UPLOADER_MISMATCH | 업로더와 요청자 불일치 |
| 400 | IMAGE_UPLOAD_EXCEED | 이미지 3장 초과 |
| 400 | DUPLICATED_IMAGE_SORT_ORDER_IN_REQUEST | sortOrder 중복 |
| 400 | DUPLICATED_IMAGE_KEY_IN_REQUEST | imageKey 중복 |
| 400 | INVALID_GROUP_IMAGE_ITEM | images 항목 구조 오류 |
| 400 | INVALID_GROUP_IMAGE_KEY | imageKey null/blank |
| 400 | INVALID_GROUP_IMAGE_EMPTY_LIST | images가 빈 배열로 전달됨 |
특정 모임의 상세 정보를 조회하는 API입니다.
비로그인 사용자도 조회할 수 있으며, 로그인 여부 및 조회자의 역할(HOST 여부)에 따라 응답에 포함되는 정보 범위가 달라집니다.
- 비로그인 사용자: 모임 기본 정보 + 이미지 + 태그 + 참여자 요약 정보 조회 가능
-
로그인 사용자:
myMembership(내 참여 정보) 제공 -
호스트(HOST):
joinedMembers에 모든 멤버 상태 노출 -
호스트가 아닌 사용자:
joinedMembers에 ATTEND 상태만 노출
Headers (선택)
비로그인 조회가 가능하므로 Authorization 헤더는 선택입니다.
| 이름 | 값 | 필수 |
|---|---|---|
| Authorization | Bearer {JWT} | X |
Path Variables
| 이름 | 타입 | 필수 | 설명 |
|---|---|---|---|
| groupId | Long | O | 조회할 모임 ID |
📥 Example Request
GET /api/v2/groups/10
Authorization: Bearer {JWT} // 로그인한 경우에만 전달
⚠️ 응답의images는 항상sortOrder오름차순으로 정렬되며,sortOrder=0이 대표 이미지입니다.⚠️ 비로그인인 경우myMembership는 반드시 null 입니다.
{
"status": 200,
"success": true,
"data": {
"id": 1,
"title": "참여 취소 테스트용 자바 스터디",
"joinPolicy": "FREE",
"status": "RECRUITING",
"address": {
"location": "서울 서초구",
"locationDetail": "교대역 1번 출구 근처 카페"
},
"startTime": "2026-12-20T19:00:00",
"endTime": "2026-12-20T21:00:00",
"images": [
{
"groupImageId": 1,
"imageKey": "12a420df-708e-43d3-8689-8b92b5b99419",
"sortOrder": 0,
"variants": [
{
"variantId": 1,
"type": "CARD_440_240",
"width": 440,
"height": 240,
"format": "WEBP",
"imageUrl": "https://we-go-bucket.s3.ap-northeast-2.amazonaws.com/20251223172948_0_12a420df-708e-43d3-8689-8b92b5b99419_440x240.webp"
},
{
"variantId": 2,
"type": "THUMBNAIL_100_100",
"width": 100,
"height": 100,
"format": "WEBP",
"imageUrl": "https://we-go-bucket.s3.ap-northeast-2.amazonaws.com/20251223172948_0_12a420df-708e-43d3-8689-8b92b5b99419_100x100.webp"
}
]
}
],
"tags": ["참여취소테스트", "자바"],
"description": "참여 취소/중복 취소/HOST 취소 예외를 테스트하는 모임입니다.",
"participantCount": 3,
"maxParticipants": 5,
"createdBy": {
"userId": 102,
"nickName": "HostTwo",
"profileImage": null,
"profileMessage": null
},
"createdAt": "2025-12-23T17:29:52.273873",
"updatedAt": "2025-12-23T17:29:52.273873",
"myMembership": {
"groupUserId": 3,
"role": "MEMBER",
"status": "ATTEND",
"joinedAt": "2025-12-23T17:30:09.180863",
"leftAt": null
},
"joinedMembers": [
{
"userId": 102,
"groupUserId": 1,
"role": "HOST",
"status": "ATTEND",
"nickName": "HostTwo",
"profileImage": null,
"joinedAt": "2025-12-23T17:29:52.255956",
"leftAt": null
}
]
}
}✅ 응답 필드 설명 (핵심)
| 필드 | 타입 | 설명 |
|---|---|---|
| id | Long | 모임 ID |
| title | String | 모임 제목 |
| joinPolicy | String | 모임 참여 정책 |
| status | GroupV2Status | 모임 상태 |
| address | Address | 위치 정보 |
| startTime | LocalDateTime | 시작 시간 |
| endTime | LocalDateTime | 종료 시간 |
| images | GroupImageItem[] | 이미지 목록(variants 포함) |
| tags | String[] | 태그 목록 |
| description | String | 모임 설명 |
| participantCount | long | 현재 ATTEND 인원 수 |
| maxParticipants | int | 최대 인원 |
| createdBy | CreatedBy | 호스트 정보 |
| createdAt | LocalDateTime | 생성 시각 |
| updatedAt | LocalDateTime | 수정 시각 |
| myMembership | MyMembership | 내 참여 정보(로그인 시) |
| joinedMembers | JoinedMember[] | 참여자 목록(권한별 노출) |
🧾 정책 및 주의사항 (필독)
1) myMembership 노출 정책
-
비로그인:
myMembership = null -
로그인
- 참여 이력이 있으면 해당 멤버십 정보 반환
- 참여 이력이 없으면
null
2) joinedMembers 노출 정책
-
HOST가 조회한 경우
- 모든 멤버 노출:
ATTEND,LEFT,KICKED,BANNED -
status,leftAt포함
- 모든 멤버 노출:
-
HOST가 아닌 사용자 / 비로그인
-
ATTEND상태 멤버만 노출 - 이미 나간 사용자 정보는 노출되지 않음
-
3) participantCount 계산 기준
-
participantCount는 ATTEND 상태만 집계합니다.
4) 이미지 노출 정책
- 상세 조회에서는 모든 이미지에 대해
variants정보를 포함합니다. - 정렬 기준은
sortOrder오름차순이며,0번 이미지가 대표입니다. - 이미지가 없는 경우
images: []로 응답됩니다.
❗ 오류 케이스 (대표)
| HTTP | ErrorCode | 조건 |
|---|---|---|
| 404 | GROUP_NOT_FOUND | 존재하지 않는 groupId |
| 401 | UNAUTHORIZED | 토큰 형식은 있으나 유효하지 않음 |
⚠️ 토큰이 없는 경우는 비로그인 조회로 처리하며 오류가 아닙니다.⚠️ 토큰이 존재하지만 유효하지 않은 경우만 401을 반환합니다.
사용자에게 노출되는 모임 목록을 조회하는 API입니다.
키워드 검색과 커서 기반 페이징을 지원하며,
filter / includeStatuses / excludeStatuses 조합으로 노출할 모임 상태 범위를 제어할 수 있습니다.
목록 응답에는 프론트엔드에서 별도 계산이 필요 없도록,
-
remainingSeats(남은 자리 수) -
joinable(참여 가능 여부)
를 서버에서 함께 계산하여 내려줍니다.
Headers
| 이름 | 값 | 필수 |
|---|---|---|
| Authorization | Bearer {JWT} | X |
목록 조회는 비로그인도 가능합니다. 토큰이 없으면 비로그인 사용자로 처리합니다.
📥 Query Parameters
| 이름 | 타입 | 필수 | 기본값 | 설명 |
|---|---|---|---|---|
| keyword | String | X | - | 검색 키워드(제목/위치/상세위치/설명) |
| cursor | Long | X | - | 커서 기준 ID (id < cursor) |
| size | int | X | 20 | 페이지 크기(최대 50) |
| filter | GroupListFilter | X | ACTIVE | 상태 필터 프리셋 |
| includeStatuses | GroupV2Status[] | X | - | 포함할 상태 목록 |
| excludeStatuses | GroupV2Status[] | X | - | 제외할 상태 목록 |
🧾 상태 필터 동작 규칙 (중요)
1) filter 기본값
-
filter가 없으면 기본값은ACTIVE
2) filter 프리셋 의미
-
ACTIVE- 기본 포함 상태:
RECRUITING,FULL,CLOSED
- 기본 포함 상태:
-
ARCHIVED- 기본 포함 상태:
CANCELLED,FINISHED
- 기본 포함 상태:
-
ALL- 상태 제한 없이 전체 조회
3) includeStatuses 우선 규칙
-
includeStatuses가 비어있지 않게 전달되면- filter의 기본 포함 상태는 무시됩니다.
- includeStatuses에 명시된 상태만 포함됩니다.
4) excludeStatuses 적용 규칙
-
excludeStatuses에 포함된 상태는 최종 결과에서 제외됩니다.
5) 충돌 시 우선순위
-
includeStatuses와excludeStatuses가 충돌하면 → excludeStatuses가 우선 적용됩니다.
🔎 검색 범위 (keyword)
keyword가 전달되면 다음 필드에 대해 부분 검색(대소문자 무시) 을 수행합니다.
- title
- address.location
- address.locationDetail
- description
📌 페이징 방식 (cursor)
- 기본 정렬:
groupId desc - cursor가 있으면:
groupId < cursor - 응답의
nextCursor는 다음 요청에 사용할 cursor 값입니다. - 더 이상 데이터가 없으면
nextCursor = null
📥 Example Request
GET /api/v2/groups
GET /api/v2/groups?keyword=강남&size=10
GET /api/v2/groups?filter=ARCHIVED
GET /api/v2/groups?includeStatuses=RECRUITING
GET /api/v2/groups?cursor=120&size=20{
"status": 200,
"success": true,
"data": {
"items": [
{
"id": 6,
"title": "참여 취소 테스트용 자바 스터디",
"joinPolicy": "FREE",
"status": "RECRUITING",
"location": "서울 서초구",
"locationDetail": "교대역 1번 출구 근처 카페",
"startTime": "2026-12-20T19:00:00",
"endTime": "2026-12-20T21:00:00",
"images": [
"https://we-go-bucket.s3.ap-northeast-2.amazonaws.com/sample_440x240.webp"
],
"tags": ["자바", "스터디"],
"description": "참여 취소 테스트용 모임입니다.",
"participantCount": 1,
"maxParticipants": 5,
"remainingSeats": 4,
"joinable": true,
"createdBy": {
"userId": 102,
"nickName": "HostTwo",
"profileImage": null
},
"createdAt": "2025-12-22T14:27:50.696442",
"updatedAt": "2025-12-22T14:27:50.696442"
}
],
"nextCursor": 6
}
}🧩 GroupListItemV2Response 필드
| 필드 | 타입 | 설명 |
|---|---|---|
| id | Long | 모임 ID |
| title | String | 제목 |
| joinPolicy | String | 모임 정책 |
| status | GroupV2Status | 모임 상태 |
| location | String | 위치 |
| locationDetail | String | 상세 위치 |
| startTime | LocalDateTime | 시작 시간 |
| endTime | LocalDateTime | 종료 시간 |
| images | String[] | CARD_440_240 이미지 URL 목록 |
| tags | String[] | 태그 |
| description | String | 설명 |
| participantCount | int | ATTEND 인원 수 |
| maxParticipants | int | 최대 인원 |
| remainingSeats | int | 남은 자리 수 |
| joinable | boolean | 참여 가능 여부 |
| createdBy | CreatedByV2Response | 작성자 정보 |
| createdAt | LocalDateTime | 생성 시각 |
| updatedAt | LocalDateTime | 수정 시각 |
📝 joinable 계산 규칙
-
status == RECRUITINGANDremainingSeats > 0→true - 그 외(
FULL,CLOSED,CANCELLED,FINISHED) →false
📝 이미지 노출 정책 (목록)
- 목록 조회에서는 CARD_440_240 이미지 URL만 내려줍니다.
- 모임당 최대 3개까지 내려갑니다.
- 이미지가 없는 경우
images: []로 응답됩니다.
❗ 오류 케이스
| HTTP | ErrorCode | 조건 |
|---|---|---|
| 400 | INVALID_QUERY_PARAMETER | 파라미터 타입 오류 |
| 400 | INVALID_PAGE_SIZE | size 범위 초과 |
| 401 | UNAUTHORIZED | 토큰이 존재하지만 유효하지 않음 |
토큰이 없는 경우는 비로그인 조회로 정상 처리됩니다.
로그인한 사용자가 내 모임 목록을 조회하는 API입니다.
type 파라미터로 “현재 참여 / 지난 모임 / 내가 만든 모임” 탭을 구분합니다.
-
current: 현재 참여 중인 모임(기본) -
past: 종료/취소된 모임 -
myPost: 내가 생성한 모임(호스트)
이 API는 반드시 로그인 필요합니다.
Headers
| 이름 | 값 | 필수 |
|---|---|---|
| Authorization | Bearer {JWT} | O |
📥 Query Parameters
| 이름 | 타입 | 필수 | 기본값 | 설명 |
|---|---|---|---|---|
| type | String | X | current | 탭 구분(current / past / myPost) |
| cursor | Long | X | - | 커서 기준 ID (id < cursor) |
| size | int | X | 20 | 페이지 크기(최대 50) |
| filter | GroupListFilter | X | type별 자동 | 상태 필터 프리셋(ACTIVE / ARCHIVED / ALL) |
| includeStatuses | GroupV2Status[] | X | - | 포함할 모임 상태 |
| excludeStatuses | GroupV2Status[] | X | - | 제외할 모임 상태 |
| myStatuses | GroupUserV2Status[] | X | type별 상이 | 내 멤버십 상태 필터 |
🧾 type(탭) 정책
1) 기본값
-
type이 없으면current
2) 허용 값
-
current: 현재 참여 중인 모임 -
past: 종료/취소된 모임 -
myPost: 내가 생성한 모임(호스트)
그 외 값은 오류 처리합니다.
🧾 filter 기본값(type 연동)
요청에 filter가 없으면 서버가 type에 따라 자동 설정합니다.
| type | 적용 filter |
|---|---|
| current | ACTIVE |
| myPost | ACTIVE |
| past | ARCHIVED |
filter 의미
-
ACTIVE→RECRUITING,FULL,CLOSED -
ARCHIVED→CANCELLED,FINISHED -
ALL→ 전체(= include 조건 미적용)
🧾 myStatuses 정책 (코드 기준)
-
current,past에서는 내 멤버십 상태(myGu.status) 기준으로 필터링합니다. -
myPost에서는 “내가 만든 모임”을 조회하지만, 코드상 myStatuses 파라미터는 쿼리에서 사용되지 않습니다. (즉, 전달해도 결과에 영향 없음)
기본 동작
-
type=current또는type=past에서myStatuses가 없으면 기본값은ATTEND -
type=myPost에서는myStatuses를 사용하지 않음
참고:
type=past에서myStatuses를LEFT,KICKED등으로 확장할지 여부는 프로젝트 정책에 따라 허용 범위를 결정하면 됩니다.
🧾 상태 필터 동작 규칙 (서비스 로직 기준)
-
filter는 기본 include 세트를 제공합니다. -
includeStatuses가 오면 include가 우선 적용되며, 이때 기본 include는 사용하지 않습니다. -
excludeStatuses는 최종적으로 제외 조건으로 적용됩니다. - include/exclude 충돌 시 exclude가 우선됩니다. (include에서 충돌 항목 제거)
참고:
filter=ACTIVE인데 include/exclude도 없고 include가 비어있으면, 서버는 안전장치로RECRUITING,FULL을 include로 설정합니다. (현재 enum 기본 include가 이미 ACTIVE에 포함을 주므로 일반적으로는 이 케이스가 잘 발생하지 않습니다.)
📌 페이징 방식 (cursor)
- 기본 정렬:
id desc - cursor가 있으면:
id < cursor - 응답의
nextCursor는 다음 요청의cursor로 그대로 사용 - 더 이상 데이터가 없으면
null
📥 Example Request (내 모임 조회 시나리오별)
1. 내 모임 조회 (기본: CURRENT + ACTIVE + myStatuses=ATTEND)
GET /api/v2/groups/me
Authorization: Bearer {JWT}- 포함 상태:
RECRUITING,FULL,CLOSED
2. 내 모임 조회 (CURRENT 명시)
GET /api/v2/groups/me?type=current
Authorization: Bearer {JWT}- 포함 상태:
RECRUITING,FULL,CLOSED
3. 내 모임 조회 (PAST: 지난 모임 탭, filter=ARCHIVED 자동 적용)
GET /api/v2/groups/me?type=past
Authorization: Bearer {JWT}- 포함 상태:
CANCELLED,FINISHED
4. 내 모임 조회 (ALL: 상태 제한 없이 전부)
GET /api/v2/groups/me?filter=ALL
Authorization: Bearer {JWT}- 포함 상태: 전체
(
RECRUITING,FULL,CLOSED,CANCELLED,FINISHED)
5. 내 모임 조회 (PAST + ALL: past 탭이지만 상태 제한 해제)
GET /api/v2/groups/me?type=past&filter=ALL
Authorization: Bearer {JWT}- 포함 상태: 전체
6. 내 모임 조회 (내가 만든 모임: MY_POST, 기본 filter=ACTIVE)
GET /api/v2/groups/me?type=myPost
Authorization: Bearer {JWT}- 포함 상태:
RECRUITING,FULL,CLOSED
7. 내가 만든 모임 조회 (MY_POST + 지난 모임)
GET /api/v2/groups/me?type=myPost&filter=ARCHIVED
Authorization: Bearer {JWT}- 포함 상태:
CANCELLED,FINISHED
8. 내가 만든 모임 조회 (MY_POST + ALL: 내가 만든 모임 전부)
GET /api/v2/groups/me?type=myPost&filter=ALL
Authorization: Bearer {JWT}- 포함 상태: 전체
9. 내 모임 조회 (참여 상태 필터: myStatuses 지정)
GET /api/v2/groups/me?myStatuses=ATTEND
Authorization: Bearer {JWT}
GET /api/v2/groups/me?myStatuses=PENDING
Authorization: Bearer {JWT}
GET /api/v2/groups/me?myStatuses=ATTEND&myStatuses=PENDING
Authorization: Bearer {JWT}- 의미: 내 membership 상태(
GroupUserV2Status) 기준으로 필터링
10. 내 모임 조회 (페이징: cursor 기반 다음 페이지)
GET /api/v2/groups/me?cursor={nextCursor}
Authorization: Bearer {JWT}
GET /api/v2/groups/me?type=past&cursor={nextCursor}
Authorization: Bearer {JWT}
GET /api/v2/groups/me?type=myPost&filter=ARCHIVED&cursor={nextCursor}
Authorization: Bearer {JWT}-
nextCursor는 이전 응답의data.nextCursor값 사용
⚠️ 목록 응답의images는 CARD_440_240 URL만 내려가며, 이미지가 없으면[]입니다.⚠️ nextCursor는 다음 요청의cursor로 그대로 사용합니다.
GET http://localhost:8080/api/v2/groups/me?type=past&filter=ALL
HTTP/1.1 200
Vary: Origin
Vary: Access-Control-Request-Method
Vary: Access-Control-Request-Headers
X-Content-Type-Options: nosniff
X-XSS-Protection: 0
Cache-Control: no-cache, no-store, max-age=0, must-revalidate
Pragma: no-cache
Expires: 0
X-Frame-Options: SAMEORIGIN
Content-Type: application/json
Transfer-Encoding: chunked
Date: Mon, 29 Dec 2025 22:37:19 GMT{
"status": 200,
"success": true,
"data": {
"items": [
{
"id": 1,
"title": "내 모임(current) 테스트용 - ACTIVE",
"joinPolicy": "FREE",
"status": "FINISHED",
"location": "서울 서초구",
"locationDetail": "교대역 1번 출구 근처 카페",
"startTime": "2025-12-29T22:18:00",
"endTime": "2026-12-29T22:20:00",
"images": [
"https://we-go-bucket.s3.ap-northeast-2.amazonaws.com/20251229221640_0_075c8d34-e857-4588-a58e-7364c464be15_440x240.webp",
"https://we-go-bucket.s3.ap-northeast-2.amazonaws.com/20251229221641_1_54ea517a-b2ae-4145-a770-f6b65c14a2f3_440x240.webp"
],
"tags": [
"ME_HTTP",
"ACTIVE"
],
"description": "me.http current/active 테스트용 모임입니다.",
"participantCount": 2,
"maxParticipants": 5,
"remainingSeats": 3,
"joinable": false,
"createdBy": {
"userId": 102,
"nickName": "HostTwo",
"profileImage": null,
"profileMessage": null
},
"createdAt": "2025-12-29T22:17:06.12904",
"updatedAt": "2025-12-29T22:17:06.12904",
"myMembership": {
"groupUserId": 2,
"role": "MEMBER",
"status": "ATTEND",
"joinedAt": "2025-12-29T22:17:12.74574",
"leftAt": null
}
}
],
"nextCursor": null
}
}❗ 오류 케이스
| HTTP | ErrorCode | 조건 |
|---|---|---|
| 401 | UNAUTHORIZED | 인증 실패 |
| 400 | INVALID_MY_GROUP_TYPE | type 값이 허용되지 않음 |
| 400 | INVALID_QUERY_PARAMETER | 파라미터 타입 오류 |
| 400 | INVALID_PAGE_SIZE | size 범위 초과 |
모임 정보를 부분 수정(Partial Update) 하는 API입니다.
요청에 포함된 필드만 변경되며, 호스트(HOST)만 수정 가능합니다.
다음 상태의 모임은 수정할 수 없습니다.
-
CANCELLED(취소) -
FINISHED(종료) -
deletedAt이 존재하는 경우
Headers
| 이름 | 값 | 필수 |
|---|---|---|
| Authorization | Bearer {JWT} | O |
| Content-Type | application/json | O |
Path Variables
| 이름 | 타입 | 필수 | 설명 |
|---|---|---|---|
| groupId | Long | O | 수정할 모임 ID |
📥 Request Body – UpdateGroupV2Request
images필드는 모임 생성(create)과 동일한 구조를 사용합니다.
-
images = null→ 이미지 변경 없음 -
images = []→ 이미지 전체 삭제 -
images = [{ sortOrder, imageKey }, ...]→ 최종 상태로 교체
{
"title": "모임 제목 수정",
"description": "설명 수정 - 일정과 장소를 변경했습니다.",
"joinPolicy": "FREE",
"location": "서울 강남구",
"locationDetail": "역삼역 2번 출구",
"startTime": "2026-12-30T19:00:00",
"endTime": "2026-12-30T21:00:00",
"maxParticipants": 10,
"status": "CLOSED",
"tags": ["러닝", "주말"],
"images": [
{ "sortOrder": 0, "imageKey": "{{img0_key}}" },
{ "sortOrder": 1, "imageKey": "{{img1_key}}" }
]
}🧾 수정 정책 (핵심)
1) 권한 정책
- 요청자는 해당 모임의 HOST여야 합니다.
- HOST가 아니면 수정 불가합니다.
2) 수정 불가 상태
다음 조건 중 하나라도 만족하면 모든 수정이 거부됩니다.
status = CANCELLEDstatus = FINISHEDdeletedAt != null
3) 부분 수정(null 처리)
- 요청 필드가
null이면 해당 필드는 변경되지 않습니다.
| 필드 | null 처리 |
|---|---|
| title | 변경 없음 |
| description | 변경 없음 |
| joinPolicy | 변경 없음 |
| location / locationDetail | 부분 수정 |
| startTime / endTime | 부분 수정 |
| maxParticipants | 변경 없음 |
| status | 변경 없음 |
| tags | 변경 없음 |
| images | 변경 없음 |
4) 제목(title) 정책
- 공백/빈 문자열 불가
- 최대 50자
- trim 후 저장
5) 설명(description) 정책
- 공백/빈 문자열 불가
- 최대 300자
- trim 후 저장
6) 주소(location / locationDetail) 정책
- 둘 중 하나라도 전달되면 “주소 변경 시도”로 판단
-
location = null→ 기존 값 유지 -
locationDetail = null→ 기존 값 유지 - 최종적으로
location은 필수(빈 값 불가)
7) 시간(startTime / endTime) 정책
- 하나만 수정하더라도 최종 상태 기준으로 유효해야 합니다
- 규칙
startTime < endTime- endTime이 존재한다면 반드시 startTime 이후
8) 정원(maxParticipants) 정책
- 0 이하 불가
- 현재 ATTEND 인원보다 작게 줄일 수 없습니다
9) 상태(status) 전이 정책
허용되는 상태 전이
RECRUITING → FULL / CLOSED / CANCELLED / FINISHEDFULL → RECRUITING / CLOSED / CANCELLED / FINISHEDCLOSED → CANCELLED / FINISHED
허용되지 않는 전이
-
CANCELLED또는FINISHED→ 다른 상태
10) 태그(tags) 수정 정책
-
tags = null→ 변경 없음 -
tags전달 시 전체 교체 - 최대 10개
- 중복/빈 문자열 불가
11) 이미지(images) 수정 정책 (최종 상태 방식)
11-1) images = null
- 이미지 변경 없음
11-2) images = []
- 이미지 전체 삭제
11-3) images = 1~3개 (수정)
- 리스트에 포함된
imageKey만 최종적으로 유지 - 기존 이미지 중 리스트에 없는 항목은 삭제
- 신규
imageKey는 반드시- 사전 업로드(pre-upload) key
- 요청자와 업로더 일치
- 사용 시 consume(1회성)
정렬 규칙
- 모든 항목의
sortOrder가 null → 요청 배열 순서 적용 - 하나라도 존재 →
sortOrder오름차순 - 중복 sortOrder 불가
- 허용 범위:
0 ~ 2
✅ 주의: images: []를 비워두지 말고, 반드시 pre-upload 응답의 imageKey로 채워서 요청합니다.
응답 구조는 모임 생성 응답과 동일합니다.
{
"status": 200,
"success": true,
"data": {
"id": 2,
"title": "모임 제목 수정",
"joinPolicy": "FREE",
"status": "CLOSED",
"address": {
"location": "서울 강남구",
"locationDetail": "역삼역 2번 출구"
},
"startTime": "2026-12-30T19:00:00",
"endTime": "2026-12-30T21:00:00",
"tags": ["러닝", "주말"],
"description": "설명 수정 - 일정과 장소를 변경했습니다.",
"maxParticipants": 10,
"images": [],
"updatedAt": "2025-12-22T14:40:07.41714"
}
}❗ 오류 케이스
| HTTP | ErrorCode | 조건 |
|---|---|---|
| 401 | UNAUTHORIZED | 인증 실패 |
| 403 | NO_PERMISSION_TO_UPDATE_GROUP | HOST 아님 |
| 404 | GROUP_NOT_FOUND | 모임 없음 |
| 400 | INVALID_GROUP_STATUS | 수정 불가 상태 |
| 400 | INVALID_TIME_RANGE | start/end 시간 오류 |
| 400 | INVALID_MAX_PARTICIPANTS | 정원 축소 불가 |
| 400 | DUPLICATED_IMAGE_SORT_ORDER_IN_REQUEST | sortOrder 중복 |
| 400 | DUPLICATED_IMAGE_KEY_IN_REQUEST | imageKey 중복 |
| 400 | INVALID_GROUP_IMAGE_ITEM | images 구조 오류 |
| 400 | IMAGE_KEY_NOT_FOUND | imageKey 만료/이미 사용 |
특정 모임을 완전히 삭제(하드 삭제) 하는 API입니다.
삭제는 HOST만 가능하며, 모임과 관련된 데이터(참가자, 태그 연결, 이미지/이미지 변형)도 함께 정리됩니다.
또한 이미지 파일(S3)은 DB 삭제가 커밋으로 확정된 이후(afterCommit) 에 삭제됩니다.
즉, “DB가 먼저 삭제 확정 → 그 다음 파일 삭제” 순서를 지켜 데이터 정합성을 우선합니다.
Headers
| 이름 | 값 | 필수 | 설명 |
|---|---|---|---|
| Authorization | Bearer {JWT} | O | 로그인 필수(호스트 권한 확인) |
Path Variables
| 이름 | 타입 | 필수 | 설명 |
|---|---|---|---|
| groupId | Long | O | 삭제할 모임 ID |
Example Request
DELETE /api/v2/groups/10
Authorization: Bearer {JWT}✅ 모임 삭제 정책(핵심)
1) 삭제는 HOST만 가능합니다.
- 요청한 사용자가 해당 모임의 hostId와 다르면 삭제할 수 없습니다.
- 즉, “내가 만든 모임만 삭제 가능”합니다.
2) 삭제는 Hard Delete 입니다.
이 API는 soft delete가 아니라 hard delete로 동작합니다.
- groups(v2_groups) 레코드가 DB에서 제거됩니다.
- 연결된 관계 데이터도 함께 정리됩니다.
3) 삭제 순서(명시적 정리)
연관관계가 여러 개 얽혀 있기 때문에, FK 충돌 없이 안정적으로 삭제되도록 순서를 지킵니다.
- 모임 참가자(v2_group_users) 삭제
- 모임-태그 연결(v2_group_tags) 삭제 (Tag 테이블 자체는 삭제하지 않음)
- 이미지 변형(v2_group_image_variants) 삭제
- 이미지(v2_group_images) 삭제
- 모임(v2_groups) 삭제
4) S3 파일 삭제는 afterCommit에서 수행됩니다.
- 삭제할 URL 목록(variants의 imageUrl들)을 DB 삭제 이전에 조회하여 확보합니다.
- DB 삭제 트랜잭션이 커밋 확정된 이후(afterCommit) 에 확보한 URL 기반으로 S3 파일을 삭제합니다.
📌 이 방식의 장점
- DB 삭제가 롤백되면 파일도 삭제되지 않습니다.
- 즉, “DB는 남아있는데 파일만 없어지는 사고”를 예방합니다.
5) afterCommit 파일 삭제 실패 처리(운영 관점)
afterCommit에서 파일 삭제를 수행하기 때문에, 파일 삭제 단계에서 장애가 나면 DB는 이미 삭제된 상태일 수 있습니다.
따라서 운영 확장 포인트를 고려할 수 있습니다.
- 파일 삭제 실패 URL 로깅
- 재시도(outbox, 배치 정리)
- 장애 발생 시 알림/모니터링
✅ Response (204 No Content)
삭제가 성공하면 응답 바디 없이 204 No Content를 반환합니다.
HTTP/1.1 204 No Content
❗ 오류 케이스
| HTTP | ErrorCode | 조건 |
|---|---|---|
| 401 | UNAUTHORIZED | 인증 실패 |
| 403 | NO_PERMISSION_TO_DELETE_GROUP | HOST가 아님 |
| 404 | GROUP_NOT_FOUND | groupId에 해당하는 모임이 없음 |
| 400 | INVALID_GROUP_STATUS | 정책상 삭제 불가 상태(프로젝트에서 제한한다면) |
✅ 참고: 현재 문서에서는 “삭제 불가 상태”를 별도로 제한하지 않고 HOST면 삭제 가능으로 읽힙니다. 만약 “FINISHED만 삭제 가능” 같은 제한을 둘 거면
INVALID_GROUP_STATUS를 활성화하면 됩니다.
특정 모임에 참여(ATTEND) 하는 API입니다.
참여는 모집중(RECRUITING) 상태에서만 가능하며, 참여 처리 후 정원이 가득 차면 자동으로 FULL(정원마감) 으로 전환됩니다.
과거에 나갔거나(LEFT) 강퇴(KICKED)된 사용자는 재참여(reAttend) 가 가능하지만, 차단(BANNED) 된 사용자는 참여할 수 없습니다.
승인제에서는
message를 함께 보내면 가입 신청 메시지(joinRequestMessage) 로 저장됩니다.
Headers
| 이름 | 값 | 필수 |
|---|---|---|
| Authorization | Bearer {JWT} | O |
Path Variables
| 이름 | 타입 | 필수 | 설명 |
|---|---|---|---|
| groupId | Long | O | 참여할 모임 ID |
Body
- 선택(Optional)
- 승인제(APPROVAL_REQUIRED)에서만 의미가 있으며, FREE 모임에서는 무시될 수 있습니다.
{
"message": "참여 신청합니다!"
}| 필드 | 타입 | 필수 | 설명 |
|---|---|---|---|
| message | String | X | 가입 신청 메시지(최대 300자) |
✅ 참여 정책(핵심)
1) 로그인 사용자만 참여 가능
- Authorization(JWT)이 필수입니다.
- userId가 없으면 참여 처리 불가입니다.
2) HOST는 자신의 모임에 참여할 수 없습니다
- 모임 생성 시 HOST는 이미
HOST + ATTEND상태로 등록됩니다. - HOST가 attend API를 호출하면 오류 처리됩니다.
3) RECRUITING 상태에서만 참여 가능
다음 상태에서는 참여할 수 없습니다.
- FULL
- CLOSED
- CANCELLED
- FINISHED
4) joinPolicy에 따른 참여 결과
-
FREE: 즉시ATTEND -
APPROVAL_REQUIRED: 즉시PENDING(승인 대기)
5) 기존 멤버십 상태에 따른 처리
서버는 (groupId, userId) 기준으로 기존 멤버십을 조회합니다.
| 기존 상태 | 처리 결과 (FREE) | 처리 결과 (APPROVAL_REQUIRED) |
|---|---|---|
| 멤버십 없음 | 신규 생성 (role=MEMBER, status=ATTEND) | 신규 생성 (role=MEMBER, status=PENDING) |
| ATTEND | 중복 참여 → 오류 | 중복 참여 → 오류 |
| LEFT | 재참여 허용 → ATTEND로 전환 | 재참여 허용 → PENDING로 전환(재신청) |
| KICKED | 재참여 허용 → ATTEND로 전환 | 재참여 허용 → PENDING로 전환(재신청) |
| BANNED | 참여 불가 → 오류 | 참여 불가 → 오류 |
| PENDING | (정책상 발생 X) | 중복 요청 → 오류 |
| REJECTED | 재요청 불가 → 오류 | 재요청 불가 → 오류 |
6) 정원(maxParticipants) 초과 불가 (FREE / APPROVE 공통)
- 최종적으로
ATTEND가 되는 시점(즉시 참여 or 승인 처리)에서 ATTEND 인원을 다시 카운트합니다. -
attendCount > maxParticipants가 되면 전체 처리를 롤백하고 오류를 반환합니다.
7) 정원 도달 시 FULL 자동 전환 (ATTEND 확정 시점)
attendCount == maxParticipants- 그리고 모임 상태가
RECRUITING이면 → 모임 상태를 FULL로 자동 변경합니다.
승인제에서는 “승인(approve)” 시점에 FULL 전환이 일어날 수 있습니다.
응답 구조는 기존 AttendanceGroupV2Response 그대로 사용
단, 승인제 모임에서는 myMembership.status가 PENDING으로 내려올 수 있습니다.
{
"status": 200,
"success": true,
"data": {
"groupId": 10,
"groupStatus": "RECRUITING",
"participantCount": 5,
"maxParticipants": 8,
"myMembership": {
"groupUserId": 44,
"role": "MEMBER",
"status": "PENDING",
"joinedAt": "2025-12-19T16:10:20.123456",
"leftAt": null
},
"serverTime": "2025-12-19T16:10:20.999999"
}
}❗ 오류 케이스 (수정)
| HTTP | ErrorCode | 조건 |
|---|---|---|
| 401 | UNAUTHORIZED | 인증 실패 |
| 404 | GROUP_NOT_FOUND_BY_ID | 모임 없음 |
| 400 | GROUP_NOT_RECRUITING | 모집 상태 아님 |
| 400 | ALREADY_ATTEND_GROUP | 이미 참여 중 |
| 400 | GROUP_BANNED_USER | 차단된 사용자 |
| 400 | GROUP_HOST_CANNOT_ATTEND | HOST 재참여 시도 |
| 400 | GROUP_IS_FULL | 정원 초과 |
| 409 | GROUP_ALREADY_PENDING | 이미 승인 대기중 |
특정 모임에서 나가기(LEFT) 처리하는 API입니다.
✅ 나가기 정책(핵심)
1) 로그인 사용자만 가능
- JWT 필수
2) HOST는 나가기 불가
- HOST는 leave 호출 시 오류
3) ATTEND 상태에서만 가능
- 멤버십 없거나 ATTEND가 아니면 오류
4) 나가기 처리 결과
- 상태를
LEFT로 변경 -
leftAt에 서버 현재시간 기록 -
joinedAt유지
5) FULL → RECRUITING 자동 복귀
- FULL 상태에서 나가기 후 자리가 생기면 RECRUITING으로 복귀
응답 구조는 참여(attend)와 동일
❗ 오류 케이스 (수정)
| HTTP | ErrorCode | 조건 |
|---|---|---|
| 401 | UNAUTHORIZED | 인증 실패 |
| 404 | GROUP_NOT_FOUND_BY_ID | 모임 없음 |
| 400 | GROUP_HOST_CANNOT_LEFT | HOST 나가기 시도 |
| 400 | GROUP_MEMBERSHIP_NOT_FOUND | 참여 기록 없음 |
| 400 | GROUP_NOT_ATTEND_STATUS | ATTEND 아님 |
APPROVAL_REQUIRED 모임 전용 승인 API입니다.
✅ 승인 정책(핵심)
1) HOST만 가능
2) 승인제 모임에서만 가능
- FREE 모임 호출 시 오류
3) 대상은 반드시 PENDING
4) 승인 처리 결과
PENDING → ATTEND-
joinedAt유지 - ATTEND 인원 증가
5) 정원/상태 전이
- 승인 후 정원 도달 시 FULL 전환 가능
(기존 형식 유지)
❗ 오류 케이스 (수정)
| HTTP | ErrorCode | 조건 |
|---|---|---|
| 401 | UNAUTHORIZED | 인증 실패 |
| 403 | GROUP_HOST_ONLY | HOST 아님 |
| 404 | GROUP_NOT_FOUND_BY_ID | 모임 없음 |
| 404 | GROUP_USER_NOT_FOUND | 대상 멤버십 없음 |
| 409 | GROUP_JOIN_POLICY_NOT_APPROVAL_REQUIRED | 승인제 아님 |
| 409 | GROUP_TARGET_STATUS_NOT_PENDING | 대상이 PENDING 아님 |
| 409 | GROUP_IS_FULL | 승인 시 정원 초과 |
APPROVAL_REQUIRED 모임 전용 거절 API입니다.
✅ 거절 정책(핵심)
1) HOST만 가능
2) 승인제 모임에서만 가능
3) 대상은 반드시 PENDING
4) 거절 처리 결과
PENDING → REJECTED- ATTEND 인원 변화 없음
-
leftAt기록하지 않음 (이탈 아님)
(기존 형식 유지)
❗ 오류 케이스
| HTTP | ErrorCode | 조건 |
|---|---|---|
| 401 | UNAUTHORIZED | 인증 실패 |
| 403 | GROUP_HOST_ONLY | HOST 아님 |
| 404 | GROUP_NOT_FOUND_BY_ID | 모임 없음 |
| 404 | GROUP_USER_NOT_FOUND | 대상 멤버십 없음 |
| 409 | GROUP_JOIN_POLICY_NOT_APPROVAL_REQUIRED | 승인제 아님 |
| 409 | GROUP_TARGET_STATUS_NOT_PENDING | 대상이 PENDING 아님 |
✅ 전체 정리 (내부 일관성)
- ATTEND / LEFT / KICKED / BANNED → leftAt 사용
- PENDING / REJECTED → leftAt 사용 안 함
- 참여 / 승인 / 강퇴 / 차단 / 나가기 모두 ATTEND 기준으로 상태 전이 일관
- 조회 API(15
17)와 실행 API(814)의 대상 상태 완전 일치
호스트가 모임 참여자를 강퇴하여 KICKED로 전환합니다.
- 일반적으로 ATTEND → KICKED
- HOST만 가능
- 강퇴로 인해
participantCount(ATTEND)가 감소할 수 있으며,- 모임이 FULL이었다면 자리 발생 시 RECRUITING으로 자동 복귀할 수 있습니다(정책 적용)
Headers
| 이름 | 값 | 필수 |
|---|---|---|
| Authorization | Bearer {JWT} | O |
| Content-Type | application/json | X (바디 없음) |
Path Variables
| 이름 | 타입 | 필수 | 설명 |
|---|---|---|---|
| groupId | Long | O | 모임 ID |
| targetUserId | Long | O | 강퇴 대상 유저 ID |
Body
- 없음 (
{}보내도 무방)
Example Request
POST /api/v2/groups/1/attendance/103/kick
Authorization: Bearer {JWT}✅ 강퇴 정책(핵심)
1) 인증/권한
- 로그인 필수(JWT 필수)
- 요청자는 해당 모임의 HOST여야 합니다. (아니면 오류)
2) 강퇴 대상 제한
- 호스트 자신(targetUserId=hostId)을 강퇴할 수 없습니다.
- 대상 멤버십이 존재해야 합니다. (없으면 오류)
3) 대상 상태 제한(강퇴 가능 상태) (수정: 일관성 강화)
서버는 (groupId, targetUserId)로 대상 멤버십을 조회합니다.
- 강퇴는 ATTEND 상태에서만 허용합니다.
- 아래 상태들은 강퇴 대상이 될 수 없습니다(정책 오류):
-
PENDING,REJECTED,LEFT,KICKED,BANNED
-
4) 강퇴 처리 결과 (수정: leftAt 기록 정책 통일)
- 대상 멤버십 상태를
KICKED로 변경합니다. -
leftAt에 강퇴 시각(서버 현재시간) 을 기록합니다. (LEFT/KICKED/BANNED의 “이탈 시각”을leftAt으로 통일)
5) FULL → RECRUITING 자동 복귀(선택 정책)
강퇴 후 서버는 ATTEND 인원을 다시 카운트합니다.
- 강퇴 이전 모임 상태가
FULL이고 - 강퇴 후
attendCount < maxParticipants라면 → 모임 상태를RECRUITING으로 자동 변경할 수 있습니다.
강퇴 성공 시, 서버는 다음 정보를 반환합니다.
-
groupStatus(강퇴 후 모임 상태: FULL→RECRUITING 복귀 가능) -
participantCount(ATTEND 인원 수) -
targetMembership(강퇴된 대상의 최소 정보) serverTime
{
"status": 200,
"success": true,
"data": {
"groupId": 1,
"groupStatus": "RECRUITING",
"joinPolicy": "FREE",
"participantCount": 2,
"maxParticipants": 5,
"targetMembership": {
"userId": 103,
"groupUserId": 3,
"status": "KICKED"
},
"serverTime": "2025-12-23T16:06:47.8129076"
}
}❗ 오류 케이스
| HTTP | ErrorCode | 조건 |
|---|---|---|
| 401 | UNAUTHORIZED | 인증 실패 |
| 403 | GROUP_HOST_ONLY | 호스트가 아님 |
| 404 | GROUP_NOT_FOUND_BY_ID | 모임 없음 |
| 404 | GROUP_USER_NOT_FOUND | 대상 멤버십 없음 |
| 409 | GROUP_CANNOT_KICK_HOST | 호스트 자신 강퇴 시도 |
| 409 | GROUP_TARGET_STATUS_NOT_KICKABLE | 대상이 강퇴 불가 상태(ATTEND 아님 등) |
호스트가 특정 유저를 차단(BAN) 하여 BANNED 상태로 전환합니다.
- 일반적으로 ATTEND → BANNED
- HOST만 가능
- 차단된 유저는 이후 해당 모임에 참여(attend) 불가
- 차단 시점에 대상이 ATTEND였다면
participantCount(ATTEND)가 감소할 수 있으며,- 모임이 FULL이었다면 자리 발생 시 RECRUITING으로 자동 복귀할 수 있습니다(정책 적용)
Headers
| 이름 | 값 | 필수 |
|---|---|---|
| Authorization | Bearer {JWT} | O |
| Content-Type | application/json | X (바디 없음) |
Path Variables
| 이름 | 타입 | 필수 | 설명 |
|---|---|---|---|
| groupId | Long | O | 모임 ID |
| targetUserId | Long | O | 차단 대상 유저 ID |
Body
- 없음 (
{}보내도 무방)
Example Request
POST /api/v2/groups/1/attendance/103/ban
Authorization: Bearer {JWT}
✅ 차단 정책(핵심)
1) 인증/권한
- 로그인 필수(JWT 필수)
- 요청자는 해당 모임의 HOST여야 합니다. (아니면 오류)
2) 차단 대상 제한
- 호스트 자신(targetUserId=hostId)을 차단할 수 없습니다.
- 대상 멤버십이 존재해야 합니다. (없으면 오류)
3) 대상 상태 제한(차단 가능 상태) (수정: 일관성 강화)
서버는 (groupId, targetUserId)로 대상 멤버십을 조회합니다.
- 차단은 ATTEND 상태에서만 허용합니다.
- 아래 상태들은 차단 대상이 될 수 없습니다(정책 오류):
-
PENDING,REJECTED,LEFT,KICKED,BANNED
-
4) 차단 처리 결과 (수정: leftAt 기록 정책 통일)
- 대상 멤버십 상태를
BANNED로 변경합니다. -
leftAt에 차단 시각(서버 현재시간) 을 기록합니다. (LEFT/KICKED/BANNED의 “이탈 시각”을leftAt으로 통일)
5) FULL → RECRUITING 자동 복귀(선택 정책)
차단 후 서버는 ATTEND 인원을 다시 카운트합니다.
- 차단 이전 모임 상태가
FULL이고 - 차단 후
attendCount < maxParticipants라면 → 모임 상태를RECRUITING으로 자동 변경할 수 있습니다.
차단 성공 시, 서버는 다음 정보를 반환합니다.
-
groupStatus(차단 후 모임 상태: FULL→RECRUITING 복귀 가능) -
participantCount(ATTEND 인원 수) -
targetMembership(차단된 대상의 최소 정보) serverTime
{
"status": 200,
"success": true,
"data": {
"groupId": 1,
"groupStatus": "RECRUITING",
"joinPolicy": "FREE",
"participantCount": 2,
"maxParticipants": 5,
"targetMembership": {
"userId": 103,
"groupUserId": 2,
"status": "BANNED"
},
"serverTime": "2025-12-23T16:08:07.5338729"
}
}
❗ 오류 케이스
| HTTP | ErrorCode | 조건 |
|---|---|---|
| 401 | UNAUTHORIZED | 인증 실패 |
| 403 | GROUP_HOST_ONLY | 호스트가 아님 |
| 404 | GROUP_NOT_FOUND_BY_ID | 모임 없음 |
| 404 | GROUP_USER_NOT_FOUND | 대상 멤버십 없음 |
| 409 | GROUP_CANNOT_BAN_HOST | 호스트 자신 차단 시도 |
| 409 | GROUP_TARGET_STATUS_NOT_BANNABLE | 대상이 차단 불가 상태(ATTEND 아님, 이미 BANNED 등) |
호스트가 차단된 유저를 차단 해제(Unban) 합니다.
- BANNED → KICKED
- HOST만 가능
- unban은 “차단만 해제”이며, 재참여는
/attend에서 처리합니다.
왜 KICKED로 복귀? 차단 해제 후 바로 ATTEND로 복구시키면 “호스트 승인 없이 자동 재참여”가 되어 운영상 혼선이 생길 수 있어요. 그래서 차단 해제 = 참여는 다시 가능하지만, 즉시 참여 상태는 아님 으로 정의합니다.
Headers
| 이름 | 값 | 필수 |
|---|---|---|
| Authorization | Bearer {JWT} | O |
| Content-Type | application/json | X (바디 없음) |
Path Variables
| 이름 | 타입 | 필수 | 설명 |
|---|---|---|---|
| groupId | Long | O | 모임 ID |
| targetUserId | Long | O | 차단 해제 대상 유저 ID |
Body
- 없음 (
{}보내도 무방)
Example Request
POST /api/v2/groups/1/attendance/103/unban
Authorization: Bearer {JWT}
✅ 차단 해제 정책(핵심)
1) 인증/권한
- 로그인 필수(JWT 필수)
- 요청자는 해당 모임의 HOST여야 합니다.
2) 대상 상태 제한
서버는 (groupId, targetUserId)로 대상 멤버십을 조회합니다.
- 대상이 BANNED 상태가 아니면 unban 불가입니다.
- 예: ATTEND / LEFT / KICKED / PENDING / REJECTED 인데 unban 호출 → 오류
3) 차단 해제 처리 결과(복귀 상태) (수정: 명확화)
- BANNED → KICKED 로 변경합니다.
-
leftAt은 변경하지 않습니다.-
leftAt은 “이탈(강퇴/차단/나가기) 시각”으로 사용하며, - unban은 “상태 복귀”이므로 이탈 시각을 새로 찍지 않습니다.
-
차단 해제 성공 시, 서버는 다음 정보를 반환합니다.
-
groupStatus(차단 해제는 ATTEND 인원에 영향이 없으므로 보통 상태 변화 없음) -
participantCount(ATTEND 인원 수) -
targetMembership(차단 해제된 대상의 최소 정보) serverTime
{
"status": 200,
"success": true,
"data": {
"groupId": 1,
"groupStatus": "RECRUITING",
"joinPolicy": "FREE",
"participantCount": 2,
"maxParticipants": 5,
"targetMembership": {
"userId": 103,
"groupUserId": 2,
"status": "KICKED"
},
"serverTime": "2025-12-23T16:08:43.0092063"
}
}❗ 오류 케이스
| HTTP | ErrorCode | 조건 |
|---|---|---|
| 401 | UNAUTHORIZED | 인증 실패 |
| 403 | GROUP_HOST_ONLY | 호스트가 아님 |
| 404 | GROUP_NOT_FOUND_BY_ID | 모임 없음 |
| 404 | GROUP_USER_NOT_FOUND | 대상 멤버십 없음 |
| 409 | GROUP_TARGET_NOT_BANNED | 대상이 BANNED가 아닌데 unban 시도 |
호스트가 현재 강퇴 가능한 대상 목록을 조회합니다.
- HOST만 가능
- 기본 정책:
ATTEND상태 중 HOST 본인을 제외한 사용자가 대상 - 정책 확장 가능:
- MANAGER 제외
- PENDING 제외(승인제 모임에서)
- 이미 KICKED/LEFT/BANNED/REJECTED 등 제외
Headers
| 이름 | 값 | 필수 |
|---|---|---|
| Authorization | Bearer {JWT} | O |
Path Variables
| 이름 | 타입 | 필수 | 설명 |
|---|---|---|---|
| groupId | Long | O | 모임 ID |
Body
- 없음
Example Request
GET /api/v2/groups/1/attendance/kick-targets
Authorization: Bearer {JWT}
✅ 조회 정책(핵심)
1) 인증/권한
- 로그인 필수(JWT 필수)
- 요청자는 해당 모임의 HOST여야 합니다. (아니면 오류)
2) 조회 대상 필터링 규칙 (수정: “강퇴 가능 상태” 일관성 강화)
서버는 groupId의 멤버십 중 아래 조건을 만족하는 유저만 반환합니다.
status = ATTEND-
role != HOST(호스트 본인 제외)
문서의 강퇴 API(12번) 기준이 ATTEND만 강퇴 가능이므로, 조회도 동일하게
ATTEND만 노출되도록 고정합니다.
3) 응답 목록 정렬(권장) (수정: 기준 명확화)
-
joinedAt asc(먼저 참여한 사람부터 위)
{
"status": 200,
"success": true,
"data": {
"groupId": 1,
"targets": [
{
"userId": 103,
"nickName": "KickMember1",
"profileImage": null,
"groupUserId": 3,
"status": "ATTEND",
"joinedAt": "2025-12-23T16:06:20.216063"
},
{
"userId": 104,
"nickName": "KickMember2",
"profileImage": null,
"groupUserId": 2,
"status": "ATTEND",
"joinedAt": "2025-12-23T16:07:49.398200"
}
],
"serverTime": "2025-12-23T16:06:26.3672819"
}
}
🧩 응답 필드 설명 (KickTargetsV2Response)
| 필드 | 타입 | 설명 |
|---|---|---|
| groupId | Long | 모임 ID |
| targets | KickTargetItem[] | 강퇴 가능한 대상 목록 |
| serverTime | LocalDateTime | 서버 기준 응답 시간 |
KickTargetItem
| 필드 | 타입 | 설명 |
|---|---|---|
| userId | Long | 대상 유저 ID |
| nickName | String | 닉네임 |
| profileImage | String | 프로필 이미지 URL(없으면 null) |
| groupUserId | Long | 대상 멤버십 ID |
| status | GroupUserV2Status | 대상 상태(항상 ATTEND) |
| joinedAt | LocalDateTime | 참여 시간 |
❗ 오류 케이스
| HTTP | ErrorCode | 조건 |
|---|---|---|
| 401 | UNAUTHORIZED | 인증 실패 |
| 403 | GROUP_HOST_ONLY | 호스트가 아님 |
| 404 | GROUP_NOT_FOUND_BY_ID | 모임 없음 |
📝 특징 및 주의사항
- HOST만 조회 가능
- 대상은 ATTEND 상태 중 HOST 제외
-
targets가 비어있으면targets: []로 내려갑니다. - 강퇴 실행(
/kick) 시에는 대상 상태가 여전히 ATTEND인지를 다시 검증해야 합니다(조회 시점과 실행 시점 사이 변경 가능)
호스트가 차단 가능한 대상 목록을 조회합니다.
- HOST만 가능
- 기본 정책:
ATTEND상태 중 HOST 본인을 제외한 사용자가 대상 - 정책 확장 가능:
- MANAGER 제외
- PENDING/REJECTED 등 포함 여부(운영 정책에 따라)
Headers
| 이름 | 값 | 필수 |
|---|---|---|
| Authorization | Bearer {JWT} | O |
Path Variables
| 이름 | 타입 | 필수 | 설명 |
|---|---|---|---|
| groupId | Long | O | 모임 ID |
Body
- 없음
Example Request
GET /api/v2/groups/1/attendance/ban-targets
Authorization: Bearer {JWT}
✅ 조회 정책(핵심)
1) 인증/권한
- 로그인 필수(JWT 필수)
- 요청자는 해당 모임의 HOST여야 합니다. (아니면 오류)
2) 조회 대상 필터링 규칙 (수정: 차단 API(13번)와 일관성 유지)
서버는 groupId의 멤버십 중 아래 조건을 만족하는 유저만 반환합니다.
status = ATTEND-
role != HOST(호스트 본인 제외)
문서의 차단 API(13번) 기준이 ATTEND만 차단 가능이므로, 조회도 동일하게
ATTEND만 노출되도록 고정합니다.
3) 응답 목록 정렬(권장) (수정: 운영 UX 기준 명확화)
-
joinedAt desc(최근 참여한 사람부터 위)
{
"status": 200,
"success": true,
"data": {
"groupId": 1,
"targets": [
{
"userId": 103,
"nickName": "BanMembers1",
"profileImage": null,
"groupUserId": 2,
"status": "ATTEND",
"joinedAt": "2025-12-23T16:09:32.244210"
},
{
"userId": 104,
"nickName": "BanMembers2",
"profileImage": null,
"groupUserId": 3,
"status": "ATTEND",
"joinedAt": "2025-12-23T16:07:49.398200"
}
],
"serverTime": "2025-12-23T16:10:10.6443403"
}
}
🧩 응답 필드 설명 (BanTargetsV2Response)
| 필드 | 타입 | 설명 |
|---|---|---|
| groupId | Long | 모임 ID |
| targets | BanTargetItem[] | 차단 가능한 대상 목록 |
| serverTime | LocalDateTime | 서버 기준 응답 시간 |
BanTargetItem
| 필드 | 타입 | 설명 |
|---|---|---|
| userId | Long | 대상 유저 ID |
| nickName | String | 닉네임 |
| profileImage | String | 프로필 이미지 URL(없으면 null) |
| groupUserId | Long | 대상 멤버십 ID |
| status | GroupUserV2Status | 대상 상태(항상 ATTEND) |
| joinedAt | LocalDateTime | 참여 시간 |
❗ 오류 케이스
| HTTP | ErrorCode | 조건 |
|---|---|---|
| 401 | UNAUTHORIZED | 인증 실패 |
| 403 | GROUP_HOST_ONLY | 호스트가 아님 |
| 404 | GROUP_NOT_FOUND_BY_ID | 모임 없음 |
📝 특징 및 주의사항
- HOST만 조회 가능
- 대상은 ATTEND 상태 중 HOST 제외
-
targets가 비어있으면targets: []로 내려갑니다. - 실제 ban(
/ban) 실행 시에는 대상이 여전히 ATTEND인지를 다시 검증해야 합니다(조회 시점과 실행 시점 사이 변경 가능)
호스트가 현재 BANNED 상태인 유저 목록을 조회합니다.
- HOST만 가능
- “현재 차단 중인 유저를 관리(해제/unban 등)”하기 위한 목록 API입니다.
- 반환 대상은 해당 모임의 멤버십 중
status = BANNED인 항목입니다.
Headers
| 이름 | 값 | 필수 |
|---|---|---|
| Authorization | Bearer {JWT} | O |
Path Variables
| 이름 | 타입 | 필수 | 설명 |
|---|---|---|---|
| groupId | Long | O | 모임 ID |
Body
- 없음
Example Request
GET /api/v2/groups/1/attendance/banned-targets
Authorization: Bearer {JWT}
✅ 조회 정책(핵심)
1) 인증/권한
- 로그인 필수(JWT 필수)
- 요청자는 해당 모임의 HOST여야 합니다.
2) 조회 대상 필터링 규칙
서버는 groupId의 멤버십 중 아래 조건만 반환합니다.
status = BANNED
3) 응답 목록 정렬(권장) (수정: “차단 관리” 관점에 맞게)
-
leftAt desc(최근 차단된 사람부터 위)
13번에서 BANNED 전환 시 leftAt 기록으로 통일했기 때문에, 차단 관리 목록은
leftAt desc정렬이 가장 직관적입니다.
{
"status": 200,
"success": true,
"data": {
"groupId": 1,
"targets": [
{
"userId": 103,
"nickName": "BanMembers1",
"profileImage": null,
"groupUserId": 2,
"status": "BANNED",
"joinedAt": "2025-12-23T16:09:32.244210"
}
],
"serverTime": "2025-12-23T16:11:00.8673094"
}
}
🧩 응답 필드 설명 (BannedTargetsV2Response)
| 필드 | 타입 | 설명 |
|---|---|---|
| groupId | Long | 모임 ID |
| targets | BannedTargetItem[] | 차단된 유저 목록 |
| serverTime | LocalDateTime | 서버 기준 응답 시간 |
BannedTargetItem
| 필드 | 타입 | 설명 |
|---|---|---|
| userId | Long | 유저 ID |
| nickName | String | 닉네임 |
| profileImage | String | 프로필 이미지 URL(없으면 null) |
| groupUserId | Long | 멤버십 ID |
| status | GroupUserV2Status | 항상 BANNED
|
| joinedAt | LocalDateTime | 참여 시간(또는 최초 관여 시간) |
❗ 오류 케이스
| HTTP | ErrorCode | 조건 |
|---|---|---|
| 401 | UNAUTHORIZED | 인증 실패 |
| 403 | GROUP_HOST_ONLY | 호스트가 아님 |
| 404 | GROUP_NOT_FOUND_BY_ID | 모임 없음 |
📝 특징 및 주의사항 (수정: 일관성 반영)
- HOST만 조회 가능
- 결과가 없으면
targets: [] - 실제 unban(
/unban) 실행 시에는 대상이 여전히BANNED인지 재검증해야 합니다(조회 시점과 실행 시점 사이 변경 가능) - 운영에서 “차단 시각”을 보여주려면,
- 13번 정책대로
leftAt을 차단 시각으로 사용하고, - 17번 목록은 정렬을
leftAt desc로 두는 구성이 가장 자연스럽습니다.
- 13번 정책대로
호스트가 해당 모임의 가입 신청/참여 관련 멤버십 목록을 status로 필터링하여 조회합니다.
기본값은 PENDING이며, 승인제 모임에서는 가입 신청자 목록 조회로 사용됩니다.
Headers
| 이름 | 값 | 필수 |
|---|---|---|
| Authorization | Bearer {JWT} | O |
Path Variables
| 이름 | 타입 | 필수 | 설명 |
|---|---|---|---|
| groupId | Long | O | 모임 ID |
Query Parameters
| 이름 | 타입 | 필수 | 기본값 | 설명 |
|---|---|---|---|---|
| status | GroupUserV2Status | X | PENDING | 조회할 멤버십 상태 필터 |
예:
PENDING,REJECTED,ATTEND,LEFT,KICKED,BANNED등
✅ 조회 정책(핵심)
1) HOST만 조회 가능
- 로그인 필수(JWT 필수)
- 요청자는 해당 모임의 HOST여야 합니다. (아니면 오류)
2) 상태(status)로 필터링
- 서버는
(groupId, status)조건으로 멤버십을 조회합니다. - HOST 멤버십은 기본적으로 제외됩니다.
3) 정렬(권장)
-
status = PENDING:joinedAt desc(최근 신청이 위)
{
"status": 200,
"success": true,
"data": {
"groupId": 1,
"status": "PENDING",
"count": 2,
"items": [
{
"userId": 104,
"nickName": "Member2",
"profileImage": null,
"groupUserId": 3,
"status": "PENDING",
"joinedAt": "2025-12-29T17:43:41.480751",
"joinRequestMessage": null
},
{
"userId": 103,
"nickName": "Member1",
"profileImage": null,
"groupUserId": 2,
"status": "PENDING",
"joinedAt": "2025-12-29T17:43:37.570959",
"joinRequestMessage": "안녕하세요! 승인제 모임 참가 신청합니다. 메시지 저장 테스트입니다."
}
],
"serverTime": "2025-12-29T17:43:45.974586"
}
}🧩 응답 필드 설명
| 필드 | 타입 | 설명 |
|---|---|---|
| groupId | Long | 모임 ID |
| status | GroupUserV2Status | 조회 대상 status |
| items | JoinRequestItem[] | 가입/참여 관련 목록 |
| serverTime | LocalDateTime | 서버 기준 응답 시간 |
JoinRequestItem
| 필드 | 타입 | 설명 |
|---|---|---|
| userId | Long | 유저 ID |
| nickName | String | 닉네임 |
| profileImage | String | 프로필 이미지 URL(없으면 null) |
| groupUserId | Long | 멤버십 ID |
| status | GroupUserV2Status | 멤버십 상태 |
| joinedAt | LocalDateTime | 신청/참여 시간(정책상 joinedAt) |
| joinRequestMessage | String | 가입 신청 메시지(없으면 null) |
❗ 오류 케이스
| HTTP | ErrorCode | 조건 |
|---|---|---|
| 401 | UNAUTHORIZED | 인증 실패 |
| 404 | GROUP_NOT_FOUND_BY_ID | 모임 없음 |
| 403 | NO_PERMISSION_TO_VIEW_JOIN_REQUESTS | HOST가 아닌데 조회 시도 |