본문으로 건너뛰기

v1.3.0 릴리즈 노트

릴리즈 날짜: 2026년 4월 타입: Minor Release (Breaking Changes 포함) 호환성: ⚠️ Breaking — 약관 조회 응답 스키마 변경, 클라이언트 마이그레이션 필요


📋 요약

약관(agreements) 조회 및 동의/철회 엔드포인트 전체를 단일 UserAgreementDto 구조로 통일하고, 레거시 중복 엔드포인트 4개를 deprecated 처리했습니다.

핵심 목표:

  • 클라이언트가 약관 본문을 얻기 위해 여러 엔드포인트를 호출하던 구조 제거
  • 엔드포인트별로 다른 DTO 타입을 쓰던 비일관성 제거 (조회와 쓰기 API 모두)
  • 중복 책임(약관 조회/동의) 엔드포인트 정리
  • 동의/철회 API의 URL 대칭 (POST/DELETE /v1/users/agreements/:agreementId/consent)

🗂 변경 요약 매트릭스

조회 API (Read)

엔드포인트변경브레이킹
GET /v1/auth/agreementsSwagger deprecated 태그 (기능 유지)
POST /v1/auth/login응답 agreements 필드 → 항상 []
GET /v1/mobile/app-settingsagreements 필드 → UserAgreementDto[] (본문 포함)
GET /v1/users/agreements응답 → UserAgreementsResponseDto
GET /v1/users/agreements/history응답 → UserAgreementHistoryResponseDto

쓰기 API (Write)

엔드포인트변경브레이킹
POST /v1/users/agreements/:agreementId/consent신규 (동의 기록)-
DELETE /v1/users/agreements/:agreementId/consent응답 → UserAgreementDto
POST /v1/agreements/versions/:versionId/agreeSwagger deprecated 태그
GET /v1/agreements/versions/:versionId/has-agreedSwagger deprecated 태그

🆕 신규 DTO: UserAgreementDto

모든 agreement 조회 응답의 아이템에 사용되는 통일된 타입.

interface UserAgreementDto {
agreementId: string; // 약관 ID
versionId: string; // 약관 버전 ID
type: AgreementsType; // TERMS | CONSENT | PRIVACY_POLICY | CONSENT_*
title: string; // 약관 제목
text: string; // 약관 요약 텍스트
detailsUrl: string; // 상세 URL
orderIndex: number; // UI 표시 순서 (오름차순)
isRequired: boolean; // 필수 동의 여부

// 로그인 컨텍스트에서만 제공 (비로그인 엔드포인트에선 undefined)
agreed?: boolean; // 현재 동의 여부
agreedAt?: number; // 동의 시각 (Unix ms)
revokedAt?: number; // 철회 시각 (Unix ms)
}

타입별 동작 (클라이언트 가이드)

type동의 가능 여부UI 동작
TERMS, CONSENT, CONSENT_*동의 가능체크박스/토글 제공
PRIVACY_POLICY공지성 (동의 아님)정보 표시만, 동의 액션 없음

참고: 이전 버전의 isAgreeable 필드는 제거되었습니다. type으로 분기하세요.


🔄 엔드포인트별 상세 변경

1. GET /v1/mobile/app-settingsagreements 필드 확장

Before (v1.2.0):

{
"agreements": [
{
"versionsId": "bab42d93-...",
"type": "TERMS",
"validFrom": 1746316800000,
"isRequired": true,
"orderIndex": 1
}
]
}

After (v1.3.0):

{
"agreements": [
{
"agreementId": "9f8a7b6c-...",
"versionId": "bab42d93-...",
"type": "TERMS",
"title": "서비스 이용약관",
"text": "서비스 이용약관 내용...",
"detailsUrl": "https://www.weltcorp.com/terms",
"orderIndex": 1,
"isRequired": true
}
]
}

변경점:

  • versionsIdversionId (오타 수정)
  • validFrom 제거
  • agreementId / title / text / detailsUrl 추가 (본문 포함)
  • agreed / agreedAt / revokedAt없음 (AppToken 컨텍스트)

이 엔드포인트가 새로운 통합 진입점입니다. 회원가입 전/로그인 전 약관 조회는 여기서 한 번에 받을 수 있습니다.


2. GET /v1/users/agreements — 로그인 사용자 현재 상태

Before (v1.2.0):

{
"items": [
{
"agreementId": "agreement-uuid",
"versionId": "b8a961f2-...",
"type": "CONSENT",
"isRequired": true,
"title": "약관 제목",
"text": "본문",
"detailsUrl": "https://...",
"agreed": true,
"isAgreeable": true
}
]
}

After (v1.3.0):

{
"items": [
{
"agreementId": "agreement-uuid",
"versionId": "b8a961f2-...",
"type": "CONSENT",
"title": "약관 제목",
"text": "본문",
"detailsUrl": "https://...",
"orderIndex": 0,
"isRequired": true,
"agreed": true,
"agreedAt": 1767836575312
}
]
}

변경점:

  • isAgreeable 제거type으로 분기
  • orderIndex 추가
  • agreedAt (Unix ms) 추가
  • 철회된 약관의 경우 agreed: false + revokedAt (Unix ms)

3. GET /v1/users/agreements/history — 동의 이력

Before (v1.2.0):

{
"items": [
{
"agreementId": "agreement-uuid",
"versionId": "version-uuid",
"type": "CONSENT_PERSONAL_DATA",
"isRequired": true,
"title": "Datenschutzeinwilligung",
"detailsUrl": "https://...",
"agreed": true,
"version": "7",
"timestamp": "2026-02-05T17:20:00.000Z"
}
]
}

After (v1.3.0):

{
"items": [
{
"agreementId": "agreement-uuid",
"versionId": "version-uuid",
"type": "CONSENT_PERSONAL_DATA",
"title": "Datenschutzeinwilligung",
"text": "본문",
"detailsUrl": "https://...",
"orderIndex": 2,
"isRequired": true,
"agreed": true,
"agreedAt": 1738776000000
}
]
}

변경점:

  • version (string) 제거versionId로 식별
  • timestamp (ISO string) 제거agreedAt / revokedAt (Unix ms)
    • agreed: true 이력 → agreedAt 사용
    • agreed: false 이력 (철회) → agreedAt + revokedAt 모두 존재
  • text / orderIndex 추가

4. POST /v1/auth/loginagreements 필드 빈 배열

변경점:

  • 응답의 agreements 필드가 **항상 빈 배열 []**로 반환됩니다.
  • 마이그레이션 경로: 로그인 후 약관 상태가 필요하면 GET /v1/users/agreements를 호출하세요.
  • 향후 스웨거 문서에도 deprecated 태그가 표시될 예정입니다 (별도 PR).

5. GET /v1/auth/agreements — Deprecated

변경점:

  • Swagger에 deprecated: true 태그 표시
  • 기능은 그대로 동작 (점진적 이관 가능)
  • 마이그레이션 경로:
    • 비로그인 컨텍스트: GET /v1/mobile/app-settings 사용
    • 로그인 컨텍스트: GET /v1/users/agreements 사용

📦 PRIVACY_POLICY 처리 변경

v1.2.0에서는 GET /v1/auth/agreements 응답에 privacyPolicy가 별도 필드로 분리되어 있었습니다.

v1.3.0부터는 items[] 배열 내부에 type='PRIVACY_POLICY' 아이템으로 포함됩니다.

클라이언트 구현 예시

Before (v1.2.0):

const { items, privacyPolicy } = await fetchAuthAgreements();
// items는 동의 대상, privacyPolicy는 공지성 별도 렌더링

After (v1.3.0):

const { items } = await fetchUserAgreements();
// 하나의 배열 안에 모든 타입 혼재

const consentItems = items.filter(item =>
item.type !== 'PRIVACY_POLICY'
);
const privacyPolicyItem = items.find(item =>
item.type === 'PRIVACY_POLICY'
);

🆕 신규: POST /v1/users/agreements/:agreementId/consent

Request:

POST /v1/users/agreements/9f8a7b6c.../consent
Authorization: Bearer <JWT>
Content-Type: application/json

{ "versionId": "b8a961f2-6e29-44f7-a7cd-67005d177362" }

Response (200 OK): UserAgreementDto

{
"agreementId": "9f8a7b6c-...",
"versionId": "b8a961f2-...",
"type": "TERMS",
"title": "서비스 이용약관",
"text": "본문...",
"detailsUrl": "https://...",
"orderIndex": 1,
"isRequired": true,
"agreed": true,
"agreedAt": 1767836575312
}

특징:

  • 멱등성: 이미 같은 versionId로 활성 동의된 상태면 기존 상태 반환 (중복 INSERT 없음)
  • 검증: versionId가 URL의 agreementId에 속하지 않으면 400 VERSION_AGREEMENT_MISMATCH
  • 메타데이터: 서버가 ipAddress/userAgent 자동 기록 (클라이언트 전송 불필요)

에러 코드:

HTTP코드조건
400VERSION_AGREEMENT_MISMATCH (4002)versionId가 agreementId 소속 아님
400(validation)versionId UUID 아님/누락
401-JWT 없음/만료
404AGREEMENT_VERSION_NOT_FOUND (4001)versionId 미존재

🔄 변경: DELETE /v1/users/agreements/:agreementId/consent

Before:

{
"success": true,
"agreementId": "9f8a7b6c-...",
"agreed": false,
"revokedAt": "2026-03-11T10:15:30.000Z"
}

After (v1.3.0):

{
"agreementId": "9f8a7b6c-...",
"versionId": "b8a961f2-...",
"type": "CONSENT",
"title": "Marketing Consent",
"text": "본문...",
"detailsUrl": "https://...",
"orderIndex": 8,
"isRequired": false,
"agreed": false,
"agreedAt": 1700000000000,
"revokedAt": 1767836575312
}

변경점:

  • success 필드 제거 (HTTP 200 자체가 성공)
  • revokedAt 포맷 ISO string → Unix ms (number)
  • 약관 본문(title, text, detailsUrl, orderIndex, type, isRequired) 포함
  • agreedAt 필드 추가 (최초 동의 시각)

⚠️ Deprecated 쓰기 엔드포인트

POST /v1/agreements/versions/:versionId/agree

POST /v1/users/agreements/:agreementId/consent 사용 권장

GET /v1/agreements/versions/:versionId/has-agreed

GET /v1/users/agreements 응답의 items[].agreed 필드 사용 권장

두 엔드포인트 모두 기능은 유지됩니다. Swagger에 deprecated 태그만 추가되었습니다.


✅ 클라이언트 마이그레이션 체크리스트

필드 리네임/제거

  • agreements[].versionsIdversionId (mobile/app-settings)
  • agreements[].validFrom 제거 (mobile/app-settings) — 필요 없으면 사용 코드 삭제
  • items[].isAgreeable 제거 (users/agreements) → type 기반 분기 코드로 변경
  • items[].version, items[].timestamp 제거 (users/agreements/history) → versionId, agreedAt/revokedAt로 대체

신규 필드 활용

  • agreements[].title / text / detailsUrl (mobile/app-settings) — 본문을 별도 호출 없이 표시 가능
  • items[].agreedAt (Unix ms, users/agreements) — 동의 시각 UI 표시
  • items[].revokedAt (Unix ms, users/agreements, history) — 철회 시각 UI 표시
  • items[].text, orderIndex (history) — UI 일관된 정렬 및 본문

엔드포인트 이관

  • POST /v1/auth/login 응답의 agreements 필드 사용 코드 제거 → GET /v1/users/agreements 호출로 교체
  • GET /v1/auth/agreements 사용 시점 정리:
    • 회원가입 전 → GET /v1/mobile/app-settings
    • 로그인 후 → GET /v1/users/agreements
  • POST 동의 호출: POST /v1/agreements/versions/{versionId}/agreePOST /v1/users/agreements/{agreementId}/consent + body { versionId }로 변경
  • has-agreed 엔드포인트 사용 제거 → GET /v1/users/agreements 응답의 items[].agreed 참조

DELETE 응답 파싱 변경

  • response.success 제거 (HTTP 200 자체가 성공)
  • response.revokedAt을 **Unix ms(number)**로 처리 (기존 ISO string)
  • DELETE 응답의 title/text/detailsUrl 신규 필드로 UI 즉시 갱신 가능 (조회 재요청 불필요)

PRIVACY_POLICY 처리

  • response.privacyPolicy 참조 제거 → items.find(i => i.type === 'PRIVACY_POLICY') 로 변경
  • PRIVACY_POLICY 아이템은 동의 액션 UI 노출 안 함 (공지성)

🚀 배포 순서 권장 (백엔드 관점)

본 변경은 단일 PR/단일 배포이지만, 실제 프로덕션 롤아웃은 아래 순서 권장:

  1. 스테이징 배포 → 클라이언트 팀이 스키마 변경 확인
  2. 클라이언트 측 코드 대응 완료 후 프로덕션 배포
  3. 프로덕션 배포 후 /v1/auth/agreements, /v1/auth/login.agreements 사용처 추적
  4. 충분한 이관 기간 후 완전 제거 (별도 PR)

🔗 참고 자료

설계/계획

  • docs/superpowers/specs/2026-04-21-agreements-api-consolidation-design.md — 조회 API 통일
  • docs/superpowers/specs/2026-04-21-agreements-consent-api-design.md — 동의/철회 API 정리
  • docs/superpowers/plans/2026-04-21-agreements-api-consolidation.md
  • docs/superpowers/plans/2026-04-21-agreements-consent-api.md

구현

  • Swagger: 로컬 — http://localhost:3000/v1/docs
  • 공유 DTO: libs/shared/contracts-agreements/src/lib/dtos/user-agreement.dto.ts
  • 신규 도메인 에러: libs/feature/agreements VersionAgreementMismatchError (코드 4002)

📝 Out-of-Scope (별도 PR 예정)

  • 타 앱(dha-sleep-api, dha-metabolic-api, dta-wikr-api, dta-wed-api) 마이그레이션
  • libs/feature/auth/coreLoginResponseDto.agreements Swagger deprecated 태그 추가 (타 앱 영향 검토 필요)
  • libs/feature/auth/core의 레거시 command 핸들러 실제 제거