Skip to content

Feature/#12 - SwiftData 기반 Secret CRUD 및 보안 계층 구성 - #16

Merged
dlguszoo merged 45 commits into
developfrom
feature/#12/Secret_ORM
Jun 30, 2026
Merged

Feature/#12 - SwiftData 기반 Secret CRUD 및 보안 계층 구성#16
dlguszoo merged 45 commits into
developfrom
feature/#12/Secret_ORM

Conversation

@dlguszoo

@dlguszoo dlguszoo commented May 23, 2026

Copy link
Copy Markdown
Contributor

✨ What’s this PR?

📌 관련 이슈 (Related Issue)


📁새로 생긴 폴더링과 파일

Domain

Projects/DVDomain/Sources
├── Entity
│   ├── Secret.swift
│   ├── SecretMetadata.swift
│   └── SecretPayload.swift
├── Repository
│   ├── Error
│   │   └── SecretRepositoryError.swift
│   ├── Interface
│   │   └── SecretRepository.swift
│   ├── SecretPatch.swift
│   └── SecretQuery.swift
├── SecretContent
│   ├── Metadata
│   │   ├── Interface
│   │   │   └── SecretMetadataContent.swift
│   │   ├── APIKeyMetadata.swift
│   │   ├── DatabaseMetadata.swift
│   │   ├── LicenseKeyMetadata.swift
│   │   ├── OAuthClientMetadata.swift
│   │   ├── SSHKeyMetadata.swift
│   │   ├── SSLCertMetadata.swift
│   │   └── ServiceAccountMetadata.swift
│   └── Payload
│       ├── Interface
│       │   └── SecretPayloadData.swift
│       ├── APIKeyPayload.swift
│       ├── CustomPayload.swift
│       ├── DatabasePayload.swift
│       ├── EnvSetPayload.swift
│       ├── LicenseKeyPayload.swift
│       ├── OAuthClientPayload.swift
│       ├── SSHKeyPayload.swift
│       ├── SSLCertPayload.swift
│       └── ServiceAccountPayload.swift
├── Service
│   ├── Error
│   │   ├── SecretCryptoError.swift
│   │   └── UserAuthenticationError.swift
│   └── Interface
│       ├── SecretCryptoService.swift
│       └── UserAuthenticationService.swift
└── UseCase
   ├── Draft
   │   └── SecretDraft.swift
   ├── Error
   │   └── SecretUseCaseError.swift
   ├── Impl
   │   └── Secret
   │       ├── CreateSecretUseCaseImpl.swift
   │       ├── DeleteSecretUseCaseImpl.swift
   │       ├── FetchSecretUseCaseImpl.swift
   │       ├── PatchSecretUseCaseImpl.swift
   │       └── SecretUseCaseHelper.swift
   └── Interface
       └── Secret
           ├── CreateSecretUseCase.swift
           ├── DeleteSecretUseCase.swift
           ├── FetchSecretUseCase.swift
           └── PatchSecretUseCase.swift

Data

Projects/DVData/Sources
├── RepositoryImpl
│   └── Secret
│       ├── InMemorySecretQueryFilter.swift
│       ├── SecretFetchDescriptorBuilder.swift
│       └── SecretRepositoryImpl.swift
├── ServiceImpl
│   ├── Authentication
│   │   └── LocalUserAuthenticationServiceImpl.swift
│   └── Security
│       ├── SecretCryptoServiceImpl.swift
│       ├── JSONCoder
│       │   ├── SecretMetadataJSONCoder.swift
│       │   └── SecretPayloadJSONCoder.swift
│       └── Keychain
│           └── KeychainKeyStore.swift
└── Storage
   └── Local
       ├── LocalStorage.swift
       └── Models

Presentation

Projects/DVPresentation/Sources
└── SecretUseCaseDemoView.swift

App

Projects/Devault/Sources
└── ContentView.swift

🧶 주요 변경 내용 (Summary)

Task 1. Tuist 모듈 의존성 정리

  • Presentation -> Domain/Core
  • Domain -> Core
  • Data -> Domain/Core
  • Design -> Core
  • Core -> 의존 없음
  • Clean Architecture + TCA 기준으로 모듈 책임을 정리

Task 2. Secret Domain Entity 정의

  • Secret, SecretPayload, SecretMetadata Domain Entity 추가
  • payload는 필수, metadata는 optional 구조로 정리
  • SwiftData 모델과 Domain 모델의 역할 분리

Task 3. Repository Interface 정의

  • SecretRepository 프로토콜 추가
  • SecretQuery, SecretPatch, SecretRepositoryError 정의
  • Repository는 순수 CRUD 중심, 비즈니스 정책은 UseCase로 분리

Task 4. Secret Content 모델 정의

  • payload/metadata content protocol 추가
  • API Key, OAuth, Service Account, Database, SSH, SSL Cert, Env Set, License, Custom payload 구조 정의
  • schemaVersion 기반으로 payload/metadata 구조 변경 대응 가능하도록 설계

Task 5. Secret UseCase 구성

  • Create / Fetch / Patch / Delete UseCase 분리
  • SecretDraft 도입
  • reveal, restore, soft delete, permanent delete 등 작업 단위 정리
  • UseCase error와 Repository error 분리

Task 6. SwiftData RepositoryImpl 구현

  • SecretRepositoryImpl을 @Modelactor 기반 actor로 구현
  • SwiftData model과 Domain entity mapping 처리
  • SecretFetchDescriptorBuilder로 DB fetch 조건 구성
  • InMemorySecretQueryFilter로 searchText 후처리

Task 7. 암호화/Keychain/JSON 처리 구현

  • SecretCryptoService Domain interface 추가
  • SecretCryptoServiceImpl Data 구현체 추가
  • AES-GCM 기반 payload 암복호화
  • metadata JSON encode/decode 처리
  • Keychain 기반 master key 저장/조회 처리

Task 8. 사용자 인증 책임 분리

  • UserAuthenticationService Domain interface 추가
  • LocalUserAuthenticationServiceImpl Data 구현체 추가
  • KeychainKeyStore는 key 저장/조회만 담당하도록 정리
  • reveal 전 LocalAuthentication 인증을 UseCase에서 수행
  • SecretCryptoError와 UserAuthenticationError 분리

Task 9. 임시 Demo View 추가

  • Presentation에 Secret 생성/조회/reveal 확인용 임시 View 추가
  • App composition root에서 Repository/Crypto/Auth 구현체 주입
  • 실제 앱 실행으로 create / refresh / reveal 흐름 확인 가능

📸 스크린샷 (Optional)

2026-05-23.11.38.10.mov

🧪 테스트 / 검증 내역

  • tuist generate 성공
  • 앱 실행 후 Demo View에서 Secret 생성 / 조회 / payload reveal 흐름 수동 확인

💬 기타 공유 사항

  • Entity 네이밍 고민 -> SecretEntity와 같은 접미사를 붙일 수도 있음.

  • UseCase 단위 고민 -> 지금 구현한 방식은 “사용자 의도 하나의 작업 단위”로 보는 관점으로.

  • 앞으로 localAuth를 쓰는 곳은 많지만 정해져 있을 테니, authenticate의 reason 파라미터를 String이 아닌 AuthenticationPurpose enum으로 정의해서 받아야 할 것

  • Metadata, payload의 각 content들은 notion DB설계 페이지에서 미리 정의하고 구현한 내용입니다.

  • Service 관련

    • 현재는 UseCase에서 LocalAuthentication을 먼저 수행한 뒤 Keychain key를 조회하는 구조
    • kSecAttrAccessibleWhenUnlockedThisDeviceOnly는 LocalAuth와 별개로 “현재 기기 + 잠금 해제 상태”에서만 key 접근 가능하게 하는 Keychain 접근성 옵션

🙇🏻‍♀️ 리뷰 가이드 (선택)

  • secret CRUD라 repository(기본crud) / usecase(crud 활용한 softDelete, reveal 등) / 보안계층 까지 얽혀있는 것들이 많아 task와 코드 줄/파일 수가 많아졌습니다.. 적당히 이슈 분배하지 못한 점 죄송하고 양해 부탁드립니다.🥲

Summary by CodeRabbit

  • New Features
    • 시크릿 저장·관리(생성/조회/부분 수정/일시·복원·영구 삭제)와 페이로드·메타데이터 지원
    • 로컬 암호화 및 키체인 기반 대칭키로 안전한 저장
    • 로컬 사용자 인증 후에만 페이로드 노출
    • 대/소문자 무시 키워드 검색과 타입·서비스·환경 필터, 컬렉션 범위 및 정렬
    • API 키·DB·OAuth·SSH·SSL 등 다양한 메타데이터/페이로드 타입 지원
    • 시크릿 데모 UI 제공 및 로컬 저장소 이용 불가 시 안내 화면 표시

@dlguszoo dlguszoo linked an issue May 23, 2026 that may be closed by this pull request
9 tasks
@coderabbitai

coderabbitai Bot commented May 23, 2026

Copy link
Copy Markdown

Review Change Stack

Caution

Review failed

The pull request is closed.

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: fe5c1772-4783-4531-998d-8def29c6f7af

📥 Commits

Reviewing files that changed from the base of the PR and between 6217636 and 3f90204.

📒 Files selected for processing (63)
  • Projects/DVData/Project.swift
  • Projects/DVData/Sources/RepositoryImpl/Secret/InMemorySecretQueryFilter.swift
  • Projects/DVData/Sources/RepositoryImpl/Secret/SecretFetchDescriptorBuilder.swift
  • Projects/DVData/Sources/RepositoryImpl/Secret/SecretRepositoryImpl.swift
  • Projects/DVData/Sources/ServiceImpl/Authentication/LocalUserAuthenticationServiceImpl.swift
  • Projects/DVData/Sources/ServiceImpl/Security/JSONCoder/SecretMetadataJSONCoder.swift
  • Projects/DVData/Sources/ServiceImpl/Security/JSONCoder/SecretPayloadJSONCoder.swift
  • Projects/DVData/Sources/ServiceImpl/Security/Keychain/KeychainKeyStore.swift
  • Projects/DVData/Sources/ServiceImpl/Security/SecretCryptoServiceImpl.swift
  • Projects/DVData/Sources/Storage/Local/LocalStorage.swift
  • Projects/DVData/Sources/Storage/Local/Models/Secret.swift
  • Projects/DVData/Sources/Storage/Local/Models/SecretMetadata.swift
  • Projects/DVData/Sources/Storage/Local/Models/SecretPayload.swift
  • Projects/DVDesign/Project.swift
  • Projects/DVDomain/Project.swift
  • Projects/DVDomain/Sources/Entity/Secret.swift
  • Projects/DVDomain/Sources/Entity/SecretMetadata.swift
  • Projects/DVDomain/Sources/Entity/SecretPayload.swift
  • Projects/DVDomain/Sources/Repository/Error/SecretRepositoryError.swift
  • Projects/DVDomain/Sources/Repository/Interface/SecretRepository.swift
  • Projects/DVDomain/Sources/Repository/SecretPatch.swift
  • Projects/DVDomain/Sources/Repository/SecretQuery.swift
  • Projects/DVDomain/Sources/SecretContent/Metadata/APIKeyMetadata.swift
  • Projects/DVDomain/Sources/SecretContent/Metadata/DatabaseMetadata.swift
  • Projects/DVDomain/Sources/SecretContent/Metadata/Interface/SecretMetadataContent.swift
  • Projects/DVDomain/Sources/SecretContent/Metadata/LicenseKeyMetadata.swift
  • Projects/DVDomain/Sources/SecretContent/Metadata/OAuthClientMetadata.swift
  • Projects/DVDomain/Sources/SecretContent/Metadata/SSHKeyMetadata.swift
  • Projects/DVDomain/Sources/SecretContent/Metadata/SSLCertMetadata.swift
  • Projects/DVDomain/Sources/SecretContent/Metadata/ServiceAccountMetadata.swift
  • Projects/DVDomain/Sources/SecretContent/Payload/APIKeyPayload.swift
  • Projects/DVDomain/Sources/SecretContent/Payload/CustomPayload.swift
  • Projects/DVDomain/Sources/SecretContent/Payload/DatabasePayload.swift
  • Projects/DVDomain/Sources/SecretContent/Payload/EnvSetPayload.swift
  • Projects/DVDomain/Sources/SecretContent/Payload/Interface/SecretPayloadData.swift
  • Projects/DVDomain/Sources/SecretContent/Payload/LicenseKeyPayload.swift
  • Projects/DVDomain/Sources/SecretContent/Payload/OAuthClientPayload.swift
  • Projects/DVDomain/Sources/SecretContent/Payload/SSHKeyPayload.swift
  • Projects/DVDomain/Sources/SecretContent/Payload/SSLCertPayload.swift
  • Projects/DVDomain/Sources/SecretContent/Payload/ServiceAccountPayload.swift
  • Projects/DVDomain/Sources/Service/Error/SecretCryptoError.swift
  • Projects/DVDomain/Sources/Service/Error/UserAuthenticationError.swift
  • Projects/DVDomain/Sources/Service/Interface/SecretCryptoService.swift
  • Projects/DVDomain/Sources/Service/Interface/UserAuthenticationService.swift
  • Projects/DVDomain/Sources/UseCase/Draft/SecretDraft.swift
  • Projects/DVDomain/Sources/UseCase/Error/SecretUseCaseError.swift
  • Projects/DVDomain/Sources/UseCase/Impl/Secret/CreateSecretUseCaseImpl.swift
  • Projects/DVDomain/Sources/UseCase/Impl/Secret/DeleteSecretUseCaseImpl.swift
  • Projects/DVDomain/Sources/UseCase/Impl/Secret/FetchSecretUseCaseImpl.swift
  • Projects/DVDomain/Sources/UseCase/Impl/Secret/PatchSecretUseCaseImpl.swift
  • Projects/DVDomain/Sources/UseCase/Impl/Secret/SecretUseCaseHelper.swift
  • Projects/DVDomain/Sources/UseCase/Interface/Secret/CreateSecretUseCase.swift
  • Projects/DVDomain/Sources/UseCase/Interface/Secret/DeleteSecretUseCase.swift
  • Projects/DVDomain/Sources/UseCase/Interface/Secret/FetchSecretUseCase.swift
  • Projects/DVDomain/Sources/UseCase/Interface/Secret/PatchSecretUseCase.swift
  • Projects/DVDomain/Tests/.gitkeep
  • Projects/DVDomain/Tests/ExampleTest.swift
  • Projects/DVPresentation/Project.swift
  • Projects/DVPresentation/Sources/SecretUseCaseDemoView.swift
  • Projects/Devault/Project.swift
  • Projects/Devault/Sources/ContentView.swift
  • Projects/Devault/Sources/DevaultApp.swift
  • Projects/Devault/Sources/StorageUnavailableView.swift

Walkthrough

Secret 도메인 계약, payload/metadata 타입, SwiftData·Keychain 저장 계층, CRUD 저장소, 유스케이스, 데모 UI, 앱 진입점이 추가되었습니다.

Changes

Secret 데이터 생명주기 관리

Layer / File(s) Summary
도메인 엔티티 및 계약 정의
Projects/DVDomain/Sources/...
Secret, SecretQuery, SecretPatch, SecretDraft, 관련 오류, 저장소·암호화·인증·유스케이스 프로토콜이 추가되었습니다.
페이로드 및 메타데이터 타입 정의
Projects/DVDomain/Sources/SecretContent/Payload/*, Projects/DVDomain/Sources/SecretContent/Metadata/*
SecretPayloadData와 SecretMetadataContent 계약이 추가되고, 여러 Secret payload 및 metadata 구현체가 schemaVersion과 초기화 로직과 함께 정의되었습니다.
로컬 저장소 및 암호화 기반
Projects/DVData/Project.swift, Projects/DVData/Sources/...
LocalStorage, JSON coder, KeychainKeyStore, SecretCryptoServiceImpl, SwiftData 모델 도메인 변환, DVData 타겟 구성이 추가되었습니다.
SwiftData 저장소 구현 (CRUD)
Projects/DVData/Sources/RepositoryImpl/Secret/*
SecretFetchDescriptorBuilder, InMemorySecretQueryFilter, SecretRepositoryImpl이 query predicate, sort, searchText 후처리, create/fetch/patch/delete CRUD와 payload/metadata 반영 로직을 구현했습니다.
비즈니스 유스케이스
Projects/DVDomain/Sources/UseCase/Impl/Secret/*
Create, Fetch, Patch, Delete 유스케이스 구현체와 공통 검증/updatedAt 보정 헬퍼가 추가되었습니다.
프레젠테이션 및 앱 통합
Projects/DVPresentation/Project.swift, Projects/DVPresentation/Sources/SecretUseCaseDemoView.swift, Projects/Devault/Sources/*
SecretUseCaseDemoView가 추가되고, ContentView와 DevaultApp가 로컬 저장소·암호화·인증 서비스를 조합해 데모 화면을 주입하도록 변경되었습니다. StorageUnavailableView도 추가되었습니다.

Sequence Diagram(s)

sequenceDiagram
  participant DevaultApp
  participant LocalStorage
  participant ContentView
  participant SecretUseCaseDemoView
  participant SecretRepositoryImpl
  participant SecretCryptoServiceImpl
  participant LocalUserAuthenticationServiceImpl

  DevaultApp->>LocalStorage: makeDefault()
  DevaultApp-->>ContentView: storage
  ContentView->>SecretUseCaseDemoView: inject use cases
  SecretUseCaseDemoView->>SecretRepositoryImpl: fetch / create / patch / delete
  SecretUseCaseDemoView->>SecretCryptoServiceImpl: encryptPayload / decryptPayload
  SecretUseCaseDemoView->>LocalUserAuthenticationServiceImpl: authenticate(reason)
Loading

Possibly related PRs

  • DevaultProject/Devault-macOS#3: 같은 DVData 저장 모델 영역에서 AppSchema와 SwiftData 로컬 모델 정의를 확장한 PR이라, 이번 Secret 모델/매핑 변경과 직접 연결됩니다.

Suggested labels

🎨 Design

Suggested reviewers

  • yeseonglee
  • doyeonk429

Estimated code review effort

🎯 5 (Critical) | ⏱️ ~90+ minutes

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Out of Scope Changes check ⚠️ Warning CRUD 범위를 넘는 보안 계층, 유스케이스, 프레젠테이션 데모, 앱 조립 코드가 함께 추가되었습니다. 이슈 #12 범위는 CRUD/매핑/ModelContainer로 제한하고, 보안·유스케이스·UI 조립은 별도 PR로 분리하세요.
Docstring Coverage ⚠️ Warning Docstring coverage is 44.44% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed 제목이 SwiftData 기반 Secret CRUD와 보안 계층 추가라는 변경의 핵심을 정확히 요약합니다.
Linked Issues check ✅ Passed #12의 CRUD 프로토콜, 엔티티, SwiftData 구현, 매핑, ModelContainer 구성이 모두 포함되어 있습니다.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feature/#12/Secret_ORM

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@dlguszoo
dlguszoo requested a review from doyeonk429 May 23, 2026 14:46
@dlguszoo dlguszoo self-assigned this May 23, 2026
@dlguszoo dlguszoo added the ✨ Feature 새로운 기능 개발 label May 23, 2026

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 5

🧹 Nitpick comments (9)
Projects/DVDomain/Sources/Entity/Secret.swift (1)

14-14: 🏗️ Heavy lift

Bool 프로퍼티 네이밍을 isLiked로 통일하세요.

Line [14]의 liked는 Bool 네이밍 규칙과 어긋납니다. isLiked로 바꾸면 도메인 의도가 더 명확하고 호출부 가독성이 좋아집니다(동일 필드가 있는 Draft/Patch도 함께 정리 권장).

As per coding guidelines, 'Bool 프로퍼티에 is, has, can 등의 접두사가 있는지 확인하세요.'

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@Projects/DVDomain/Sources/Entity/Secret.swift` at line 14, Secret 구조체의 Bool
프로퍼티명 `liked`는 네이밍 규칙에 어긋나므로 `isLiked`로 리네임하세요; 변경해야 할 곳은 Entity/Secret의
선언(`public var liked` → `public var isLiked`), 관련 Draft/Patch 타입들(동일 필드명),
초기화/복사 메서드, Codable CodingKeys, DB/mapping 키, 사용처(모든 참조와 테스트), 그리고
Equatable/Hashable 구현부입니다; 리네임 시 기존 외부 API/직렬화 포맷이 변경되는 경우 기존 키와 매핑을 유지하거나
마이그레이션 처리(예: CodingKeys에 이전 이름 포함)하도록 함께 조정하세요.
Projects/DVDomain/Sources/Repository/SecretQuery.swift (1)

31-47: ⚡ Quick win

public extension 대신 선언별 접근 제어로 바꾸세요.

Line [31]의 public extension SecretQuery는 현재 저장소 규칙과 충돌합니다. Collection, Sortpublic enum으로 직접 선언하는 형태로 바꾸는 게 맞습니다.

변경 예시
-public extension SecretQuery {
-    enum Collection: Equatable, Sendable {
+extension SecretQuery {
+    public enum Collection: Equatable, Sendable {
         case all
         case liked
         case expired(referenceDate: Date)
         case deleted
         case project(id: UUID)
     }
 
-    enum Sort: Equatable, Sendable {
+    public enum Sort: Equatable, Sendable {
         case recentlyAdded
         case oldestFirst
         case expiringSoon
         case nameAscending
         case nameDescending
     }
 }

As per coding guidelines, 'public extension 패턴 대신 각 선언에 직접 접근 제어가 명시되어 있는지 확인하세요.'

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@Projects/DVDomain/Sources/Repository/SecretQuery.swift` around lines 31 - 47,
Replace the `public extension SecretQuery` block by declaring the enums with
explicit access control instead of using an extension: remove the extension
wrapper and make two top-level declarations `public enum Collection: Equatable,
Sendable { ... }` and `public enum Sort: Equatable, Sendable { ... }` (keeping
the same cases), then update any uses of `SecretQuery.Collection` /
`SecretQuery.Sort` to the new types or adjust placement so the enums remain
nested but declared directly inside the `SecretQuery` type with `public` access
on each enum declaration rather than using `public extension`.
Projects/DVDomain/Sources/UseCase/Error/SecretUseCaseError.swift (1)

12-12: ⚡ Quick win

알 수 없는 오류의 원인 정보가 소실됩니다

현재 .unexpected로만 매핑되어 원인 추적이 어렵습니다. 최소한 디버깅 가능한 문자열 컨텍스트는 보존하는 편이 좋습니다.

개선 예시 diff
 public enum SecretUseCaseError: Error, Equatable, Sendable {
@@
-    case unexpected
+    case unexpected(message: String)
 }
@@
-        return .unexpected
+        return .unexpected(message: String(describing: error))
     }
 }

Also applies to: 36-36

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@Projects/DVDomain/Sources/UseCase/Error/SecretUseCaseError.swift` at line 12,
The .unexpected enum variant currently loses cause info; change it to carry
context (e.g. change `case unexpected` to `case unexpected(String)` or `case
unexpected(Error)` in SecretUseCaseError, update all places that construct
`.unexpected` (and the other identical occurrence referenced) to pass a
descriptive string such as `String(describing: error)` or
`error.localizedDescription`, and update any switch/throw sites and tests to
handle the associated value so the original error/debug context is preserved for
logging and debugging.
Projects/DVDomain/Sources/SecretContent/Metadata/DatabaseMetadata.swift (1)

10-23: ⚡ Quick win

Bool 프로퍼티 네이밍 접두사를 규칙에 맞춰주세요.

Line 10의 sslRequired는 Bool 타입인데 is/has/can 접두사 규칙과 맞지 않습니다. isSSLRequired로 통일하면 도메인 모델 전반의 가독성과 일관성이 좋아집니다.

변경 제안
 public struct DatabaseMetadata: SecretMetadataContent, Equatable {
     public static let schemaVersion = 1

     public var host: String?
     public var port: Int?
     public var databaseName: String?
     public var username: String?
-    public var sslRequired: Bool?
+    public var isSSLRequired: Bool?

     public init(
         host: String? = nil,
         port: Int? = nil,
         databaseName: String? = nil,
         username: String? = nil,
-        sslRequired: Bool? = nil
+        isSSLRequired: Bool? = nil
     ) {
         self.host = host
         self.port = port
         self.databaseName = databaseName
         self.username = username
-        self.sslRequired = sslRequired
+        self.isSSLRequired = isSSLRequired
     }
 }

As per coding guidelines, "Bool 프로퍼티에 is, has, can 등의 접두사가 있는지 확인하세요."

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@Projects/DVDomain/Sources/SecretContent/Metadata/DatabaseMetadata.swift`
around lines 10 - 23, Rename the Bool property sslRequired to isSSLRequired and
update its initializer parameter and assignment in the DatabaseMetadata init so
the stored property and constructor parameter match (replace occurrences of
sslRequired with isSSLRequired in the property declaration, initializer
parameter list, and self assignment). Also search for and update all
references/usages of DatabaseMetadata.sslRequired to
DatabaseMetadata.isSSLRequired (including any decoding/encoding keys or Codable
conformance if present) to keep the API and serialization consistent.
Projects/DVData/Sources/RepositoryImpl/Secret/SecretFetchDescriptorBuilder.swift (1)

42-50: 💤 Low value

강제 언래핑(!) 사용 - #Predicate 제약 확인 필요

Line 46에서 secret.expiresAt!를 사용하고 있습니다. 직전 조건(secret.expiresAt != nil)으로 nil이 아님을 보장하지만, #Predicate 매크로 내부에서는 if let/guard let 패턴을 사용할 수 없는 제약이 있습니다.

SwiftData의 flatMap 기반 옵셔널 비교가 가능한지 확인해 주세요. 불가능하다면 현재 패턴이 불가피하므로 주석으로 의도를 명시하는 것을 권장합니다.

💡 주석 추가 제안
         case let .expired(referenceDate):
+            // Note: `#Predicate` 내부에서는 optional binding 불가. nil 체크 후 강제 언래핑 사용.
             return `#Predicate`<SwiftDataModel.Secret> { secret in
                 secret.deletedAt == nil &&
                 secret.expiresAt != nil &&
                 secret.expiresAt! < referenceDate &&
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@Projects/DVData/Sources/RepositoryImpl/Secret/SecretFetchDescriptorBuilder.swift`
around lines 42 - 50, The force-unwrapping of secret.expiresAt inside the
`#Predicate` in the .expired(referenceDate) case is unsafe given macro
constraints; replace secret.expiresAt! < referenceDate with an optional-safe
expression such as secret.expiresAt.map { $0 < referenceDate } == true (or, if
the macro allows, (secret.expiresAt ?? Date.distantPast) < referenceDate) so no
force unwrap is used; if neither optional-mapping approach is supported by
`#Predicate`, retain the current logic but add a concise comment in
SecretFetchDescriptorBuilder near the .expired(referenceDate) case and the
`#Predicate`<SwiftDataModel.Secret> explaining why force-unwrapping is safe
(because of the preceding secret.expiresAt != nil check) and noting the macro
limitation.
Projects/DVDomain/Sources/UseCase/Impl/Secret/CreateSecretUseCaseImpl.swift (1)

23-76: ⚡ Quick win

중복된 Secret 조립 로직을 한 곳으로 모아 주세요.

execute 오버로드가 동일한 필드 매핑을 반복하고 있어, 필드 추가/수정 시 한쪽만 누락될 위험이 있습니다. Secret 생성 부분을 private 헬퍼로 추출해 중복을 제거하는 편이 안전합니다.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@Projects/DVDomain/Sources/UseCase/Impl/Secret/CreateSecretUseCaseImpl.swift`
around lines 23 - 76, Extract the duplicated Secret construction in both execute
overloads of CreateSecretUseCaseImpl into a private helper (e.g.,
makeSecret(from:draft:payload:metadataOpt:now:)) that accepts the SecretDraft,
current date (from dateProvider()), encrypted payload (from
cryptoService.encryptPayload), optional encoded metadata (from
cryptoService.encodeMetadata), and uses idGenerator(),
draft.name/secretType/subType/service/environment/expiresAt/memo/liked to set
fields plus createdAt/updatedAt/payload and metadata when present; update both
public execute methods to call SecretUseCaseHelper.validateDraft, compute
now/encryptedPayload/(encodedMetadata), call the new helper to get the Secret,
then return try await repository.create(secret), preserving existing error
handling (SecretUseCaseError.map).
Projects/DVPresentation/Sources/SecretUseCaseDemoView.swift (2)

54-55: ⚡ Quick win

이벤트 핸들러 함수명은 과거형으로 통일해주세요.

버튼 액션 핸들러(createDemoSecret, revealSelectedPayload, fetchAllSecrets)를 didTap... 형태로 맞추면 규칙 일관성이 좋아집니다.

As per coding guidelines 이벤트 핸들러 함수명이 과거형인지 확인하세요. (didTapButton ✅ / handleButtonTap ❌).

Also applies to: 62-63, 121-122, 171-171, 197-197, 206-206

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@Projects/DVPresentation/Sources/SecretUseCaseDemoView.swift` around lines 54
- 55, Rename the event-handler functions to past-tense `didTap...` form for
consistency: change `createDemoSecret` → `didTapCreateDemoSecret`,
`revealSelectedPayload` → `didTapRevealSelectedPayload`, `fetchAllSecrets` →
`didTapFetchAllSecrets` (and any other handlers noted at the same locations) and
update all call sites (e.g., Task { await createDemoSecret() } to Task { await
didTapCreateDemoSecret() }) plus any references in UI button labels, method
declarations, and tests; ensure async/throws signatures remain unchanged and run
a build to fix any broken imports/usages.

3-4: ⚡ Quick win

임포트 순서를 가이드에 맞춰 정리해주세요.

현재는 내장 프레임워크(SwiftUI)가 뒤에 와 있습니다.

🔧 제안 패치
-import DVDomain
-import SwiftUI
+import SwiftUI
+
+import DVDomain

As per coding guidelines 모듈 임포트가 알파벳 순으로 정렬되어 있는지 확인하세요. (내장 프레임워크 먼저, 빈 줄로 서드파티 구분).

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@Projects/DVPresentation/Sources/SecretUseCaseDemoView.swift` around lines 3 -
4, The import statements are out of order: move the system framework import
SwiftUI to be first, then add a blank line and place the module import DVDomain
after it so imports follow the guideline (system frameworks first, blank line,
third-party/local modules) — update the import block containing SwiftUI and
DVDomain accordingly.
Projects/Devault/Sources/ContentView.swift (1)

1-4: ⚡ Quick win

임포트 순서를 내장 프레임워크 우선으로 정리해주세요.

🔧 제안 패치
-import DVData
-import DVDomain
-import DVPresentation
 import SwiftUI
+
+import DVData
+import DVDomain
+import DVPresentation

As per coding guidelines 모듈 임포트가 알파벳 순으로 정렬되어 있는지 확인하세요. (내장 프레임워크 먼저, 빈 줄로 서드파티 구분).

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@Projects/Devault/Sources/ContentView.swift` around lines 1 - 4, Reorder the
import statements so built-in frameworks come first and third-party/internal
modules follow separated by a blank line; specifically move import SwiftUI to
the top, then add a blank line and sort the remaining module imports
alphabetically (import DVData, import DVDomain, import DVPresentation) to comply
with the project’s import ordering rule.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In
`@Projects/DVData/Sources/ServiceImpl/Security/Keychain/KeychainKeyStore.swift`:
- Around line 102-104: Remove the force-unwrap of baseAddress in the
SecRandomCopyBytes call inside bytes.withUnsafeMutableBytes: replace the direct
$0.baseAddress! usage with a safe guard (e.g., inside the closure do guard let
base = $0.baseAddress else { return errSecParam } and pass base to
SecRandomCopyBytes) so that bytes.withUnsafeMutableBytes { ... } returns
errSecParam when baseAddress is nil; update the surrounding code in
KeychainKeyStore.swift (the bytes.withUnsafeMutableBytes / SecRandomCopyBytes
invocation) to handle the returned status accordingly.

In `@Projects/DVData/Sources/Storage/Local/LocalStorage.swift`:
- Line 10: modelContainer이 현재 `public lazy var modelContainer`로 외부에서 재할당 가능한
상태이므로 접근을 제한하세요; 수정 방법은 두 가지 중 하나로 선택합니다: 1) 인스턴스 생성 시 즉시 초기화할 수 있으면 `public let
modelContainer`로 변경하고 초기값을 생성자(init)에서 설정하거나, 2) lazy 초기화가 필요하면 `public
private(set) lazy var modelContainer`로 변경해 외부 쓰기를 막고 내부에서만 변경 가능하게 만드세요; 대상 심볼은
modelContainer이며 변경은 이 프로퍼티 선언부와 필요한 초기화 로직(초기화 생성자 또는 기존 lazy 블록)만 수정하면 됩니다.
- Around line 10-21: The lazy modelContainer currently calls fatalError on
ModelContainer init failure; change this to propagate the error instead: remove
fatalError from the modelContainer closure and refactor so the ModelContainer
creation can throw and be handled by the composition root (e.g., make
LocalStorage initializer throw or expose a throwing factory method that
constructs ModelContainer using ModelConfiguration and Schema.appSchema), or
make modelContainer optional and surface the init error via a thrown error or
result type; also tighten access control where possible (e.g., mark
modelContainer private or fileprivate if not used externally) and ensure callers
handle initialization failures to present fallback or error UI instead of
crashing.

In `@Projects/DVDomain/Sources/UseCase/Impl/Secret/FetchSecretUseCaseImpl.swift`:
- Around line 41-45: 현재 revealPayload 구현은 repository.fetch(id:)로 시크릿 존재를 먼저 확인해
SecretUseCaseError.secretNotFound를 던지므로, 인증 전에도 ID 존재 여부가 노출됩니다; authenticate를
먼저 호출하도록 revealPayload의 흐름을 변경해 authenticationService.authenticate(reason:)를 호출한
뒤 repository.fetch(id:)로 시크릿을 조회하고, 존재하지 않으면 SecretUseCaseError.secretNotFound를
던지며 마지막으로 cryptoService.decryptPayload(_:as:)로 복호화 결과를 반환하도록 수정하세요.

In `@Projects/DVPresentation/Sources/SecretUseCaseDemoView.swift`:
- Around line 197-202: In revealSelectedPayload(), the code always calls
fetchSecretUseCase.revealPayload(id:as:) with APIKeyPayload.self which causes
failures when the selectedSecret is not an API key; change the logic to inspect
selectedSecret (e.g., selectedSecret?.type or secretType) and branch by type
(switch or if) to call revealPayload with the matching payload type (or handle
unsupported types), then assign revealedPayload and statusMessage accordingly so
non-APIKey secrets don’t try to decode as APIKeyPayload.self.

---

Nitpick comments:
In `@Projects/Devault/Sources/ContentView.swift`:
- Around line 1-4: Reorder the import statements so built-in frameworks come
first and third-party/internal modules follow separated by a blank line;
specifically move import SwiftUI to the top, then add a blank line and sort the
remaining module imports alphabetically (import DVData, import DVDomain, import
DVPresentation) to comply with the project’s import ordering rule.

In
`@Projects/DVData/Sources/RepositoryImpl/Secret/SecretFetchDescriptorBuilder.swift`:
- Around line 42-50: The force-unwrapping of secret.expiresAt inside the
`#Predicate` in the .expired(referenceDate) case is unsafe given macro
constraints; replace secret.expiresAt! < referenceDate with an optional-safe
expression such as secret.expiresAt.map { $0 < referenceDate } == true (or, if
the macro allows, (secret.expiresAt ?? Date.distantPast) < referenceDate) so no
force unwrap is used; if neither optional-mapping approach is supported by
`#Predicate`, retain the current logic but add a concise comment in
SecretFetchDescriptorBuilder near the .expired(referenceDate) case and the
`#Predicate`<SwiftDataModel.Secret> explaining why force-unwrapping is safe
(because of the preceding secret.expiresAt != nil check) and noting the macro
limitation.

In `@Projects/DVDomain/Sources/Entity/Secret.swift`:
- Line 14: Secret 구조체의 Bool 프로퍼티명 `liked`는 네이밍 규칙에 어긋나므로 `isLiked`로 리네임하세요; 변경해야
할 곳은 Entity/Secret의 선언(`public var liked` → `public var isLiked`), 관련
Draft/Patch 타입들(동일 필드명), 초기화/복사 메서드, Codable CodingKeys, DB/mapping 키, 사용처(모든
참조와 테스트), 그리고 Equatable/Hashable 구현부입니다; 리네임 시 기존 외부 API/직렬화 포맷이 변경되는 경우 기존 키와
매핑을 유지하거나 마이그레이션 처리(예: CodingKeys에 이전 이름 포함)하도록 함께 조정하세요.

In `@Projects/DVDomain/Sources/Repository/SecretQuery.swift`:
- Around line 31-47: Replace the `public extension SecretQuery` block by
declaring the enums with explicit access control instead of using an extension:
remove the extension wrapper and make two top-level declarations `public enum
Collection: Equatable, Sendable { ... }` and `public enum Sort: Equatable,
Sendable { ... }` (keeping the same cases), then update any uses of
`SecretQuery.Collection` / `SecretQuery.Sort` to the new types or adjust
placement so the enums remain nested but declared directly inside the
`SecretQuery` type with `public` access on each enum declaration rather than
using `public extension`.

In `@Projects/DVDomain/Sources/SecretContent/Metadata/DatabaseMetadata.swift`:
- Around line 10-23: Rename the Bool property sslRequired to isSSLRequired and
update its initializer parameter and assignment in the DatabaseMetadata init so
the stored property and constructor parameter match (replace occurrences of
sslRequired with isSSLRequired in the property declaration, initializer
parameter list, and self assignment). Also search for and update all
references/usages of DatabaseMetadata.sslRequired to
DatabaseMetadata.isSSLRequired (including any decoding/encoding keys or Codable
conformance if present) to keep the API and serialization consistent.

In `@Projects/DVDomain/Sources/UseCase/Error/SecretUseCaseError.swift`:
- Line 12: The .unexpected enum variant currently loses cause info; change it to
carry context (e.g. change `case unexpected` to `case unexpected(String)` or
`case unexpected(Error)` in SecretUseCaseError, update all places that construct
`.unexpected` (and the other identical occurrence referenced) to pass a
descriptive string such as `String(describing: error)` or
`error.localizedDescription`, and update any switch/throw sites and tests to
handle the associated value so the original error/debug context is preserved for
logging and debugging.

In `@Projects/DVDomain/Sources/UseCase/Impl/Secret/CreateSecretUseCaseImpl.swift`:
- Around line 23-76: Extract the duplicated Secret construction in both execute
overloads of CreateSecretUseCaseImpl into a private helper (e.g.,
makeSecret(from:draft:payload:metadataOpt:now:)) that accepts the SecretDraft,
current date (from dateProvider()), encrypted payload (from
cryptoService.encryptPayload), optional encoded metadata (from
cryptoService.encodeMetadata), and uses idGenerator(),
draft.name/secretType/subType/service/environment/expiresAt/memo/liked to set
fields plus createdAt/updatedAt/payload and metadata when present; update both
public execute methods to call SecretUseCaseHelper.validateDraft, compute
now/encryptedPayload/(encodedMetadata), call the new helper to get the Secret,
then return try await repository.create(secret), preserving existing error
handling (SecretUseCaseError.map).

In `@Projects/DVPresentation/Sources/SecretUseCaseDemoView.swift`:
- Around line 54-55: Rename the event-handler functions to past-tense
`didTap...` form for consistency: change `createDemoSecret` →
`didTapCreateDemoSecret`, `revealSelectedPayload` →
`didTapRevealSelectedPayload`, `fetchAllSecrets` → `didTapFetchAllSecrets` (and
any other handlers noted at the same locations) and update all call sites (e.g.,
Task { await createDemoSecret() } to Task { await didTapCreateDemoSecret() })
plus any references in UI button labels, method declarations, and tests; ensure
async/throws signatures remain unchanged and run a build to fix any broken
imports/usages.
- Around line 3-4: The import statements are out of order: move the system
framework import SwiftUI to be first, then add a blank line and place the module
import DVDomain after it so imports follow the guideline (system frameworks
first, blank line, third-party/local modules) — update the import block
containing SwiftUI and DVDomain accordingly.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: e67782fc-98df-4222-80ea-deddaffed4e3

📥 Commits

Reviewing files that changed from the base of the PR and between 12c8009 and e69a414.

📒 Files selected for processing (61)
  • Projects/DVData/Project.swift
  • Projects/DVData/Sources/RepositoryImpl/Secret/InMemorySecretQueryFilter.swift
  • Projects/DVData/Sources/RepositoryImpl/Secret/SecretFetchDescriptorBuilder.swift
  • Projects/DVData/Sources/RepositoryImpl/Secret/SecretRepositoryImpl.swift
  • Projects/DVData/Sources/ServiceImpl/Authentication/LocalUserAuthenticationServiceImpl.swift
  • Projects/DVData/Sources/ServiceImpl/Security/JSONCoder/SecretMetadataJSONCoder.swift
  • Projects/DVData/Sources/ServiceImpl/Security/JSONCoder/SecretPayloadJSONCoder.swift
  • Projects/DVData/Sources/ServiceImpl/Security/Keychain/KeychainKeyStore.swift
  • Projects/DVData/Sources/ServiceImpl/Security/SecretCryptoServiceImpl.swift
  • Projects/DVData/Sources/Storage/Local/LocalStorage.swift
  • Projects/DVData/Sources/Storage/Local/Models/Secret.swift
  • Projects/DVData/Sources/Storage/Local/Models/SecretMetadata.swift
  • Projects/DVData/Sources/Storage/Local/Models/SecretPayload.swift
  • Projects/DVDesign/Project.swift
  • Projects/DVDomain/Project.swift
  • Projects/DVDomain/Sources/Entity/Secret.swift
  • Projects/DVDomain/Sources/Entity/SecretMetadata.swift
  • Projects/DVDomain/Sources/Entity/SecretPayload.swift
  • Projects/DVDomain/Sources/Repository/Error/SecretRepositoryError.swift
  • Projects/DVDomain/Sources/Repository/Interface/SecretRepository.swift
  • Projects/DVDomain/Sources/Repository/SecretPatch.swift
  • Projects/DVDomain/Sources/Repository/SecretQuery.swift
  • Projects/DVDomain/Sources/SecretContent/Metadata/APIKeyMetadata.swift
  • Projects/DVDomain/Sources/SecretContent/Metadata/DatabaseMetadata.swift
  • Projects/DVDomain/Sources/SecretContent/Metadata/Interface/SecretMetadataContent.swift
  • Projects/DVDomain/Sources/SecretContent/Metadata/LicenseKeyMetadata.swift
  • Projects/DVDomain/Sources/SecretContent/Metadata/OAuthClientMetadata.swift
  • Projects/DVDomain/Sources/SecretContent/Metadata/SSHKeyMetadata.swift
  • Projects/DVDomain/Sources/SecretContent/Metadata/SSLCertMetadata.swift
  • Projects/DVDomain/Sources/SecretContent/Metadata/ServiceAccountMetadata.swift
  • Projects/DVDomain/Sources/SecretContent/Payload/APIKeyPayload.swift
  • Projects/DVDomain/Sources/SecretContent/Payload/CustomPayload.swift
  • Projects/DVDomain/Sources/SecretContent/Payload/DatabasePayload.swift
  • Projects/DVDomain/Sources/SecretContent/Payload/EnvSetPayload.swift
  • Projects/DVDomain/Sources/SecretContent/Payload/Interface/SecretPayloadData.swift
  • Projects/DVDomain/Sources/SecretContent/Payload/LicenseKeyPayload.swift
  • Projects/DVDomain/Sources/SecretContent/Payload/OAuthClientPayload.swift
  • Projects/DVDomain/Sources/SecretContent/Payload/SSHKeyPayload.swift
  • Projects/DVDomain/Sources/SecretContent/Payload/SSLCertPayload.swift
  • Projects/DVDomain/Sources/SecretContent/Payload/ServiceAccountPayload.swift
  • Projects/DVDomain/Sources/Service/Error/SecretCryptoError.swift
  • Projects/DVDomain/Sources/Service/Error/UserAuthenticationError.swift
  • Projects/DVDomain/Sources/Service/Interface/SecretCryptoService.swift
  • Projects/DVDomain/Sources/Service/Interface/UserAuthenticationService.swift
  • Projects/DVDomain/Sources/UseCase/Draft/SecretDraft.swift
  • Projects/DVDomain/Sources/UseCase/Error/SecretUseCaseError.swift
  • Projects/DVDomain/Sources/UseCase/Impl/Secret/CreateSecretUseCaseImpl.swift
  • Projects/DVDomain/Sources/UseCase/Impl/Secret/DeleteSecretUseCaseImpl.swift
  • Projects/DVDomain/Sources/UseCase/Impl/Secret/FetchSecretUseCaseImpl.swift
  • Projects/DVDomain/Sources/UseCase/Impl/Secret/PatchSecretUseCaseImpl.swift
  • Projects/DVDomain/Sources/UseCase/Impl/Secret/SecretUseCaseHelper.swift
  • Projects/DVDomain/Sources/UseCase/Interface/Secret/CreateSecretUseCase.swift
  • Projects/DVDomain/Sources/UseCase/Interface/Secret/DeleteSecretUseCase.swift
  • Projects/DVDomain/Sources/UseCase/Interface/Secret/FetchSecretUseCase.swift
  • Projects/DVDomain/Sources/UseCase/Interface/Secret/PatchSecretUseCase.swift
  • Projects/DVDomain/Tests/.gitkeep
  • Projects/DVDomain/Tests/ExampleTest.swift
  • Projects/DVPresentation/Project.swift
  • Projects/DVPresentation/Sources/SecretUseCaseDemoView.swift
  • Projects/Devault/Project.swift
  • Projects/Devault/Sources/ContentView.swift
💤 Files with no reviewable changes (1)
  • Projects/DVDomain/Tests/ExampleTest.swift

Comment on lines +102 to +104
let status = bytes.withUnsafeMutableBytes {
SecRandomCopyBytes(kSecRandomDefault, count, $0.baseAddress!)
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

강제 언래핑 제거 및 baseAddress nil 처리 필요
SecRandomCopyBytes(..., $0.baseAddress!)의 강제 언래핑은 baseAddress가 nil인 경우 크래시 리스크가 있어 guard let로 안전 처리하세요(예: nil이면 errSecParam 반환).

개선 예시
-        let status = bytes.withUnsafeMutableBytes {
-            SecRandomCopyBytes(kSecRandomDefault, count, $0.baseAddress!)
-        }
+        let status = bytes.withUnsafeMutableBytes { buffer -> OSStatus in
+            guard let baseAddress = buffer.baseAddress else {
+                return errSecParam
+            }
+            return SecRandomCopyBytes(kSecRandomDefault, count, baseAddress)
+        }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
let status = bytes.withUnsafeMutableBytes {
SecRandomCopyBytes(kSecRandomDefault, count, $0.baseAddress!)
}
let status = bytes.withUnsafeMutableBytes { buffer -> OSStatus in
guard let baseAddress = buffer.baseAddress else {
return errSecParam
}
return SecRandomCopyBytes(kSecRandomDefault, count, baseAddress)
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@Projects/DVData/Sources/ServiceImpl/Security/Keychain/KeychainKeyStore.swift`
around lines 102 - 104, Remove the force-unwrap of baseAddress in the
SecRandomCopyBytes call inside bytes.withUnsafeMutableBytes: replace the direct
$0.baseAddress! usage with a safe guard (e.g., inside the closure do guard let
base = $0.baseAddress else { return errSecParam } and pass base to
SecRandomCopyBytes) so that bytes.withUnsafeMutableBytes { ... } returns
errSecParam when baseAddress is nil; update the surrounding code in
KeychainKeyStore.swift (the bytes.withUnsafeMutableBytes / SecRandomCopyBytes
invocation) to handle the returned status accordingly.

Comment thread Projects/DVData/Sources/Storage/Local/LocalStorage.swift Outdated
Comment thread Projects/DVData/Sources/Storage/Local/LocalStorage.swift Outdated
Comment on lines +197 to +202
func revealSelectedPayload() async {
guard let id = selectedSecret?.id else { return }

await run("Revealing payload...") {
revealedPayload = try await fetchSecretUseCase.revealPayload(id: id, as: APIKeyPayload.self)
statusMessage = "Payload revealed"

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

타입 고정 Reveal로 인해 선택한 secretType과 불일치 시 항상 실패합니다.

Line 201에서 APIKeyPayload.self를 고정 사용하고 있어, API Key가 아닌 레코드를 선택하면 기본적으로 실패 상태가 됩니다. 타입 가드 후 사용자 메시지를 분기하는 편이 안전합니다.

🔧 제안 패치
 func revealSelectedPayload() async {
-    guard let id = selectedSecret?.id else { return }
+    guard let selectedSecret else { return }
+    guard selectedSecret.secretType == "apiKey" else {
+        statusMessage = "Unsupported payload type for demo reveal"
+        revealedPayload = nil
+        return
+    }
+    let id = selectedSecret.id
 
     await run("Revealing payload...") {
         revealedPayload = try await fetchSecretUseCase.revealPayload(id: id, as: APIKeyPayload.self)
         statusMessage = "Payload revealed"
     }
 }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@Projects/DVPresentation/Sources/SecretUseCaseDemoView.swift` around lines 197
- 202, In revealSelectedPayload(), the code always calls
fetchSecretUseCase.revealPayload(id:as:) with APIKeyPayload.self which causes
failures when the selectedSecret is not an API key; change the logic to inspect
selectedSecret (e.g., selectedSecret?.type or secretType) and branch by type
(switch or if) to call revealPayload with the matching payload type (or handle
unsupported types), then assign revealedPayload and statusMessage accordingly so
non-APIKey secrets don’t try to decode as APIKeyPayload.self.

@dlguszoo
dlguszoo requested a review from yeseonglee May 23, 2026 15:02

@doyeonk429 doyeonk429 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ 좋은 점

  • 레이어 책임 분리가 깔끔함. Domain은 외부 의존 없이 Entity/UseCase/Service 인터페이스만 정의, Data가 SwiftData/Keychain/LocalAuth 구현 담당. Secret Domain
    entity와 SwiftDataModel.Secret 분리 + toDomain() 매핑도 일관됨.
  • @Modelactor 사용으로 SwiftData 동시성 안전성 확보. SecretRepositoryImpl이 actor로 묶여 있어 ModelContext 격리가 자연스럽게 해결됨.
  • PatchField enum의 .unchanged / .set(nil) 구분이 우아함. partial update에서 흔히 망가지는 부분을 잘 잡았습니다.
  • keyTag를 payload별로 저장하는 설계 → 향후 key rotation 시 옛 데이터 복호화 가능. payload.keyTag로 복호화하고 getSymmetricKey는 새 키 생성 안 함 — 정확한 선택.
  • schemaVersion을 payload/metadata에 박아둠 → 향후 content 구조 변경 마이그레이션 대응 여지 확보.
  • idGenerator/dateProvider 주입 → UseCase 테스트 친화적 설계.
  • kSecAttrAccessibleWhenUnlockedThisDeviceOnly 선택 적절. iCloud Keychain 동기화 차단 + 잠금 상태에서 접근 차단.
  • 에러 계층 분리(Repository/Crypto/Auth/UseCase) + SecretUseCaseError.map의 패스스루(as? SecretUseCaseError 먼저 체크) 처리 깔끔.

Comment thread Projects/DVData/Sources/Storage/Local/LocalStorage.swift Outdated
@doyeonk429
doyeonk429 force-pushed the feature/#12/Secret_ORM branch from 9ab75e8 to 9faeab4 Compare May 24, 2026 14:43
dlguszoo added 21 commits June 30, 2026 15:06
# Conflicts:
#	Projects/DVDomain/Project.swift
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

✨ Feature 새로운 기능 개발

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Feature: SwiftData Secret 모델 CRUD 메서드 구현

2 participants