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.*vspatients.*)과 Plan(plan.guestvsplan.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 레코드 | 치료 사이클 관리 시작 |
GuestAccountLifecycle | ONBOARDED 이벤트 기록 |
| IAM Group 할당 | guests 또는 guests.{region} 그룹 |
| Plan 할당 | plan.guest 플랜 |
Step-up 시 변경 사항 (userId 유지):
| 항목 | 변경 전 | 변경 후 |
|---|---|---|
| userId | abc-123 | abc-123 (유지) |
| IAM Group | guests.kr | patients.kr, patients.general |
| Plan | plan.guest | plan.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 되지 않은) 사용자만 활성 관련 작업 가능.UserProfile은User당 하나만 존재.- User 레벨의
isSuspended는 deprecated이며, 치료 활동 일시 정지는UserCycle.isSuspended로 관리됨. UserCycle의isSuspended상태와UserCycleSuspensionHistory의 마지막 레코드는 일관성을 유지해야 함.UserCycle의 서비스 만료일은Plan정보와UserCycleSuspensionHistory를 기반으로 계산됨.- 사용자는 동시에 여러 개의
ACTIVE상태UserCycle을 가질 수 없음. UserCycle의planId는 사용자가 해당 시점에 할당된Plan과 일치해야 함 ([IAM 도메인] 참조).UserCycle의 상태 전이 규칙 준수 (PENDING->ACTIVE->COMPLETED/CANCELLED).- 익명화된 사용자(
isAnonymized = true)는 수정 불가능하며, 로그인 불가능함. - 익명화 프로세스에서는 모든 PII 데이터가 익명화되어야 함.
- 사용자 삭제 프로세스는 일관된 상태를 유지해야 함 (전체 삭제 상태와 도메인별 삭제 상태).
- 모든 사용자는 고유한
- Root:
-
UserDeletionProcess Aggregate
- Root:
UserDeletionProcess - Entities:
DomainDeletionStatus - Value Objects:
UserDeletionType,UserDeletionStatus - 불변식:
- 각
UserDeletionProcess는 고유한 ID를 가짐. - 하나의
UserDeletionProcess는 여러DomainDeletionStatus를 가질 수 있음. UserDeletionProcess의 status는 하위DomainDeletionStatus들의 상태에 따라 결정됨.- 모든 도메인이 완료(COMPLETED) 상태일 때만 전체 프로세스가 완료(COMPLETED) 상태가 될 수 있음.
- 각
- Root:
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 도메인 비즈니스 규칙 문서를 참조하세요.
주요 규칙 카테고리는 다음과 같습니다:
- 사용자 계정 관리 규칙: 생성, 정보 관리, 상태 관리, 비활성화/삭제 규칙
- 사용자 프로필 관리 규칙: 정보, 접근 규칙
- 사용자 역할 및 권한 규칙: IAM 도메인 책임 명시
- 사용자 주기 관리 규칙: User 도메인 책임 명시 (주기 생성, 상태 관리, dayIndex 계산 규칙 등 포함)
- 테스터 관리 규칙: 사용자에게 'tester' 그룹/역할을 할당하는 것은 IAM 도메인의 책임입니다. User 도메인은 IAM 도메인에서 제공하는 사용자 그룹/역할 정보를 조회하여 테스터 여부를 판단할 수 있습니다. (IAM 도메인 연동 필요)
- 사용자 유형 관리 규칙: 유형 정의, 유형별 정책 규칙
- 개인정보 보호 및 규정 준수 규칙: 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.0 | 2025-04-18 | bok@weltcorp.com | 최초 작성 |
| 0.2.0 | 2025-05-06 | bok@weltcorp.com | UserCycle 관리 책임을 User 도메인으로 변경하고 모델 상세 내용 추가 (ERD, 엔티티, 집계, 서비스, 이벤트, 스키마, 규칙) |
| 0.2.1 | 2025-05-06 | bok@weltcorp.com | '서비스 이용 기간 중단' 용어를 '치료 활동 일시 정지'로 변경 및 관련 모델/서비스/이벤트명 수정 |
| 0.3.0 | 2025-08-06 | bok@weltcorp.com | '일시 정지 기간 중 날짜 이동' 관련 서비스 메서드 예시 및 이벤트 정의 추가 |
| 0.4.0 | 2025-05-08 | bok@weltcorp.com | 사용자 데이터 삭제 및 익명화 관련 모델, 필드, 서비스 추가. DeletionType, DeletionStatus 열거형, UserDeletionProcess, DomainDeletionStatus, AnonymizationRecord 모델 추가. 익명화 관련 필드(isAnonymized, anonymizedAt, anonymizationId, dataRetentionEndDate) 추가 |
| 0.5.0 | 2025-06-14 | bok@weltcorp.com | Service Account 관련 모델, 서비스, 이벤트를 IAM 도메인으로 이관, USER 타입 전용 관리로 변경 |
| 0.6.0 | 2025-01-21 | bok@weltcorp.com | 계정 정지와 치료 활동 일시 정지 분리. UserSuspensionHistory를 UserAccountSuspensionHistory로 변경하고 UserCycleSuspensionHistory 추가. UserCycle에 isSuspended 필드 추가 |
| 1.0.0 | 2025-12-17 | bok@weltcorp.com | 게스트 아키텍처 개선 (v1.2.0): 게스트도 User+UserCycle 생성, IAM Group 기반 분류(guests.*), Plan 기반 기능 제어(plan.guest), Step-up 시 userId 유지, GuestLifecycleEventType에 VOLUNTARILY_WITHDRAWN 추가 |