본문으로 건너뛰기

User 도메인 모델

데이터 저장 규칙

  • private 스키마: 사용자 개인 식별 정보(PII) 및 민감 정보(User, UserProfile, UserStatusHistory, UserCycle, UserSuspensionHistory, UserDeletionProcess, DomainDeletionStatus, AnonymizationRecord 등)를 저장합니다. GDPR 등 규정 준수를 위해 접근 제어가 엄격하게 관리되어야 합니다.
  • user 스키마: 사용자와 직접 연결되지 않거나 개인 식별성이 낮은 설정, 메타데이터, 열거형 등을 저장합니다. (현재 User 도메인에서는 대부분의 정보가 사용자와 직접 연관되므로 private 스키마 사용을 기본으로 합니다. 추후 필요시 user 스키마 활용 검토)
  • 사용자 데이터 삭제/익명화: GDPR 등 규정 준수를 위한 데이터 삭제 프로세스, 익명화 기록 등의 민감 정보도 private 스키마에 저장합니다.

참고: User 엔티티 공유

  • User 엔티티는 Auth 도메인과 공유되며, 실제 데이터는 private.users 테이블에 저장됩니다.
  • 본 문서의 Prisma 스키마 섹션은 private.users 테이블의 원본(Source of Truth) 정의를 포함합니다.
  • 본 문서의 TypeScript 인터페이스(interface User)는 User 도메인의 관점에서 필요한 필드를 중심으로 정의합니다.

1. 엔티티 관계도 (ERD)

참고: 위 ERD의 User 엔티티는 private.users 공유 테이블의 전체 구조를 나타냅니다. UserGroup 등 외부 참조는 논리적 관계를 보여줍니다. UserCycle은 User와 1:N 관계이며, User 엔티티에 직접 참조되지 않고 필요시 조회합니다.

2. 엔티티

2.1 User

  • 참고: 아래 TypeScript interface User 정의는 User 도메인의 관점과 책임에 초점을 맞춥니다.
  • 주석 처리된 필드들(password, failedLoginAttempts 등)은 주로 Auth 도메인에서 관리하는 정보입니다. 비록 같은 private.users 테이블에 저장되더라도, User 도메인의 핵심 로직에서는 직접 수정하거나 주된 책임을 지지 않음을 나타냅니다.
  • 실제 TypeScript 코드 구현 시에는 도메인 경계를 명확히 하기 위해, 이 인터페이스에서 해당 필드를 제외하거나(Omit), **읽기 전용(readonly)**으로 표시하거나, Prisma가 생성한 전체 타입을 기반으로 필요한 부분만 사용하는 방식 등을 고려해야 합니다. 전체 데이터 구조는 본 문서의 Prisma 스키마 섹션을 참조하세요.
/**
* 사용자 계정 엔티티 (User 도메인 책임 필드 중심).
* Auth 도메인과 공유되는 `private.users` 테이블에 해당합니다.
* 전체 데이터 구조는 아래 Prisma 스키마 정의를 참조하세요.
*/
interface User {
id: UserId;
email: Email; // (Auth 도메인과 동기화)
name: UserName;
// passwordHash?: string; // (Auth 도메인 관리)
status: UserStatus;
userType: UserType;
accountType: AccountType; // 레거시 필드 - IAM Group(`guests.*` vs `patients.*`)으로 게스트/정식 사용자 구분 권장
// lastLoginAt?: Date; // (Auth 도메인 관리)
// failedLoginAttempts?: number; // (Auth 도메인 관리)
// lockoutUntil?: Date; // (Auth 도메인 관리)
profileData?: Record<string, any>;
createdAt: Date;
updatedAt: Date;
deletedAt?: Date;

// --- 서비스 이용 기간 정보 ---
/** 현재 치료 활동 일시 정지 여부 */
isSuspended?: boolean;
/** (계산됨) 서비스 만료일 */
expiresAt?: Date;
/** 게스트 계정일 경우 TimeMachine 기반 만료 예정 시각 */
guestExpiresAt?: Date;
/** Step-up 인증이 완료된 시각 */
upgradedAt?: Date;
// totalSuspendedDuration (UserSuspensionHistory 기반), remainingServiceDays 등은 필요시 계산되는 값
// 상세 정지/재개 이력은 UserSuspensionHistory 참조

// --- 익명화 관련 필드 ---
/** 익명화 처리 여부 */
isAnonymized: boolean;
/** 익명화 처리 시점 */
anonymizedAt?: Date;
/** 익명화 프로세스 식별자 (도메인 간 일관된 추적용) */
anonymizationId?: string;
/** GDPR 데이터 보존 종료일 */
dataRetentionEndDate?: Date;

// --- 관계 참조 (IAM 도메인 관리) ---
/** 사용자가 속한 그룹 ID 목록 (IAM 도메인에서 조회/관리됨) */
readonly groupIds?: string[];
/** 사용자에게 적용되는 플랜 ID (IAM 도메인에서 조회/관리됨) */
readonly planId?: string; // 배열이 아닌 단일 ID

// --- 관계 참조 (User 도메인 관리) ---
/** 사용자의 UserCycle 목록 (필요시 조회) */
readonly cycles?: UserCycle[];
/** 현재 활성화된 UserCycle ID (선택적, 편의 필드) */
readonly activeCycleId?: string;
}

enum UserStatus {
ACTIVE = 'ACTIVE', // 활성
INACTIVE = 'INACTIVE', // 비활성 (탈퇴)
LOCKED = 'LOCKED', // 일시 잠김
PENDING = 'PENDING', // 활성화 대기
SUSPENDED = 'SUSPENDED', // 이용 정지
BANNED = 'BANNED' // 영구 정지
}

enum UserType {
USER = 'USER' // 일반 사용자
}

enum AccountType {
GUEST = 'GUEST', // 약관 동의만 완료된 제한 계정
REGISTERED = 'REGISTERED' // 이메일/소셜 인증을 완료한 정규 계정
}

2.2 UserProfile

interface UserProfile {
userId: string; // User ID (FK, PK)
language: string; // 선호 언어 (e.g., 'ko', 'en', 'de')
timezone: string; // 타임존 (e.g., 'Asia/Seoul', 'Europe/Berlin')
createdAt: Date;
updatedAt: Date;
}

2.3 UserStatusHistory

interface UserStatusHistory {
id: string; // 이력 ID (UUID)
userId: string; // User ID (FK)
oldStatus: UserStatus; // 이전 상태
newStatus: UserStatus; // 변경된 상태
reason?: string; // 변경 사유
changedBy: string; // 변경 주체 (userId 또는 system identifier)
changedAt: Date; // 변경 시간
}

2.4 UserAccountSuspensionHistory

/**
* 사용자 계정 정지/재개 이력 엔티티 (계정 상태 SUSPENDED와 관련).
*/
interface UserAccountSuspensionHistory {
id: string; // 이력 ID (UUID)
userId: string; // User ID (FK)
suspendedAt: Date; // 정지 시작 시각
resumedAt?: Date; // 재개 시각 (현재 정지 중이면 null)
suspensionReason?: string; // 정지 사유
resumptionReason?: string; // 재개 사유
duration?: number; // 정지 기간 (일 또는 초 단위, 계산됨, 재개 시 확정)
}

2.5 UserCycle

/**
* 사용자의 치료/학습 주기 엔티티.
*/
interface UserCycle {
id: string; // 주기 ID (UUID)
userId: string; // User ID (FK)
planId: string; // 해당 주기에 적용된 Plan ID (FK, IAM 참조)
startedAt: Date; // 주기 시작 시각
completedAt?: Date; // 주기 완료 시각
cancelledAt?: Date; // 주기 취소 시각
status: UserCycleStatus; // 주기 상태
isSuspended: boolean; // 치료 활동 일시 정지 여부
createdAt: Date;
updatedAt: Date;
}

enum UserCycleStatus {
PENDING = 'PENDING', // 시작 대기
ACTIVE = 'ACTIVE', // 진행 중
COMPLETED = 'COMPLETED', // 완료
CANCELLED = 'CANCELLED' // 취소
}

2.6 UserCycleSuspensionHistory

/**
* 사용자 치료 활동 일시 정지/재개 이력 엔티티.
*/
interface UserCycleSuspensionHistory {
id: string; // 이력 ID (UUID)
userCycleId: string; // UserCycle ID (FK)
suspendedAt: Date; // 정지 시작 시각
resumedAt?: Date; // 재개 시각 (현재 정지 중이면 null)
suspensionReason?: string; // 정지 사유
resumptionReason?: string; // 재개 사유
duration?: number; // 정지 기간 (일 또는 초 단위, 계산됨, 재개 시 확정)
}

2.7 UserDeletionProcess

/**
* 사용자 데이터 삭제 프로세스 엔티티.
*/
interface UserDeletionProcess {
id: string; // 삭제 프로세스 ID (UUID)
userId: string; // User ID (FK)
initiatedAt: Date; // 삭제 요청 시각
completedAt?: Date; // 삭제 완료 시각
type: UserDeletionType; // 삭제 유형
status: UserDeletionStatus; // 삭제 상태
reason?: string; // 삭제 사유
requestedBy: string; // 삭제 요청자
domainStatuses: Record<string, any>; // 삭제 대상 도메인 상태
createdAt: Date;
updatedAt?: Date;
}

enum UserDeletionType {
SOFT_DELETE = 'SOFT_DELETE',
ANONYMIZE = 'ANONYMIZE',
HARD_DELETE = 'HARD_DELETE'
}

enum UserDeletionStatus {
PENDING = 'PENDING',
IN_PROGRESS = 'IN_PROGRESS',
COMPLETED = 'COMPLETED',
FAILED = 'FAILED',
PARTIALLY_COMPLETED = 'PARTIALLY_COMPLETED'
}

2.8 DomainDeletionStatus

/**
* 삭제 프로세스 도메인 상태 엔티티.
*/
interface DomainDeletionStatus {
id: string; // 삭제 프로세스 ID (UUID)
deletionProcessId: string; // 삭제 프로세스 ID (FK)
domainName: string; // 삭제 대상 도메인 이름
status: UserDeletionStatus; // 삭제 상태
startedAt: Date; // 삭제 시작 시각
completedAt?: Date; // 삭제 완료 시각
errorMessage?: string; // 삭제 오류 메시지
retryCount: number; // 재시도 횟수
createdAt: Date;
updatedAt?: Date;
}

2.9 AnonymizationRecord

/**
* 사용자 데이터 익명화 기록 엔티티.
*/
interface AnonymizationRecord {
id: string; // 익명화 기록 ID (UUID)
userId: string; // User ID (FK)
originalEmail: string; // 원본 이메일
anonymizedAt: Date; // 익명화 처리 시점
reason?: string; // 익명화 사유
requestedBy: string; // 익명화 요청자
originalData: Record<string, any>; // 암호화된 원본 데이터
createdAt: Date;
}

2.10 GuestAccountLifecycle

v1.2.0 아키텍처 변경: 게스트 계정도 정식 사용자와 동일하게 User + UserCycle 레코드를 생성합니다. 게스트와 정식 사용자의 구분은 accountType 필드가 아닌 IAM Group(guests.* vs patients.*)과 Plan(plan.guest vs plan.therapeutic)으로 관리됩니다. Step-up 시 userId가 변경되지 않아 기존 데이터가 자연스럽게 연결됩니다.

interface GuestAccountLifecycle {
id: string;
guestAccountId: string; // 게스트 식별자 (Auth 도메인에서 발급)
userId?: string; // User 테이블 UUID (v1.2.0: 게스트 등록 시 생성됨)
eventType: GuestLifecycleEventType;
metadata?: Record<string, any>;
occurredAt: Date;
}

type GuestLifecycleEventType =
| 'ONBOARDED' // 게스트 등록 완료 (User + UserCycle 생성됨)
| 'UPGRADE_INITIATED' // Step-up 인증 시작
| 'UPGRADED' // 정식 사용자로 업그레이드 완료 (Group/Plan 변경, userId 유지)
| 'EXPIRY_SCHEDULED' // 만료 타이머 예약됨
| 'EXPIRY_CANCELLED' // 만료 타이머 취소됨 (업그레이드 또는 자발적 탈퇴)
| 'EXPIRED' // 게스트 계정 만료됨
| 'CLEANED' // 만료 후 정리 완료
| 'VOLUNTARILY_WITHDRAWN'; // 자발적 탈퇴 (v1.2.0 추가)

게스트 등록 시 생성되는 데이터 (v1.2.0):

데이터설명
User 레코드UUID 발급, 기본 프로필 설정
UserCycle 레코드치료 사이클 관리 시작
GuestAccountLifecycleONBOARDED 이벤트 기록
IAM Group 할당guests 또는 guests.{region} 그룹
Plan 할당plan.guest 플랜

Step-up 시 변경 사항 (userId 유지):

항목변경 전변경 후
userIdabc-123abc-123 (유지)
IAM Groupguests.krpatients.kr, patients.general
Planplan.guestplan.therapeutic
기존 데이터수면 기록, 설문 등그대로 연결됨

3. 값 객체

3.1 UserId

type UserId = string; // UUID

3.2 Email

type Email = string; // Email format string

3.3 UserName

type UserName = string;

3.4 LanguagePreference

type LanguagePreference = 'ko' | 'en' | 'de' | string; // Standard language codes

3.5 Timezone

type Timezone = string; // IANA timezone identifier (e.g., 'Asia/Seoul')

3.6 UserGroup (Reference)

interface UserGroupRef {
groupId: string; // [IAM 도메인] Group ID
// 필요시 추가 정보 (예: 할당 시점)
}

3.7 PlanAssignment (Reference)

interface PlanAssignmentRef {
planId: string; // [IAM 도메인] Plan ID
// 필요시 추가 정보 (예: 할당 시점)
}

3.8 AccountType

type AccountType = 'GUEST' | 'REGISTERED';

3.9 GuestAccountId

type GuestAccountId = string; // Auth 도메인에서 발급하는 게스트 식별자

4. 집계 (Aggregates)

  • User Aggregate

    • Root: User
    • Entities: UserProfile, UserStatusHistory, UserAccountSuspensionHistory, UserCycle, UserCycleSuspensionHistory, UserDeletionProcess, AnonymizationRecord
    • Value Objects: UserId, Email (Ref), UserName, UserStatus, UserType, LanguagePreference, Timezone, UserGroupRef (List), PlanAssignmentRef, UserCycleStatus, UserDeletionType, UserDeletionStatus
    • 불변식:
      • 모든 사용자는 고유한 UserId를 가짐.
      • Email은 Auth 도메인과 일관성을 유지해야 함 (Eventual Consistency).
      • UserStatus 전이 규칙 준수 (비즈니스 규칙 1.3 참조).
      • USER 타입은 반드시 하나 이상의 UserGroup에 속해야 함 ([IAM 도메인] 검증 필요).
      • USER 타입은 반드시 Plan에 연결되어야 함 ([IAM 도메인] 검증 필요).
      • deletedAt이 설정되지 않은 (Soft delete 되지 않은) 사용자만 활성 관련 작업 가능.
      • UserProfileUser당 하나만 존재.
      • User 레벨의 isSuspended는 deprecated이며, 치료 활동 일시 정지는 UserCycle.isSuspended로 관리됨.
      • UserCycleisSuspended 상태와 UserCycleSuspensionHistory의 마지막 레코드는 일관성을 유지해야 함.
      • UserCycle의 서비스 만료일은 Plan 정보와 UserCycleSuspensionHistory를 기반으로 계산됨.
      • 사용자는 동시에 여러 개의 ACTIVE 상태 UserCycle을 가질 수 없음.
      • UserCycleplanId는 사용자가 해당 시점에 할당된 Plan과 일치해야 함 ([IAM 도메인] 참조).
      • UserCycle의 상태 전이 규칙 준수 (PENDING -> ACTIVE -> COMPLETED/CANCELLED).
      • 익명화된 사용자(isAnonymized = true)는 수정 불가능하며, 로그인 불가능함.
      • 익명화 프로세스에서는 모든 PII 데이터가 익명화되어야 함.
      • 사용자 삭제 프로세스는 일관된 상태를 유지해야 함 (전체 삭제 상태와 도메인별 삭제 상태).
  • UserDeletionProcess Aggregate

    • Root: UserDeletionProcess
    • Entities: DomainDeletionStatus
    • Value Objects: UserDeletionType, UserDeletionStatus
    • 불변식:
      • UserDeletionProcess는 고유한 ID를 가짐.
      • 하나의 UserDeletionProcess는 여러 DomainDeletionStatus를 가질 수 있음.
      • UserDeletionProcess의 status는 하위 DomainDeletionStatus들의 상태에 따라 결정됨.
      • 모든 도메인이 완료(COMPLETED) 상태일 때만 전체 프로세스가 완료(COMPLETED) 상태가 될 수 있음.

5. 도메인 서비스

5.1 UserService

interface UserService {
createUser(userData: { email: string; name: string; userType: UserType; /* accessCode?: string; */ }): Promise<User>; // AccessCode 검증은 Auth/AccessCode 도메인 선행 가정
getUserById(userId: UserId): Promise<User | null>;
getUserByEmail(email: Email): Promise<User | null>; // Auth 도메인과 협력 필요
activateUser(userId: UserId): Promise<void>;
deactivateUser(userId: UserId, reason: string): Promise<void>;
reactivateUser(userId: UserId, reason: string): Promise<void>; // 관리자 권한 필요
scheduleUserDeletion(userId: UserId): Promise<void>; // 비활성화 후 시스템 호출
deleteUserPermanently(userId: UserId): Promise<void>; // 스케줄된 작업 실행
lockUser(userId: UserId, reason: string): Promise<void>; // Auth 도메인에서 트리거될 수 있음
unlockUser(userId: UserId, reason: string): Promise<void>; // Auth 도메인에서 트리거될 수 있음
suspendUserAccount(userId: UserId, reason: string): Promise<void>; // 관리자 권한 필요 (계정 정지)
unsuspendUserAccount(userId: UserId, reason: string): Promise<void>; // 관리자 권한 필요 (계정 정지 해제)
banUser(userId: UserId, reason: string): Promise<void>; // 관리자 권한 필요
getUserStatusHistory(userId: UserId): Promise<UserStatusHistory[]>;
// IAM 관련 연동 (IAM 도메인에서 호출되거나, User 도메인이 이벤트 수신하여 처리)
assignGroupToUser(userId: UserId, groupId: string): Promise<void>; // IAM 연동
removeGroupFromUser(userId: UserId, groupId: string): Promise<void>; // IAM 연동
linkPlanToUser(userId: UserId, planId: string): Promise<void>; // IAM 연동, 필요시 새 UserCycle 생성 트리거?
unlinkPlanFromUser(userId: UserId, planId: string): Promise<void>; // IAM 연동

// --- 치료 활동 일시 정지 및 서비스 이용 기간 관련 ---
suspendUserCycleTreatment(cycleId: string, reason?: string): Promise<void>; // UserCycle의 isSuspended 업데이트 및 UserCycleSuspensionHistory 레코드 생성
resumeUserCycleTreatment(cycleId: string, reason?: string): Promise<void>; // UserCycle의 isSuspended 업데이트 및 마지막 UserCycleSuspensionHistory 레코드 업데이트
getUserCycleSuspensionHistory(cycleId: string): Promise<UserCycleSuspensionHistory[]>; // UserCycle의 정지 이력 조회
calculateCycleExpiryDate(cycleId: string): Promise<Date>; // UserCycle의 Plan과 정지 이력 기반 만료일 계산
getCycleRemainingDays(cycleId: string): Promise<number>; // UserCycle의 남은 일수 계산 (정지 기간 제외)

// 계정 정지 이력 조회
getUserAccountSuspensionHistory(userId: UserId): Promise<UserAccountSuspensionHistory[]>; // 계정 정지 이력 조회

// --- UserCycle 관련 ---
createUserCycle(userId: UserId, planId: string): Promise<UserCycle>; // Plan 할당 시 자동으로 생성될 수 있음
startUserCycle(cycleId: string): Promise<UserCycle>; // 시작 시간 기록, 상태 ACTIVE로 변경
completeUserCycle(cycleId: string): Promise<UserCycle>; // 완료 시간 기록, 상태 COMPLETED로 변경
cancelUserCycle(cycleId: string, reason?: string): Promise<UserCycle>; // 취소 시간 기록, 상태 CANCELLED로 변경
getUserCycles(userId: UserId, status?: UserCycleStatus): Promise<UserCycle[]>; // 특정 사용자의 주기 목록 조회
getActiveUserCycle(userId: UserId): Promise<UserCycle | null>; // 현재 활성 중인 주기 조회
getCurrentDayIndex(userId: UserId): Promise<number | null>; // 활성 주기 시작일과 중단 이력 기반으로 계산

// --- GDPR 관련 ---
processDataAccessRequest(userId: UserId): Promise<any>; // 데이터 반환
processDataDeletionRequest(userId: UserId): Promise<void>; // 비활성화 및 삭제 스케줄링 트리거

// --- 익명화 및 데이터 삭제 관련 ---
anonymizeUser(userId: UserId, reason: string, requestedBy: string): Promise<void>; // 사용자 데이터 익명화
initiateUserDeletionProcess(userId: UserId, type: UserDeletionType, reason: string, requestedBy: string): Promise<UserDeletionProcess>; // 삭제 프로세스 시작
getDeletionProcessById(processId: string): Promise<UserDeletionProcess | null>; // 삭제 프로세스 조회
getUserDeletionProcesses(userId: UserId): Promise<UserDeletionProcess[]>; // 사용자 삭제 프로세스 이력 조회
retryFailedDeletionProcess(processId: string): Promise<void>; // 실패한 삭제 프로세스 재시도
getAnonymizationRecords(userId: UserId): Promise<AnonymizationRecord[]>; // 익명화 기록 조회
}

5.2 UserProfileService

interface UserProfileService {
getUserProfile(userId: UserId): Promise<UserProfile | null>;
updateUserProfile(userId: UserId, profileData: Partial<Pick<UserProfile, 'language' | 'timezone'>>): Promise<UserProfile>;
updateUserName(userId: UserId, name: string): Promise<void>; // User 엔티티 직접 수정
}

6. 도메인 이벤트

(event-storming.md 문서 내용을 TypeScript 인터페이스로 표현)

6.1 사용자 계정 및 프로필 이벤트

interface UserCreatedEvent { userId: UserId; email: Email; name: UserName; userType: UserType; timestamp: Date; }
interface UserActivatedEvent { userId: UserId; timestamp: Date; }
interface UserProfileUpdatedEvent { userId: UserId; updatedFields: Partial<UserProfile>; timestamp: Date; }
interface UserNameUpdatedEvent { userId: UserId; newName: UserName; timestamp: Date; }
interface UserLanguagePreferenceUpdatedEvent { userId: UserId; newLanguage: LanguagePreference; timestamp: Date; }
interface UserTimezoneUpdatedEvent { userId: UserId; newTimezone: Timezone; timestamp: Date; }
interface UserDeactivationRequestedEvent { userId: UserId; requestorId: string; timestamp: Date; }
interface UserDeactivatedEvent { userId: UserId; reason?: string; timestamp: Date; }
interface UserReactivatedEvent { userId: UserId; reason?: string; reactivatedBy: string; timestamp: Date; }
interface UserDeletionScheduledEvent { userId: UserId; scheduledAt: Date; timestamp: Date; }
interface UserDeletedEvent { userId: UserId; timestamp: Date; }
interface UserAnonymizedEvent { userId: UserId; timestamp: Date; }
interface UserLockedEvent { userId: UserId; reason?: string; lockedBy: string; timestamp: Date; } // lockedBy: 'AUTH_SYSTEM' | 'ADMIN_USER_ID'
interface UserUnlockedEvent { userId: UserId; reason?: string; unlockedBy: string; timestamp: Date; }
interface UserSuspendedEvent { userId: UserId; reason?: string; suspendedBy: string; timestamp: Date; } // 계정 정지
interface UserUnsuspendedEvent { userId: UserId; reason?: string; unsuspendedBy: string; timestamp: Date; } // 계정 정지 해제
interface UserBannedEvent { userId: UserId; reason?: string; bannedBy: string; timestamp: Date; }
interface UserTypeAssignedEvent { userId: UserId; userType: UserType; timestamp: Date; }
interface GuestAccountOnboardedEvent { userId: UserId; guestAccountId: string; region: string; expiresAt: Date; timestamp: Date; }
interface GuestAccountUpgradeInitiatedEvent { userId: UserId; linkSessionId: string; method: string; timestamp: Date; }
interface GuestAccountUpgradedEvent { userId: UserId; previousAccountType: AccountType; newAccountType: AccountType; upgradedAt: Date; timestamp: Date; }
interface GuestAccountExpiredEvent { userId: UserId; expiredAt: Date; triggeredBy: string; timestamp: Date; }
interface GuestAccountCleanedEvent { userId: UserId; cleanupId: string; timestamp: Date; }
interface UserAssignedToGroupEvent { userId: UserId; groupId: string; assignedBy: string; timestamp: Date; } // assignedBy: 'IAM_SYSTEM' | 'ADMIN_USER_ID'
interface UserRemovedFromGroupEvent { userId: UserId; groupId: string; removedBy: string; timestamp: Date; }
interface UserLinkedToPlanEvent { userId: UserId; planId: string; linkedBy: string; timestamp: Date; }
interface UserUnlinkedFromPlanEvent { userId: UserId; planId: string; unlinkedBy: string; timestamp: Date; }
interface UserDataDeletionRequestedEvent { userId: UserId; timestamp: Date; }
interface UserDataAccessRequestedEvent { userId: UserId; timestamp: Date; }

6.2 치료 활동 및 서비스 이용 기간 이벤트

// UserCycle 레벨 치료 활동 정지/재개 이벤트
interface UserCycleTreatmentSuspendedEvent { cycleId: string; userId: UserId; suspendedAt: Date; reason?: string; timestamp: Date; }
interface UserCycleTreatmentResumedEvent { cycleId: string; userId: UserId; resumedAt: Date; reason?: string; currentExpiresAt: Date; remainingDays: number; timestamp: Date; }
interface CycleServicePeriodExpiredEvent { cycleId: string; userId: UserId; expiredAt: Date; timestamp: Date; }
interface CycleRemainingServiceDaysCalculatedEvent { cycleId: string; userId: UserId; remainingDays: number; expiresAt: Date; timestamp: Date; }

6.3 사용자 주기 이벤트 (User 도메인에서 발생)

interface UserCycleCreatedEvent { userId: UserId; cycleId: string; planId: string; createdAt: Date; timestamp: Date; }
interface UserCycleStartedEvent { userId: UserId; cycleId: string; startedAt: Date; timestamp: Date; }
interface UserCycleCompletedEvent { userId: UserId; cycleId: string; completedAt: Date; timestamp: Date; }
interface UserCycleCancelledEvent { userId: UserId; cycleId: string; cancelledAt: Date; reason?: string; timestamp: Date; }
interface UserCycleDayIndexCalculatedEvent { userId: UserId; cycleId: string; dayIndex: number; timestamp: Date; }

6.4 가상 시간 설정 관련 이벤트 (TimeMachine 도메인에서 발생, User 도메인 정보 참조)

// 참고: 아래 이벤트들은 TimeMachine 도메인에서 발생하며, User 도메인의 정보(주기 시작일, 정지 이력 등)를 참조하여 발생될 수 있음.
interface VirtualTimeSetByDayIndexEvent { targetUserId: UserId; newTime: number; newTimezoneId: string; newDayIndex: number; changedBy: string; reason?: string; timestamp: Date; }
interface VirtualTimeSetBySuspensionDayEvent { targetUserId: UserId; newTime: number; newTimezoneId: string; suspensionSequence: number; dayWithinSuspension: number; changedBy: string; reason?: string; timestamp: Date; }
interface VirtualTimeResetEvent { targetUserId: UserId; newTime: number; newTimezoneId: string; changedBy: string; reason?: string; timestamp: Date; }

6.5 익명화 및 삭제 프로세스 관련 이벤트

interface UserAnonymizationInitiatedEvent { userId: UserId; anonymizationId: string; reason?: string; requestedBy: string; timestamp: Date; }
interface UserAnonymizationCompletedEvent { userId: UserId; anonymizationId: string; timestamp: Date; }
interface UserDeletionProcessInitiatedEvent { userId: UserId; processId: string; type: UserDeletionType; reason?: string; requestedBy: string; timestamp: Date; }
interface UserDeletionProcessCompletedEvent { userId: UserId; processId: string; timestamp: Date; }
interface UserDeletionProcessFailedEvent { userId: UserId; processId: string; errorMessage: string; timestamp: Date; }
interface UserDeletionProcessRetryRequestedEvent { processId: string; requestedBy: string; timestamp: Date; }
interface DomainDeletionStatusChangedEvent { processId: string; domainName: string; oldStatus: UserDeletionStatus; newStatus: UserDeletionStatus; timestamp: Date; }

7. 도메인 규칙

본 문서에서는 모델 구조에 대한 설명에 중점을 두고 있으며, 상세한 비즈니스 규칙은 User 도메인 비즈니스 규칙 문서를 참조하세요.

주요 규칙 카테고리는 다음과 같습니다:

  1. 사용자 계정 관리 규칙: 생성, 정보 관리, 상태 관리, 비활성화/삭제 규칙
  2. 사용자 프로필 관리 규칙: 정보, 접근 규칙
  3. 사용자 역할 및 권한 규칙: IAM 도메인 책임 명시
  4. 사용자 주기 관리 규칙: User 도메인 책임 명시 (주기 생성, 상태 관리, dayIndex 계산 규칙 등 포함)
  5. 테스터 관리 규칙: 사용자에게 'tester' 그룹/역할을 할당하는 것은 IAM 도메인의 책임입니다. User 도메인은 IAM 도메인에서 제공하는 사용자 그룹/역할 정보를 조회하여 테스터 여부를 판단할 수 있습니다. (IAM 도메인 연동 필요)
  6. 사용자 유형 관리 규칙: 유형 정의, 유형별 정책 규칙
  7. 개인정보 보호 및 규정 준수 규칙: GDPR 등

8. 데이터베이스 스키마 (Prisma)

참고: 아래는 private.users 공유 테이블의 원본(Source of Truth) Prisma 스키마 정의입니다.

generator client {
provider = "prisma-client-js"
}

datasource db {
provider = "postgresql"
url = env("DATABASE_URL")
schemas = ["user", "private"]
}

// 사용자 상태 열거형
enum UserStatus {
ACTIVE
INACTIVE
LOCKED
PENDING
SUSPENDED // 계정 정지
BANNED
}

// 사용자 계정 유형 열거형
enum UserType {
USER
}

// 사용자 주기 상태 열거형
enum UserCycleStatus {
PENDING
ACTIVE
COMPLETED
CANCELLED
}

// 사용자 데이터 삭제 유형 열거형
enum DeletionType {
SOFT_DELETE // 비활성화
ANONYMIZE // 익명화
HARD_DELETE // 완전 삭제 (개발 환경용)
}

// 사용자 데이터 삭제 상태 열거형
enum DeletionStatus {
PENDING // 대기
IN_PROGRESS // 처리 중
COMPLETED // 완료
FAILED // 실패
PARTIALLY_COMPLETED // 부분 완료
}

// 사용자 모델 (Auth 도메인과 공유, private 스키마)
model User {
id String @id @default(uuid())
email String @unique // Auth 도메인 관리 (로그인 ID)
passwordHash String? @map("password_hash") // Auth 도메인 관리
status UserStatus // User/Auth 도메인 공유 관리
userType UserType @map("user_type") // User 도메인 관리
lastLoginAt DateTime? @map("last_login_at") // Auth 도메인 관리
failedLoginAttempts Int @default(0) @map("failed_login_attempts") // Auth 도메인 관리
lockoutUntil DateTime? @map("lockout_until") // Auth 도메인 관리
profileData Json? @map("profile_data") // User 도메인 관리 (확장 필드)
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @updatedAt @map("updated_at")
deletedAt DateTime? @map("deleted_at") // User 도메인 관리 (Soft delete)

// 서비스 이용 기간 관련 필드 (User 도메인 관리)
isSuspended Boolean? @default(false) @map("is_suspended") // 치료 활동 일시 정지 여부
expiresAt DateTime? @map("expires_at") // Plan과 치료 활동 정지 이력을 바탕으로 계산/업데이트됨

// 익명화 관련 필드 (User 도메인 관리)
isAnonymized Boolean @default(false) @map("is_anonymized") // 익명화 처리 여부
anonymizedAt DateTime? @map("anonymized_at") // 익명화 처리 시점
anonymizationId String? @map("anonymization_id") // 익명화 프로세스 식별자
dataRetentionEndDate DateTime? @map("data_retention_end_date") // GDPR 데이터 보존 종료일

// 관계
profile UserProfile? // User 도메인 관리
statusHistory UserStatusHistory[] // User 도메인 관리
suspensionHistory UserAccountSuspensionHistory[] // User 도메인 관리
cycles UserCycle[] // User 도메인 관리
// Auth 도메인 관계 (예: RefreshToken) 는 Auth 도메인 스키마에서 정의
// IAM, Sleep 도메인 엔티티는 ID 목록 또는 별도 연관 테이블로 관리될 수 있음 (여기서는 직접 관계 정의 X)

// --- 관계 참조 (IAM 도메인 관리) ---
/** 사용자가 속한 그룹 ID 목록 (IAM 도메인에서 조회/관리됨) */
readonly groupIds String[] @map("group_ids")
/** 사용자에게 적용되는 플랜 ID (IAM 도메인에서 조회/관리됨) */
readonly planId String? @map("plan_id")

// --- 관계 참조 (User 도메인 관리) ---
/** 사용자의 UserCycle 목록 (필요시 조회) */
readonly cycles UserCycle[] @map("cycles")
/** 현재 활성화된 UserCycle ID (선택적, 편의 필드) */
readonly activeCycleId String? @map("active_cycle_id")

@@map("users")
@@schema("private")
}

// 사용자 프로필 모델 (private 스키마)
model UserProfile {
userId String @id @map("user_id")
language String? // 선호 언어
timezone String? // 타임존
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @updatedAt @map("updated_at")

// 관계
user User @relation(fields: [userId], references: [id], onDelete: Cascade)

@@map("user_profiles")
@@schema("private")
}

// 사용자 상태 변경 이력 모델 (private 스키마)
model UserStatusHistory {
id String @id @default(uuid())
userId String @map("user_id")
oldStatus UserStatus @map("old_status")
newStatus UserStatus @map("new_status")
reason String? // 변경 사유
changedBy String @map("changed_by") // 변경 주체 (user ID 또는 'system' 또는 'auth_system')
changedAt DateTime @default(now()) @map("changed_at")

// 관계
user User @relation(fields: [userId], references: [id], onDelete: Cascade)

@@index([userId, changedAt])
@@map("user_status_history")
@@schema("private")
}

// 사용자 계정 정지 이력 모델 (private 스키마)
model UserAccountSuspensionHistory {
id String @id @default(uuid())
userId String @map("user_id")
suspendedAt DateTime @map("suspended_at")
resumedAt DateTime? @map("resumed_at")
suspensionReason String? @map("suspension_reason")
resumptionReason String? @map("resumption_reason")
durationSeconds BigInt? @map("duration_seconds") // 재개 시 계산된 초 단위 기간

// 관계
user User @relation(fields: [userId], references: [id], onDelete: Cascade)

@@index([userId, suspendedAt])
@@map("user_account_suspension_history")
@@schema("private")
}

// 사용자 주기 모델 (private 스키마)
model UserCycle {
id String @id @default(uuid())
userId String @map("user_id")
planId String @map("plan_id") // IAM 도메인의 Plan ID 참조
startedAt DateTime @map("started_at")
completedAt DateTime? @map("completed_at")
cancelledAt DateTime? @map("cancelled_at")
status UserCycleStatus // 주기 상태
isSuspended Boolean @default(false) @map("is_suspended") // 치료 활동 일시 정지 여부
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @updatedAt @map("updated_at")

// 관계
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
suspensionHistory UserCycleSuspensionHistory[]

@@index([userId, startedAt])
@@index([userId, status]) // 활성 주기 조회 등
@@map("user_cycles")
@@schema("private")
}

// 사용자 주기 일시 정지 이력 모델 (private 스키마)
model UserCycleSuspensionHistory {
id String @id @default(uuid())
userCycleId String @map("user_cycle_id")
suspendedAt DateTime @map("suspended_at")
resumedAt DateTime? @map("resumed_at")
suspensionReason String? @map("suspension_reason")
resumptionReason String? @map("resumption_reason")
durationSeconds BigInt? @map("duration_seconds") // 재개 시 계산된 초 단위 기간

// 관계
userCycle UserCycle @relation(fields: [userCycleId], references: [id], onDelete: Cascade)

@@index([userCycleId, suspendedAt])
@@map("user_cycle_suspension_history")
@@schema("private")
}

// 사용자 데이터 삭제 프로세스 모델 (private 스키마)
model UserDeletionProcess {
id String @id @default(uuid())
userId String @map("user_id")
initiatedAt DateTime @default(now()) @map("initiated_at")
completedAt DateTime? @map("completed_at")
type DeletionType @map("type")
status DeletionStatus @default(IN_PROGRESS) @map("status")
reason String?
requestedBy String @map("requested_by")
domainStatuses Json @map("domain_statuses") // 각 도메인별 삭제 상태 정보 저장

// 관계
domainStatusRecords DomainDeletionStatus[]

@@index([userId])
@@index([status])
@@map("user_deletion_processes")
@@schema("private")
}

// 도메인별 삭제 상태 기록 모델 (private 스키마)
model DomainDeletionStatus {
id String @id @default(uuid())
deletionProcessId String @map("deletion_process_id")
domainName String @map("domain_name") // USER, AUTH, IAM, SLEEP, etc.
status DeletionStatus @default(PENDING) @map("status")
startedAt DateTime? @map("started_at")
completedAt DateTime? @map("completed_at")
errorMessage String? @map("error_message")
retryCount Int @default(0) @map("retry_count")

// 관계
deletionProcess UserDeletionProcess @relation(fields: [deletionProcessId], references: [id], onDelete: Cascade)

@@unique([deletionProcessId, domainName])
@@index([deletionProcessId])
@@index([status])
@@map("domain_deletion_statuses")
@@schema("private")
}

// 익명화 기록 모델 (private 스키마)
model AnonymizationRecord {
id String @id @default(uuid())
userId String @map("user_id")
originalEmail String @map("original_email")
anonymizedAt DateTime @default(now()) @map("anonymized_at")
reason String?
requestedBy String @map("requested_by")
originalData Json? @map("original_data") // 복구 가능성을 위한 원본 데이터 (암호화 필요)

@@index([userId])
@@index([anonymizedAt])
@@map("anonymization_records")
@@schema("private")
}

참고: 위 Prisma 스키마는 private.users 테이블의 통합된 정의입니다. 각 필드의 관리 주체는 주석을 참고하세요. UserCycle 모델이 private 스키마에 추가되었습니다.

9. 변경 이력

버전날짜작성자변경 내용
0.1.02025-04-18bok@weltcorp.com최초 작성
0.2.02025-05-06bok@weltcorp.comUserCycle 관리 책임을 User 도메인으로 변경하고 모델 상세 내용 추가 (ERD, 엔티티, 집계, 서비스, 이벤트, 스키마, 규칙)
0.2.12025-05-06bok@weltcorp.com'서비스 이용 기간 중단' 용어를 '치료 활동 일시 정지'로 변경 및 관련 모델/서비스/이벤트명 수정
0.3.02025-08-06bok@weltcorp.com'일시 정지 기간 중 날짜 이동' 관련 서비스 메서드 예시 및 이벤트 정의 추가
0.4.02025-05-08bok@weltcorp.com사용자 데이터 삭제 및 익명화 관련 모델, 필드, 서비스 추가. DeletionType, DeletionStatus 열거형, UserDeletionProcess, DomainDeletionStatus, AnonymizationRecord 모델 추가. 익명화 관련 필드(isAnonymized, anonymizedAt, anonymizationId, dataRetentionEndDate) 추가
0.5.02025-06-14bok@weltcorp.comService Account 관련 모델, 서비스, 이벤트를 IAM 도메인으로 이관, USER 타입 전용 관리로 변경
0.6.02025-01-21bok@weltcorp.com계정 정지와 치료 활동 일시 정지 분리. UserSuspensionHistory를 UserAccountSuspensionHistory로 변경하고 UserCycleSuspensionHistory 추가. UserCycle에 isSuspended 필드 추가
1.0.02025-12-17bok@weltcorp.com게스트 아키텍처 개선 (v1.2.0): 게스트도 User+UserCycle 생성, IAM Group 기반 분류(guests.*), Plan 기반 기능 제어(plan.guest), Step-up 시 userId 유지, GuestLifecycleEventType에 VOLUNTARILY_WITHDRAWN 추가