Relaxation 도메인 모델
데이터 저장 규칙
- 개인정보 관련 데이터는 private 스키마에 저장되어야 합니다.
- 비개인정보 데이터는 relaxation 스키마에 저장되어야 합니다.
- 개인정보 포함 여부에 따라 위 규칙을 우선적으로 적용합니다.
- Relaxation 도메인에서는 RelaxationPlaybackHistory, RelaxationContentCompletion, RelaxationUserSetting, RelaxationRecommendationStatus, RelaxationDownloadStatus 등 사용자 ID와 연결된 데이터는 모두 개인정보로 취급하여 private 스키마에 저장합니다.
- 사용자와 연결되지 않은 데이터(RelaxationContent, RelaxationCategory, RelaxationContentDetail, RelaxationUsageGuide 등)만 relaxation 스키마에 저장합니다.
1. 엔티티 관계도 (ERD)
2. 엔티티
2.1 RelaxationContent
interface RelaxationContent {
id: string;
categoryId: string;
durationSeconds: number; // 초 단위
orderIndex: number;
level: RelaxationLevel;
stress: RelaxationStress;
poses: RelaxationPose[];
isActive: boolean;
createdAt: Date;
updatedAt: Date;
recommendationDayIndex?: number; // 1~5일차, null이면 일반 콘텐츠
}
enum RelaxationLevel {
EASY = 'EASY',
MEDIUM = 'MEDIUM',
DIFFICULT = 'DIFFICULT',
}
enum RelaxationStress {
LOW = 'LOW',
MEDIUM = 'MEDIUM',
HIGH = 'HIGH',
}
enum RelaxationPose {
LYING_DOWN = 'LYING_DOWN',
SITTING = 'SITTING',
STANDING = 'STANDING',
WALKING = 'WALKING',
}
2.2 RelaxationContentTranslation
interface RelaxationContentTranslation {
id: string;
contentId: string;
thumbnailImageUrl: string;
lottieFileUrl: string;
backgroundLottieUrl?: string;
backgroundImageUrl?: string;
audioFileUrl: string;
language: string;
title: string;
summary: string;
createdAt: Date;
updatedAt: Date;
}
2.3 RelaxationCategory
interface RelaxationCategory {
id: string;
orderIndex: number;
isActive: boolean;
createdAt: Date;
updatedAt: Date;
}
2.4 RelaxationCategoryTranslation
interface RelaxationCategoryTranslation {
id: string;
categoryId: string;
language: string;
title: string;
summary: string;
createdAt: Date;
updatedAt: Date;
}
2.5 RelaxationPlaybackHistory
interface RelaxationPlaybackHistory {
id: string;
userId: string;
userCycleId: string;
contentId: string;
startedAt: Date;
endedAt?: Date;
playbackPosition: number; // 초 단위
isCompleted: boolean;
createdAt: Date;
updatedAt: Date;
}
2.6 RelaxationContentCompletion
interface RelaxationContentCompletion {
id: string;
userId: string;
userCycleId: string;
contentId: string;
completedAt: Date;
createdAt: Date;
}
2.7 RelaxationUserSetting
interface RelaxationUserSetting {
id: string;
userId: string;
userCycleId: string;
repeatMode: boolean;
preferredCategory?: string;
lastPlayedContentId?: string;
createdAt: Date;
updatedAt: Date;
}
2.8 RelaxationRecommendationStatus
interface RelaxationRecommendationStatus {
id: string;
userId: string;
userCycleId: string;
registrationDate: Date;
currentDay: number;
isExpired: boolean;
createdAt: Date;
updatedAt: Date;
}
2.9 RelaxationContentDetail
interface RelaxationContentDetail {
id: string;
contentId: string;
version: string;
createdAt: Date;
updatedAt: Date;
}
2.10 RelaxationContentDetailTranslation
interface RelaxationContentDetailTranslation {
id: string;
detailId: string;
language: string;
purpose: string;
effects: string;
mechanism: string;
precautions?: string;
createdAt: Date;
updatedAt: Date;
}
2.11 RelaxationUsageGuide
interface RelaxationUsageGuide {
id: string;
version: string;
isActive: boolean;
createdAt: Date;
updatedAt: Date;
}
2.12 RelaxationUsageGuideTranslation
interface RelaxationUsageGuideTranslation {
id: string;
guideId: string;
language: string;
content: string;
createdAt: Date;
updatedAt: Date;
}
2.13 RelaxationDownloadStatus
interface RelaxationDownloadStatus {
id: string;
userId: string;
userCycleId: string;
contentId: string;
downloadProgress: number; // 0-100 백분율
status: DownloadStatusType;
cacheLocation?: string;
createdAt: Date;
updatedAt: Date;
}
enum DownloadStatusType {
PENDING = 'PENDING',
IN_PROGRESS = 'IN_PROGRESS',
PAUSED = 'PAUSED',
COMPLETED = 'COMPLETED',
FAILED = 'FAILED',
}
2.14 RelaxationContentBundle
interface RelaxationContentBundle {
id: string;
bundleName?: string;
version: string;
isActive: boolean;
generatedAt: Date;
createdAt: Date;
updatedAt: Date;
}
2.15 RelaxationBundleContent
interface RelaxationBundleContent {
id: string;
bundleId: string;
contentId: string;
createdAt: Date;
}
3. 값 객체
3.1 CategoryInfo
interface CategoryInfo {
categoryId: string;
name: string;
summary: string;
heroImageUrl: string;
isExpanded: boolean;
}
3.2 PlaybackState
interface PlaybackState {
isPlaying: boolean;
currentPosition: number;
duration: number;
isRepeating: boolean;
}
3.3 DownloadProgress
interface DownloadProgress {
progress: number; // 0-100
bytesDownloaded: number;
totalBytes: number;
}
3.4 RecommendationInfo
interface RecommendationInfo {
day: number;
contentId: string;
contentTitle: string;
}
3.5 ContentMetadata
interface ContentMetadata {
contentId: string;
title: string;
durationSeconds: number;
category: string;
thumbnailImageUrl: string;
backgroundLottieUrl?: string;
backgroundImageUrl?: string;
}
3.6 GuideVersion
interface GuideVersion {
version: string;
updatedAt: Date;
}
3.7 CompletionInfo
interface CompletionInfo {
isCompleted: boolean;
completedAt?: Date;
}
3.8 AudioMetadata
interface AudioMetadata {
format: string;
bitrate: number;
duration: number;
size: number;
}
3.9 ThumbnailImageInfo
interface ThumbnailImageInfo {
url: string;
width: number;
height: number;
format: string;
}
3.10 BackgroundMediaInfo
interface BackgroundMediaInfo {
url: string;
type: 'video' | 'lottie' | 'image';
width?: number;
height?: number;
format?: string;
duration?: number;
}
3.11 CacheInfo
interface CacheInfo {
location: string;
size: number;
lastAccessedAt: Date;
expiresAt?: Date;
}
3.12 RelaxationBundleInfo
interface RelaxationBundleInfo {
currentVersion: string;
clientVersion: string;
needsUpdate: boolean;
bundleName?: string;
generatedAt: Date;
}
3.13 RelaxationContentBundleMetadata
interface RelaxationContentBundleMetadata {
bundleId: string;
version: string;
hash: string;
contentCount: number;
totalSize: number;
}
4. 집계 (Aggregates)
-
이완요법 콘텐츠(RelaxationContentAggregate)
- Root: RelaxationContent
- Entities: RelaxationContentDetail, RelaxationDownloadStatus
- Value Objects: ContentMetadata, AudioMetadata, ThumbnailImageInfo, BackgroundMediaInfo
-
카테고리 관리(RelaxationCategoryManagement)
- Root: RelaxationCategory
- Entities: RelaxationContent
- Value Objects: CategoryInfo
-
재생 관리(RelaxationPlaybackManagement)
- Root: RelaxationPlaybackHistory
- Entities: RelaxationContentCompletion
- Value Objects: PlaybackState, CompletionInfo
-
추천 관리(RelaxationRecommendationManagement)
- Root: RelaxationRecommendationStatus
- Value Objects: RecommendationInfo
-
사용자 설정(RelaxationUserSettings)
- Root: RelaxationUserSetting
- Value Objects: -
-
사용 가이드(RelaxationUsageGuideAggregate)
- Root: RelaxationUsageGuide
- Value Objects: GuideVersion
-
다운로드 관리(RelaxationDownloadManagement)
- Root: RelaxationDownloadStatus
- Value Objects: DownloadProgress, CacheInfo
-
이완요법 콘텐츠 번들 관리(RelaxationContentBundleManagement)
- Root: RelaxationContentBundle
- Entities: RelaxationContent
- Value Objects: RelaxationBundleInfo, RelaxationContentBundleMetadata
5. 도메인 서비스
5.1 ContentManagementService
interface ContentManagementService {
getContentList(): Promise<RelaxationContent[]>;
getContentByCategory(categoryId: string): Promise<RelaxationContent[]>;
getContentMetadata(contentId: string): Promise<ContentMetadata>;
getContentDetail(contentId: string): Promise<RelaxationContentDetail>;
updateContentVersion(contentId: string): Promise<RelaxationContentDetail>;
validateContentAccess(userId: string, contentId: string): Promise<boolean>;
getCategoryList(): Promise<RelaxationCategory[]>;
}
5.2 PlaybackControlService
interface PlaybackControlService {
startPlayback(userId: string, contentId: string): Promise<RelaxationPlaybackHistory>;
pausePlayback(userId: string, contentId: string): Promise<RelaxationPlaybackHistory>;
resumePlayback(userId: string, contentId: string): Promise<RelaxationPlaybackHistory>;
stopPlayback(userId: string, contentId: string): Promise<RelaxationPlaybackHistory>;
toggleRepeatMode(userId: string): Promise<RelaxationUserSetting>;
markContentAsCompleted(userId: string, contentId: string): Promise<RelaxationContentCompletion>;
getPlaybackState(userId: string, contentId: string): Promise<PlaybackState>;
savePlaybackPosition(userId: string, contentId: string, position: number): Promise<void>;
}
5.3 RecommendationService
interface RecommendationService {
getDailyRecommendation(userId: string): Promise<RelaxationContent | null>;
initializeRecommendationStatus(userId: string): Promise<RelaxationRecommendationStatus>;
updateRecommendationDay(userId: string): Promise<RelaxationRecommendationStatus>;
checkRecommendationPeriod(userId: string): Promise<boolean>;
getRecommendationHistory(userId: string): Promise<RecommendationInfo[]>;
}
5.4 DownloadService
interface DownloadService {
startContentDownload(userId: string, contentId: string): Promise<RelaxationDownloadStatus>;
updateDownloadProgress(userId: string, contentId: string, progress: number): Promise<RelaxationDownloadStatus>;
pauseDownload(userId: string, contentId: string): Promise<RelaxationDownloadStatus>;
resumeDownload(userId: string, contentId: string): Promise<RelaxationDownloadStatus>;
completeDownload(userId: string, contentId: string, cacheLocation: string): Promise<RelaxationDownloadStatus>;
getCachedContent(userId: string, contentId: string): Promise<CacheInfo | null>;
clearCache(userId: string): Promise<void>;
}
5.5 GuideService
interface GuideService {
getActiveGuide(): Promise<RelaxationUsageGuide>;
updateGuideVersion(guideId: string, content: string): Promise<RelaxationUsageGuide>;
getGuideHistory(): Promise<RelaxationUsageGuide[]>;
markGuideAsViewed(userId: string, guideId: string): Promise<void>;
}
5.6 SecurityService
interface SecurityService {
validateUserAccess(userId: string, resourceId: string, resourceType: string): Promise<boolean>;
generateSecureDownloadUrl(contentId: string): Promise<string>;
logAccessAttempt(userId: string, resourceId: string, success: boolean): Promise<void>;
preventUnauthorizedCopy(contentId: string): Promise<void>;
}
5.7 CategoryManagementService
interface CategoryManagementService {
toggleCategoryExpansion(categoryId: string, isExpanded: boolean): Promise<void>;
getCategoryWithContents(categoryId: string): Promise<{ category: RelaxationCategory; contents: RelaxationContent[] }>;
getCategorySummary(categoryId: string): Promise<string>;
}
5.8 UserSettingService
interface UserSettingService {
getUserSettings(userId: string): Promise<RelaxationUserSetting>;
updateRepeatMode(userId: string, enabled: boolean): Promise<RelaxationUserSetting>;
updatePreferredCategory(userId: string, categoryId: string): Promise<RelaxationUserSetting>;
updateLastPlayedContent(userId: string, contentId: string): Promise<RelaxationUserSetting>;
getDefaultSettings(): Promise<RelaxationUserSetting>;
}
5.9 RelaxationContentBundleService
interface RelaxationContentBundleService {
getActiveBundle(): Promise<RelaxationContentBundle>;
checkBundleUpdate(clientVersion: string): Promise<RelaxationBundleInfo>;
createNewBundle(contents: RelaxationContent[], bundleName?: string): Promise<RelaxationContentBundle>;
activateBundle(bundleId: string): Promise<RelaxationContentBundle>;
getBundleMetadata(bundleId: string): Promise<RelaxationContentBundleMetadata>;
validateBundleIntegrity(bundleId: string): Promise<boolean>;
getBundleHistory(): Promise<RelaxationContentBundle[]>;
}
5.10 RelaxationContentCacheService
interface RelaxationContentCacheService {
cacheContentBundle(bundle: RelaxationContentBundle, contents: RelaxationContent[]): Promise<void>;
getCachedBundle(): Promise<{ bundle: RelaxationContentBundle; contents: RelaxationContent[] } | null>;
updateCachedBundle(newBundle: RelaxationContentBundle, contents: RelaxationContent[]): Promise<void>;
clearCache(): Promise<void>;
getCachedBundleVersion(): Promise<string | null>;
validateCacheIntegrity(): Promise<boolean>;
getCacheSize(): Promise<number>;
optimizeCache(): Promise<void>;
}
6. 도메인 이벤트
6.1 콘텐츠 관련 이벤트
interface RelaxationContentListRequestedEvent {
userId: string;
timestamp: Date;
}
interface ContentMetadataRetrievedEvent {
contentId: string;
userId: string;
timestamp: Date;
}
interface ContentDownloadStartedEvent {
userId: string;
contentId: string;
timestamp: Date;
}
interface ContentDownloadProgressUpdatedEvent {
userId: string;
contentId: string;
progress: number;
timestamp: Date;
}
interface ContentDownloadCompletedEvent {
userId: string;
contentId: string;
cacheLocation: string;
timestamp: Date;
}
interface ContentDownloadResumedEvent {
userId: string;
contentId: string;
timestamp: Date;
}
interface ContentPlaybackRecordedEvent {
userId: string;
contentId: string;
startedAt: Date;
timestamp: Date;
}
interface ContentCompletedEvent {
userId: string;
contentId: string;
completedAt: Date;
timestamp: Date;
}
interface ContentPlaybackStateSavedEvent {
userId: string;
contentId: string;
position: number;
timestamp: Date;
}
interface ContentAccessValidatedEvent {
userId: string;
contentId: string;
accessGranted: boolean;
timestamp: Date;
}
interface ContentDetailInfoProvidedEvent {
userId: string;
contentId: string;
timestamp: Date;
}
interface ContentBundleVersionCheckedEvent {
clientVersion: string;
serverVersion: string;
needsUpdate: boolean;
bundleName?: string;
timestamp: Date;
}
interface ContentBundleCachedEvent {
bundleId: string;
version: string;
itemCount: number;
cacheSize: number;
timestamp: Date;
}
interface ContentBundleUpdatedEvent {
oldBundleId?: string;
newBundleId: string;
oldVersion?: string;
newVersion: string;
contentCount: number;
timestamp: Date;
}
interface BundleVersionMismatchDetectedEvent {
clientVersion: string;
serverVersion: string;
timestamp: Date;
}
interface BundleCacheValidationFailedEvent {
bundleId: string;
version: string;
reason: string;
timestamp: Date;
}
interface ContentBundleActivatedEvent {
bundleId: string;
version: string;
activatedAt: Date;
timestamp: Date;
}
6.2 추천 관련 이벤트
interface DailyRecommendationGeneratedEvent {
userId: string;
contentId: string;
day: number;
timestamp: Date;
}
interface RecommendationPeriodExpiredEvent {
userId: string;
expirationDate: Date;
timestamp: Date;
}
6.3 카테고리 관련 이벤트
interface CategoryExpandedEvent {
userId: string;
categoryId: string;
timestamp: Date;
}
interface CategoryCollapsedEvent {
userId: string;
categoryId: string;
timestamp: Date;
}
interface ContentCategoryRetrievedEvent {
categoryId: string;
timestamp: Date;
}
6.4 가이드 관련 이벤트
interface GuideAccessedEvent {
userId: string;
guideId: string;
timestamp: Date;
}
interface GuideVersionUpdatedEvent {
guideId: string;
oldVersion: string;
newVersion: string;
timestamp: Date;
}
6.5 재생 관련 이벤트
interface PlaybackHistoryRecordedEvent {
userId: string;
contentId: string;
duration: number;
timestamp: Date;
}
interface RepeatModeChangedEvent {
userId: string;
enabled: boolean;
timestamp: Date;
}
interface PlaybackPausedEvent {
userId: string;
contentId: string;
position: number;
timestamp: Date;
}
interface PlaybackResumedEvent {
userId: string;
contentId: string;
position: number;
timestamp: Date;
}
6.6 UI 관련 이벤트
interface BottomSheetDisplayedEvent {
userId: string;
contentId: string;
timestamp: Date;
}
interface BottomSheetConfirmedEvent {
userId: string;
contentId: string;
timestamp: Date;
}
interface BottomSheetCancelledEvent {
userId: string;
contentId: string;
timestamp: Date;
}
interface ExitConfirmationPopupDisplayedEvent {
userId: string;
contentId: string;
timestamp: Date;
}
interface ContentExitConfirmedEvent {
userId: string;
contentId: string;
timestamp: Date;
}
interface ContentExitCancelledEvent {
userId: string;
contentId: string;
timestamp: Date;
}
7. 데이터베이스 스키마 (Prisma)
generator client {
provider = "prisma-client-js"
}
datasource db {
provider = "postgresql"
url = env("DATABASE_URL")
schemas = ["relaxation", "private", "public"]
}
// Enums (public 스키마에 정의됨 - 여러 도메인에서 공통 사용)
enum DownloadStatusType {
PENDING
IN_PROGRESS
PAUSED
COMPLETED
FAILED
@@schema("public")
}
// Relaxation 도메인 전용 enum들
enum RelaxationLevel {
EASY
MEDIUM
DIFFICULT
@@schema("relaxation")
}
enum RelaxationStress {
LOW
MEDIUM
HIGH
@@schema("relaxation")
}
enum RelaxationPose {
LYING_DOWN
SITTING
STANDING
WALKING
@@schema("relaxation")
}
// 비개인정보 - relaxation 스키마
model RelaxationContent {
id String @id @default(uuid())
categoryId String @map("category_id")
durationSeconds Int @map("duration_seconds")
orderIndex Int @map("order_index")
level RelaxationLevel
stress RelaxationStress
poses RelaxationPose[]
isActive Boolean @default(true) @map("is_active")
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @updatedAt @map("updated_at")
recommendationDayIndex Int? @map("recommendation_day_index") // 1~5일차, null이면 일반 콘텐츠
// 관계
category RelaxationCategory @relation(fields: [categoryId], references: [id])
translations RelaxationContentTranslation[]
details RelaxationContentDetail[]
bundleContents RelaxationBundleContent[]
// 개인정보와 관련된 관계를 위한 가상 필드 (실제 연결은 private 스키마에서 함)
playbackHistories RelaxationPlaybackHistory[]
completions RelaxationContentCompletion[]
downloadStatuses RelaxationDownloadStatus[]
@@index([categoryId, orderIndex])
@@index([categoryId])
@@index([recommendationDayIndex]) // 추천 콘텐츠 조회 최적화
@@map("relaxation_contents")
@@schema("relaxation")
}
model RelaxationContentTranslation {
id String @id @default(uuid())
contentId String @map("content_id")
thumbnailImageUrl String @map("thumbnail_image_url")
lottieFileUrl String @map("lottie_file_url")
backgroundLottieUrl String? @map("background_lottie_url")
backgroundImageUrl String? @map("background_image_url")
audioFileUrl String @map("audio_file_url")
language String
title String
summary String
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @updatedAt @map("updated_at")
// 관계
content RelaxationContent @relation(fields: [contentId], references: [id], onDelete: Cascade)
@@unique([contentId, language])
@@index([language])
@@map("relaxation_content_translations")
@@schema("relaxation")
}
model RelaxationCategory {
id String @id @default(uuid())
orderIndex Int @map("order_index")
isActive Boolean @default(true) @map("is_active")
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @updatedAt @map("updated_at")
// 관계
contents RelaxationContent[]
translations RelaxationCategoryTranslation[]
@@index([orderIndex])
@@map("categories")
@@schema("relaxation")
}
model RelaxationCategoryTranslation {
id String @id @default(uuid())
categoryId String @map("category_id")
language String
title String
summary String
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @updatedAt @map("updated_at")
// 관계
category RelaxationCategory @relation(fields: [categoryId], references: [id], onDelete: Cascade)
@@unique([categoryId, language])
@@index([language])
@@map("category_translations")
@@schema("relaxation")
}
model RelaxationContentDetail {
id String @id @default(uuid())
contentId String @map("content_id")
version String @default("1.0.0")
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @updatedAt @map("updated_at")
// 관계
content RelaxationContent @relation(fields: [contentId], references: [id], onDelete: Cascade)
translations RelaxationContentDetailTranslation[]
@@unique([contentId, version])
@@index([contentId])
@@map("relaxation_content_details")
@@schema("relaxation")
}
model RelaxationContentDetailTranslation {
id String @id @default(uuid())
detailId String @map("detail_id")
language String
purpose String @db.Text
effects String @db.Text
mechanism String @db.Text
precautions String? @db.Text
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @updatedAt @map("updated_at")
// 관계
detail RelaxationContentDetail @relation(fields: [detailId], references: [id], onDelete: Cascade)
@@unique([detailId, language])
@@index([language])
@@map("relaxation_content_detail_translations")
@@schema("relaxation")
}
model RelaxationUsageGuide {
id String @id @default(uuid())
version String @default("1.0.0")
isActive Boolean @default(true) @map("is_active")
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @updatedAt @map("updated_at")
// 관계
translations RelaxationUsageGuideTranslation[]
@@index([isActive])
@@map("usage_guides")
@@schema("relaxation")
}
model RelaxationUsageGuideTranslation {
id String @id @default(uuid())
guideId String @map("guide_id")
language String
content String @db.Text
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @updatedAt @map("updated_at")
// 관계
guide RelaxationUsageGuide @relation(fields: [guideId], references: [id], onDelete: Cascade)
@@unique([guideId, language])
@@index([language])
@@map("usage_guide_translations")
@@schema("relaxation")
}
model RelaxationContentBundle {
id String @id @default(uuid())
bundleName String? @map("bundle_name")
version String @unique
isActive Boolean @default(false) @map("is_active")
generatedAt DateTime @default(now()) @map("generated_at")
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @updatedAt @map("updated_at")
// 관계
bundleContents RelaxationBundleContent[]
@@index([isActive])
@@index([generatedAt])
@@index([version])
@@map("relaxation_content_bundles")
@@schema("relaxation")
}
// 번들-콘텐츠 관계 테이블 (relaxation 스키마)
model RelaxationBundleContent {
id String @id @default(uuid())
bundleId String @map("bundle_id")
contentId String @map("content_id")
createdAt DateTime @default(now()) @map("created_at")
// 관계
bundle RelaxationContentBundle @relation(fields: [bundleId], references: [id], onDelete: Cascade)
content RelaxationContent @relation(fields: [contentId], references: [id], onDelete: Cascade)
@@unique([bundleId, contentId])
@@index([bundleId])
@@index([contentId])
@@map("bundle_contents")
@@schema("relaxation")
}
// 개인정보 - private 스키마
model RelaxationPlaybackHistory {
id String @id @default(uuid())
userId String @map("user_id")
userCycleId String @map("user_cycle_id")
contentId String @map("content_id")
startedAt DateTime @map("started_at")
endedAt DateTime? @map("ended_at")
playbackPosition Int @map("playback_position") // 초 단위
isCompleted Boolean @default(false) @map("is_completed")
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @updatedAt @map("updated_at")
// 관계
content RelaxationContent @relation(fields: [contentId], references: [id], onDelete: Cascade)
@@index([userId, userCycleId])
@@index([contentId])
@@index([userId])
@@index([userCycleId])
@@map("relaxation_playback_histories")
@@schema("private")
}
model RelaxationContentCompletion {
id String @id @default(uuid())
userId String @map("user_id")
userCycleId String @map("user_cycle_id")
contentId String @map("content_id")
completedAt DateTime @map("completed_at")
createdAt DateTime @default(now()) @map("created_at")
// 관계
content RelaxationContent @relation(fields: [contentId], references: [id], onDelete: Cascade)
@@unique([userId, userCycleId, contentId])
@@index([userId, userCycleId])
@@index([contentId])
@@index([userId])
@@index([userCycleId])
@@map("relaxation_content_completions")
@@schema("private")
}
model RelaxationUserSetting {
id String @id @default(uuid())
userId String @map("user_id")
userCycleId String @map("user_cycle_id")
repeatMode Boolean @default(false) @map("repeat_mode")
preferredCategory String? @map("preferred_category")
lastPlayedContentId String? @map("last_played_content_id")
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @updatedAt @map("updated_at")
@@unique([userId, userCycleId])
@@index([userId])
@@index([userCycleId])
@@map("relaxation_user_settings")
@@schema("private")
}
model RelaxationRecommendationStatus {
id String @id @default(uuid())
userId String @map("user_id")
userCycleId String @map("user_cycle_id")
registrationDate DateTime @map("registration_date")
currentDay Int @default(1) @map("current_day")
isExpired Boolean @default(false) @map("is_expired")
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @updatedAt @map("updated_at")
@@unique([userId, userCycleId])
@@index([userId])
@@index([userCycleId])
@@map("relaxation_recommendation_statuses")
@@schema("private")
}
model RelaxationDownloadStatus {
id String @id @default(uuid())
userId String @map("user_id")
userCycleId String @map("user_cycle_id")
contentId String @map("content_id")
downloadProgress Float @default(0) @map("download_progress") // 0-100
status DownloadStatusType @default(PENDING)
cacheLocation String? @map("cache_location")
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @updatedAt @map("updated_at")
// 관계
content RelaxationContent @relation(fields: [contentId], references: [id], onDelete: Cascade)
@@unique([userId, userCycleId, contentId])
@@index([userId, userCycleId])
@@index([contentId])
@@index([status])
@@index([userId])
@@index([userCycleId])
@@map("relaxation_download_statuses")
@@schema("private")
}
8. 변경 이력
| 버전 | 날짜 | 작성자 | 변경 내용 |
|---|---|---|---|
| 0.1.0 | 2025-06-10 | bok@weltcorp.com | 최초 작성 |
| 0.1.1 | 2025-06-10 | bok@weltcorp.com | 콘텐츠 목록 클라이언트 캐싱을 위한 버전 관리 엔티티, 서비스, 이벤트 추가 |
| 0.1.2 | 2025-06-10 | bok@weltcorp.com | 콘텐츠 버전 관리를 ContentBundle 방식으로 변경하여 QuestionnaireBundle과 일관성 확보 |
| 0.1.3 | 2025-06-10 | bok@weltcorp.com | ContentBundle을 RelaxationContentBundle로 명확화 |
| 0.1.4 | 2025-06-10 | bok@weltcorp.com | RelaxationContent에 thumbnailImageUrl 추가, backgroundImageUrl을 backgroundVideoUrl로 변경 |
| 0.1.5 | 2025-06-10 | bok@weltcorp.com | Category 엔티티에 heroImageUrl 필드 추가 |
| 0.1.6 | 2025-06-12 | bok@weltcorp.com | RelaxationContent에 lottieFileUrl 필드 추가, Category에서 heroImageUrl 필드 제거, DownloadStatusType을 public 스키마로 이동 |
| 0.1.7 | 2025-06-12 | bok@weltcorp.com | Category를 RelaxationCategory로, CategoryTranslation을 RelaxationCategoryTranslation으로 명확화하여 모델명 충돌 방지 |
| 0.1.8 | 2025-06-12 | bok@weltcorp.com | 모든 일반적인 모델명을 Relaxation 접두사로 명확화 (UsageGuide→RelaxationUsageGuide, PlaybackHistory→RelaxationPlaybackHistory 등) |
| 0.1.9 | 2025-06-12 | bok@weltcorp.com | ContentDetail→RelaxationContentDetail로 변경하고 private 스키마 테이블명에 relaxation_ 접두사 추가하여 다른 도메인과 구분 |
| 0.1.10 | 2025-06-12 | bok@weltcorp.com | RelaxationContent에 level, stress, poses 필드 추가 및 관련 enum 정의 (RelaxationLevel, RelaxationStress, RelaxationPose) |
| 0.1.11 | 2025-01-20 | elizabeth@weltcorp.com | 현재 Prisma 스키마와 동기화: 미디어 URL 필드를 Translation 테이블로 이동, duration→durationSeconds 변경, enum 값 수정, recommendationDayIndex 추가, hash 필드 제거, version 타입 변경 |