Access Code 도메인 모델
데이터 저장 규칙
- 개인정보 관련 데이터는 private 스키마에 저장되어야 합니다.
- 비개인정보 데이터는 access_code 스키마에 저장되어야 합니다.
- 개인정보 포함 여부에 따라 위 규칙을 우선적으로 적용합니다.
- Access Code 도메인에서는 UsageHistory, UserRegistration과 같이 사용자 ID, IP 주소, 기기 정보 등 개인 식별이 가능한 데이터는 모두 개인정보로 취급하여 private 스키마에 저장합니다.
- 사용자와 연결되지 않은 AccessCode, Batch, Issuer, SourceSite 등의 데이터는 access_code 스키마에 저장합니다.
1. 엔티티 관계도(ERD)
2. 엔티티
2.1 AccessCode
interface AccessCode {
id: string;
code: string;
status: CodeStatus;
type: CodeType;
startDate: Date;
expirationDate: Date;
groupId?: string;
planId?: string;
isRevoked: boolean;
verificationAttempts: number;
issueSourceType: IssueSourceType;
metadata?: Record<string, any>;
issuerAccountId: string;
sourceSiteId: string;
batchId?: string;
useTimeMachine: boolean;
virtualTimeStartDate?: number; // 13-digit Unix timestamp (ms)
createdAt: Date;
updatedAt: Date;
}
2.2 Batch
interface Batch {
id: string;
name: string;
codeCount: number;
commonExpirationDate: Date;
issueSourceType: IssueSourceType;
issuerAccountId: string;
sourceSiteId: string;
groupId?: string;
planId?: string;
useTimeMachineForAll: boolean;
commonVirtualTimeStartDate?: number; // 13-digit Unix timestamp (ms)
createdAt: Date;
updatedAt: Date;
}
2.3 UsageHistory
interface UsageHistory {
id: string;
accessCodeId: string;
userId: string;
usedAt: Date;
ipAddress?: string;
userAgent?: string;
timeMachineEnabled: boolean;
virtualTimeStartDate?: number; // 13-digit Unix timestamp (ms)
createdAt: Date;
}
2.4 UserRegistration
interface UserRegistration {
id: string;
userId: string;
accessCodeId: string;
registeredAt: Date;
ipAddress?: string;
userAgent?: string;
timeMachineEnabled: boolean;
virtualTimeStartDate?: number; // 13-digit Unix timestamp (ms)
createdAt: Date;
}
2.5 Issuer
interface Issuer {
id: string;
name: string;
type: IssuerType;
permissions: IssuerPermissions;
createdAt: Date;
updatedAt: Date;
}
2.6 SourceSite
interface SourceSite {
id: string;
name: string;
url: string;
description?: string;
isActive: boolean;
createdAt: Date;
updatedAt: Date;
}
2.7 FormatPolicy
interface FormatPolicy {
id: string;
codeLength: number;
allowedCharacters: string;
caseSensitive: boolean;
createdAt: Date;
updatedAt: Date;
}
2.8 IssueSourceMetadata
interface IssueSourceMetadata {
id: string;
type: IssueSourceType;
metadata: Record<string, any>;
supportsTimeMachine: boolean;
createdAt: Date;
updatedAt: Date;
}
2.9 VirtualTimeSettings
interface VirtualTimeSettings {
id: string;
accessCodeId: string;
useTimeMachine: boolean;
virtualTimeStartDate?: number; // 13-digit Unix timestamp (ms)
createdAt: Date;
updatedAt: Date;
}
2.10 TimeMachineIntegration
interface TimeMachineIntegration {
id: string;
userRegistrationId: string;
virtualTimeId: string;
activatedAt: Date;
deactivatedAt?: Date;
isActive: boolean;
createdAt: Date;
updatedAt: Date;
}
2.11 열거형(Enums)
2.11.1 CodeStatus
enum CodeStatus {
PENDING = 'PENDING', // 미래의 특정일 부터 사용 가능하여 지금은 대기 중인 상태
ACTIVE = 'ACTIVE', // 사용 가능
USED = 'USED', // 사용 중
EXPIRED = 'EXPIRED', // 만료됨
BLOCKED = 'BLOCKED', // 차단됨
REVOKED = 'REVOKED' // 회수됨
}
2.11.2 CodeType
enum CodeType {
STANDARD = 'STANDARD',
SAMPLE = 'SAMPLE',
DEMO = 'DEMO',
DEVELOPMENT = 'DEVELOPMENT'
}
2.11.3 IssuerType
enum IssuerType {
OPERATOR = 'OPERATOR',
SYSTEM = 'SYSTEM',
API = 'API'
}
2.11.4 IssueSourceType
enum IssueSourceType {
CLINIC_DASHBOARD = 'CLINIC_DASHBOARD',
OCR_INVOICE = 'OCR_INVOICE',
INSURANCE = 'INSURANCE',
SYSTEM = 'SYSTEM',
OTHER = 'OTHER'
}
3. 값 객체
3.1 CodeStatus
interface CodeStatus {
status: string;
description: string;
}
3.2 CodeType
interface CodeType {
type: string;
description: string;
externalPrefix: string;
}
3.3 ExpirationTime
interface ExpirationTime {
date: Date;
}
3.4 CodeFormat
interface CodeFormat {
length: number;
charSet: string;
pattern: string;
}
3.5 GroupId
interface GroupId {
value: string;
}
3.6 PlanId
interface PlanId {
value: string;
}
3.7 TreatmentPeriod
interface TreatmentPeriod {
startDate: Date;
endDate: Date;
}
3.7 IssuerPermissions
interface IssuerPermissions {
canGenerateCodes: boolean;
canViewCodes: boolean;
canRevokeCodes: boolean;
canManageBatches: boolean;
}
3.8 IssueSource
interface IssueSource {
type: IssueSourceType;
description: string;
}
3.9 ClinicDashboardMetadata
interface ClinicDashboardMetadata {
clinicId: string;
doctorId: string;
patientId: string;
appointmentId?: string;
visitDate?: Date;
}
3.10 OCRInvoiceMetadata
interface OCRInvoiceMetadata {
invoiceId: string;
hospitalName: string;
treatmentDate: Date;
confidenceScore: number;
invoiceItems?: Record<string, number>;
}
3.11 InsuranceCodeMetadata
interface InsuranceCodeMetadata {
insuranceCompanyId: string;
externalValidationId?: string;
validationResult?: boolean;
validationTimestamp?: Date;
}
4. 집계 (Aggregates)
-
AccessCode Aggregate
- Root: AccessCode
- Entities: UsageHistory
- Value Objects: CodeStatus, CodeType, ExpirationTime, IssueSource
-
Batch Aggregate
- Root: Batch
- Entities: AccessCode
- Value Objects: GroupId, PlanId, IssueSource
-
CodeVerification Aggregate
- Root: AccessCode
- Value Objects: CodeStatus, CodeFormat, ExpirationTime
5. 도메인 서비스
5.1 CodeGenerationService
interface CodeGenerationService {
generateCode(
type: CodeType,
issueSourceType: IssueSourceType,
expirationDate?: Date,
groupId?: string,
planId?: string,
metadata?: Record<string, any>
): Promise<AccessCode>;
generateBatch(
batchSize: number,
type: CodeType,
issueSourceType: IssueSourceType,
expirationDate: Date,
sourceSiteId: string,
groupId?: string,
planId?: string,
metadata?: Record<string, any>
): Promise<Batch>;
formatCode(code: string): string;
}
5.2 CodeManagementService
interface CodeManagementService {
getCodeById(id: string): Promise<AccessCode>;
getCodeByValue(code: string): Promise<AccessCode>;
updateCodeStatus(id: string, status: CodeStatus): Promise<AccessCode>;
revokeCode(id: string, reason: string): Promise<AccessCode>;
extendExpirationDate(id: string, newExpirationDate: Date): Promise<AccessCode>;
processExpiredCodes(): Promise<void>;
}
5.3 CodeVerificationService
interface CodeVerificationService {
verifyCode(code: string, userId: string): Promise<boolean>;
recordVerificationAttempt(code: string, success: boolean): Promise<void>;
blockCodeAfterFailedAttempts(code: string, maxAttempts: number): Promise<void>;
}
5.4 BatchManagementService
interface BatchManagementService {
getBatchById(id: string): Promise<Batch>;
getBatchCodes(batchId: string, filterOptions?: CodeFilterOptions): Promise<AccessCode[]>;
updateBatchExpirationDate(batchId: string, newExpirationDate: Date): Promise<Batch>;
revokeBatch(batchId: string, reason: string): Promise<void>;
}
5.5 UsageHistoryService
interface UsageHistoryService {
recordUsage(accessCodeId: string, userId: string, ipAddress: string, userAgent: string): Promise<UsageHistory>;
getUsageHistoryByCodeId(accessCodeId: string): Promise<UsageHistory[]>;
getUsageStatistics(timeRange: TimeRange): Promise<UsageStatistics>;
}
5.6 SecurityService
interface SecurityService {
monitorFailedAttempts(ipAddress: string): Promise<void>;
preventBruteForceAttacks(ipAddress: string, attemptThreshold: number): Promise<boolean>;
logSecurityEvent(eventType: SecurityEventType, details: Record<string, any>): Promise<void>;
}
5.7 IssueSourceService
interface IssueSourceService {
validateIssueSource(issueSourceType: IssueSourceType, metadata: Record<string, any>): Promise<boolean>;
getIssueSourceMetadata(issueSourceType: IssueSourceType, id: string): Promise<Record<string, any>>;
createIssueSourceMetadata(issueSourceType: IssueSourceType, metadata: Record<string, any>): Promise<IssueSource>;
updateIssueSourceMetadata(id: string, metadata: Record<string, any>): Promise<IssueSource>;
}
5.8 ClinicDashboardIntegrationService
interface ClinicDashboardIntegrationService {
issueAccessCode(clinicId: string, doctorId: string, patientId: string, appointmentId?: string): Promise<AccessCode>;
verifyDoctorProfile(doctorId: string): Promise<boolean>;
getPatientAppointmentHistory(patientId: string): Promise<Record<string, any>[]>;
}
5.9 OCRProcessingService
interface OCRProcessingService {
processInvoiceImage(imageData: Buffer): Promise<OCRInvoiceMetadata>;
validateConfidenceScore(metadata: OCRInvoiceMetadata): Promise<boolean>;
issueAccessCodeFromOCR(metadata: OCRInvoiceMetadata): Promise<AccessCode>;
}
5.10 ExternalValidationService
interface ExternalValidationService {
validateInsuranceCode(code: string, insuranceCompanyId: string): Promise<boolean>;
retryValidation(code: string, maxAttempts: number): Promise<boolean>;
handleValidationFailure(code: string, reason: string): Promise<void>;
logExternalApiCall(apiEndpoint: string, requestData: Record<string, any>, responseData: Record<string, any>): Promise<void>;
}
6. 도메인 이벤트
6.1 코드 관련 이벤트
interface AccessCodeGeneratedEvent {
codeId: string;
codeType: CodeType;
expirationDate: Date;
issuerAccountId: string;
timestamp: Date;
}
interface AccessCodeActivatedEvent {
codeId: string;
activationDate: Date;
timestamp: Date;
}
interface AccessCodeUsedEvent {
codeId: string;
userId: string;
usedAt: Date;
timestamp: Date;
}
interface AccessCodeExpiredEvent {
codeId: string;
expirationDate: Date;
timestamp: Date;
}
interface AccessCodeBlockedEvent {
codeId: string;
reason: string;
timestamp: Date;
}
interface AccessCodeRevokedEvent {
codeId: string;
reason: string;
revokedBy: string;
timestamp: Date;
}
6.2 배치 관련 이벤트
interface BatchCreatedEvent {
batchId: string;
codeCount: number;
expirationDate: Date;
issuerAccountId: string;
timestamp: Date;
}
interface BatchExpirationUpdatedEvent {
batchId: string;
oldExpirationDate: Date;
newExpirationDate: Date;
timestamp: Date;
}
interface BatchRevokedEvent {
batchId: string;
reason: string;
revokedBy: string;
timestamp: Date;
}
6.3 검증 관련 이벤트
interface CodeVerificationSucceededEvent {
codeId: string;
userId: string;
timestamp: Date;
}
interface CodeVerificationFailedEvent {
codeValue: string;
reason: string;
attemptCount: number;
timestamp: Date;
}
interface VerificationAttemptsExceededEvent {
codeId: string;
attemptsCount: number;
timestamp: Date;
}
6.4 사용자 등록 관련 이벤트
interface UserRegisteredWithAccessCodeEvent {
userId: string;
accessCodeId: string;
accessCode: string;
registeredAt: Date;
timestamp: Date;
}
interface AccessCodeLinkedToUserEvent {
accessCodeId: string;
userId: string;
linkType: string;
timestamp: Date;
}
6.5 발급 출처 관련 이벤트
interface AccessCodeIssuedFromClinicDashboardEvent {
codeId: string;
clinicId: string;
doctorId: string;
patientId: string;
appointmentId?: string;
timestamp: Date;
}
interface AccessCodeIssuedFromOCRInvoiceEvent {
codeId: string;
invoiceId: string;
hospitalName: string;
confidenceScore: number;
timestamp: Date;
}
interface InsuranceCodeReceivedEvent {
codeId: string;
insuranceCompanyId: string;
timestamp: Date;
}
interface InsuranceCodeValidatedEvent {
codeId: string;
validationResult: boolean;
timestamp: Date;
}
interface ExternalAPICallFailedEvent {
codeId: string;
apiEndpoint: string;
errorMessage: string;
attemptCount: number;
timestamp: Date;
}
7. 도메인 규칙
AccessCode 도메인의 비즈니스 규칙은 business-rules.md 문서를 참조하세요. 이 문서에는 다음과 같은 비즈니스 규칙 카테고리가 포함되어 있습니다:
- 코드 생성 규칙
- 코드 사용 규칙
- 대량 관리 규칙
- 조회 및 관리 규칙
- 표준 포맷 규칙
- 외부 시스템 연동 규칙
8. 데이터베이스 스키마 (Prisma)
generator client {
provider = "prisma-client-js"
}
datasource db {
provider = "postgresql"
url = env("DATABASE_URL")
schemas = ["access_code", "private"]
}
// 접근 코드 상태 열거형
enum CodeStatus {
PENDING
ACTIVE
USED
EXPIRED
BLOCKED
REVOKED
}
// 접근 코드 타입 열거형
enum CodeType {
STANDARD
SAMPLE
DEMO
DEVELOPMENT
}
// 발급자 타입 열거형
enum IssuerType {
OPERATOR
SYSTEM
API
}
// 발급 출처 타입 열거형
enum IssueSourceType {
CLINIC_DASHBOARD
OCR_INVOICE
INSURANCE
SYSTEM
OTHER
}
// 접근 코드 (비개인 정보)
model AccessCode {
id String @id @default(uuid())
code String @unique
status CodeStatus
type CodeType
startDate DateTime @map("start_date")
expirationDate DateTime @map("expiration_date")
groupId String? @map("group_id")
planId String? @map("plan_id")
isRevoked Boolean @default(false) @map("is_revoked")
verificationAttempts Int @default(0) @map("verification_attempts")
issueSourceType IssueSourceType @map("issue_source_type")
// 관계
issuer Issuer @relation(fields: [issuerAccountId], references: [id])
issuerAccountId String @map("issuer_account_id")
sourceSite SourceSite @relation(fields: [sourceSiteId], references: [id])
sourceSiteId String @map("source_site_id")
batch Batch? @relation(fields: [batchId], references: [id])
batchId String? @map("batch_id")
// TimeMachine 관련 필드
useTimeMachine Boolean @default(false) @map("use_time_machine")
virtualTimeStartDate BigInt? @map("virtual_time_start_date") @db.BigInt // Unix timestamp (ms)
// 메타데이터 및 감사
metadata Json?
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @updatedAt @map("updated_at")
// 자식 관계
usageHistory UsageHistory[]
userRegistrations UserRegistration[]
@@index([code])
@@index([status])
@@index([batchId])
@@index([issueSourceType])
@@map("access_codes")
@@schema("access_code")
}
// 코드 배치 (비개인 정보)
model Batch {
id String @id @default(uuid())
name String
codeCount Int @map("code_count")
commonExpirationDate DateTime @map("common_expiration_date")
issueSourceType IssueSourceType @map("issue_source_type")
groupId String? @map("group_id")
planId String? @map("plan_id")
// 관계
issuer Issuer @relation(fields: [issuerAccountId], references: [id])
issuerAccountId String @map("issuer_account_id")
sourceSite SourceSite @relation(fields: [sourceSiteId], references: [id])
sourceSiteId String @map("source_site_id")
// TimeMachine 관련 필드 (배치 레벨)
useTimeMachineForAll Boolean @default(false) @map("use_time_machine_for_all")
commonVirtualTimeStartDate BigInt? @map("common_virtual_time_start_date") @db.BigInt // Unix timestamp (ms)
// 감사
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @updatedAt @map("updated_at")
// 자식 관계
accessCodes AccessCode[]
@@index([issuerAccountId])
@@index([issueSourceType])
@@map("batches")
@@schema("access_code")
}
// 사용 이력 (개인 정보 포함)
model UsageHistory {
id String @id @default(uuid())
// 관계 - 크로스 스키마 참조
accessCode AccessCode @relation(fields: [accessCodeId], references: [id])
accessCodeId String @map("access_code_id")
// 필드 - 개인 정보 포함
userId String @map("user_id")
usedAt DateTime @map("used_at")
ipAddress String? @map("ip_address")
userAgent String? @map("user_agent")
// TimeMachine 관련 필드
timeMachineEnabled Boolean @default(false) @map("time_machine_enabled")
virtualTimeStartDate BigInt? @map("virtual_time_start_date") @db.BigInt // Unix timestamp (ms)
// 감사
createdAt DateTime @default(now()) @map("created_at")
@@index([accessCodeId])
@@index([usedAt])
@@map("usage_history")
@@schema("private")
}
// 발급자 (비개인 정보)
model Issuer {
id String @id @default(uuid())
name String
type IssuerType
permissions Json // 권한 관련 JSON 객체
// 감사
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @updatedAt @map("updated_at")
// 자식 관계
accessCodes AccessCode[]
batches Batch[]
@@map("issuers")
@@schema("access_code")
}
// 출처 사이트 (비개인 정보)
model SourceSite {
id String @id @default(uuid())
name String
url String
description String?
isActive Boolean @default(true) @map("is_active")
// 감사
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @updatedAt @map("updated_at")
// 자식 관계
accessCodes AccessCode[]
batches Batch[]
@@map("source_sites")
@@schema("access_code")
}
// 코드 형식 정책 (비개인 정보)
model FormatPolicy {
id String @id @default(uuid())
codeLength Int @default(16) @map("code_length")
allowedCharacters String @map("allowed_characters")
caseSensitive Boolean @default(false) @map("case_sensitive")
// 감사
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @updatedAt @map("updated_at")
@@map("format_policies")
@@schema("access_code")
}
// 발급 출처 메타데이터 모델 추가
model IssueSourceMetadata {
id String @id @default(uuid())
type IssueSourceType
metadata Json
// 감사
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @updatedAt @map("updated_at")
@@index([type])
@@map("issue_source_metadata")
@@schema("access_code")
}
// 사용자 등록 정보 (개인 정보 포함)
model UserRegistration {
id String @id @default(uuid())
// 관계 - 크로스 스키마 참조
accessCode AccessCode @relation(fields: [accessCodeId], references: [id])
accessCodeId String @map("access_code_id")
// 필드 - 개인 정보 포함
userId String @unique @map("user_id")
registeredAt DateTime @map("registered_at")
ipAddress String? @map("ip_address")
userAgent String? @map("user_agent")
// TimeMachine 관련 필드
timeMachineEnabled Boolean @default(false) @map("time_machine_enabled")
virtualTimeStartDate BigInt? @map("virtual_time_start_date") @db.BigInt // Unix timestamp (ms)
// 감사
createdAt DateTime @default(now()) @map("created_at")
@@index([accessCodeId])
@@map("user_registrations")
@@schema("private")
}
// 가상 시간 설정 (AccessCode와 1:1) - 이 모델이 필요 없을 수도 있음 (AccessCode에 직접 포함)
model VirtualTimeSettings {
id String @id @default(uuid())
// 관계
accessCode AccessCode @relation(fields: [accessCodeId], references: [id])
accessCodeId String @unique @map("access_code_id") // 1:1 관계
useTimeMachine Boolean @default(false) @map("use_time_machine")
virtualTimeStartDate BigInt? @map("virtual_time_start_date") @db.BigInt // Unix timestamp (ms)
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @updatedAt @map("updated_at")
@@map("virtual_time_settings")
@@schema("access_code")
}
9. 변경 이력
| 버전 | 날짜 | 작성자 | 변경 내용 |
|---|---|---|---|
| 0.1.0 | 2025-03-30 | bok@weltcorp.com | 최초 작성 |