Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
dc1a4db
refactor: #101 기존 레포지토리 임시 폴더링 수정
dev-domo Feb 3, 2026
64faf0e
feat: #101 데이터베이스 스키마 설정
dev-domo Feb 3, 2026
6e63073
refactor: #101 소셜 로그인, 로그아웃 기능 삭제
dev-domo Feb 11, 2026
409bbdb
refactor: #101 프레임워크명 명시
dev-domo Feb 11, 2026
a0ed942
feat: #101 CoreData 사용 설정
dev-domo Feb 11, 2026
2dbe90a
feat: #101 Member API를 CoreData로 대체
dev-domo Feb 11, 2026
edb4d34
test: #101 Member 관련 유스케이스 테스트 작성
dev-domo Feb 11, 2026
62f6a1c
test: #101 TermsStorage 기능 테스트
dev-domo Feb 25, 2026
9a551d4
refactor: #101 테스트 환경에서 앱과 동일한 모델 인스턴스를 재사용하도록 수정
dev-domo Feb 25, 2026
152d4e9
feat: MemberStorage 구현 및 테스트
dev-domo Mar 4, 2026
7110af7
refactor: #101 생성자 내 객체 생성 코드 수정
dev-domo Mar 4, 2026
6a98f52
refactor: #101 구조체 네이밍 수정
dev-domo Mar 4, 2026
19b5db2
chore: #101 테스트 메서드명 수정
dev-domo Mar 4, 2026
07b6237
refactor: #101 NotificationsEntity 속성명 수정
dev-domo Mar 4, 2026
8b0166d
refactor: #101 엔티티 id를 Int64형으로 수정
dev-domo Mar 11, 2026
b9fa9ef
refactor: #101 파라미터명 수정사항 반영
dev-domo Mar 11, 2026
d80de5b
test: #101 시나리오 엔티티 관련 테스트 작성
dev-domo Mar 11, 2026
e3f4566
test: #101 미션 관련 유스케이스 테스트 작성
dev-domo Mar 11, 2026
16884eb
feat: #101 Mission 저장소 구현
dev-domo Mar 11, 2026
8cb633a
feat: #101 Scenario 저장소 구현
dev-domo Mar 11, 2026
a984b66
feat: #101 Terms 저장소 구현
dev-domo Mar 11, 2026
0ae9d36
chore: #101 불필요한 열거형 제거
dev-domo Mar 11, 2026
f5d3ab0
feat: #101 엔티티 id 배정 유틸리티 구현
dev-domo Mar 11, 2026
737a360
refactor: #101 toRawvalues 메서드 구현
dev-domo Mar 11, 2026
bb4346f
refactor: #101 저장소 구현에 필요한 에러 케이스 정의
dev-domo Mar 11, 2026
0e8ec30
refactor: #101 DefaultValue 재설정
dev-domo Mar 12, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions BeforeGoing.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,13 @@
/* End PBXFileReference section */

/* Begin PBXFileSystemSynchronizedBuildFileExceptionSet section */
1827AF552F307CD200D18376 /* Exceptions for "BeforeGoing" folder in "BeforeGoingTests" target */ = {
isa = PBXFileSystemSynchronizedBuildFileExceptionSet;
membershipExceptions = (
Data/BeforeGoingModel.xcdatamodel,
);
target = 186E06F02DDDF10600E32490 /* BeforeGoingTests */;
};
186E07032DDDF10600E32490 /* Exceptions for "BeforeGoing" folder in "BeforeGoing" target */ = {
isa = PBXFileSystemSynchronizedBuildFileExceptionSet;
membershipExceptions = (
Expand All @@ -45,6 +52,7 @@
isa = PBXFileSystemSynchronizedRootGroup;
exceptions = (
186E07032DDDF10600E32490 /* Exceptions for "BeforeGoing" folder in "BeforeGoing" target */,
1827AF552F307CD200D18376 /* Exceptions for "BeforeGoing" folder in "BeforeGoingTests" target */,
);
path = BeforeGoing;
sourceTree = "<group>";
Expand Down
9 changes: 9 additions & 0 deletions BeforeGoing/Core/BeforeGoingError.swift
Original file line number Diff line number Diff line change
Expand Up @@ -30,4 +30,13 @@ enum BeforeGoingError: Error, Equatable {
case tooManyRequset
case withdrawFailed
case notFoundProvider


case userIDNotFound
case memberNotFound
case termsNotFound
case scenarioNotFound
case notificationNotFound
case invalidMissionType
case invalidDateFormat
}
64 changes: 64 additions & 0 deletions BeforeGoing/Data/BeforeGoingModel.xcdatamodel/contents
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<model type="com.apple.IDECoreDataModeler.DataModel" documentVersion="1.0" lastSavedToolsVersion="23605" systemVersion="24D60" minimumToolsVersion="Automatic" sourceLanguage="Swift" userDefinedModelVersionIdentifier="">
<entity name="Member" representedClassName="Member" syncable="YES" codeGenerationType="class">
<attribute name="createdAt" attributeType="Date" usesScalarValueType="NO"/>
<attribute name="id" attributeType="Integer 64" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="nickname" attributeType="String"/>
<attribute name="updatedAt" attributeType="Date" usesScalarValueType="NO"/>
<relationship name="scenarios" optional="YES" toMany="YES" deletionRule="Nullify" destinationEntity="Scenario" inverseName="member" inverseEntity="Scenario"/>
<relationship name="term" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="Terms" inverseName="member" inverseEntity="Terms"/>
</entity>
<entity name="Mission" representedClassName="Mission" syncable="YES" codeGenerationType="class">
<attribute name="content" attributeType="String"/>
<attribute name="createdAt" attributeType="Date" usesScalarValueType="NO"/>
<attribute name="id" attributeType="Integer 64" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="isChecked" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
<attribute name="missionOrder" optional="YES" attributeType="Integer 64" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="missionType" attributeType="String"/>
<attribute name="updatedAt" attributeType="Date" usesScalarValueType="NO"/>
<attribute name="useDate" optional="YES" attributeType="Date" usesScalarValueType="NO"/>
<relationship name="children" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="Mission" inverseName="parent" inverseEntity="Mission"/>
<relationship name="parent" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="Mission" inverseName="children" inverseEntity="Mission"/>
<relationship name="scenario" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="Scenario" inverseName="missions" inverseEntity="Scenario"/>
</entity>
<entity name="Notification" representedClassName="Notification" syncable="YES" codeGenerationType="class">
<attribute name="createdAt" attributeType="Date" usesScalarValueType="NO"/>
<attribute name="daysOfWeek" optional="YES" attributeType="String"/>
<attribute name="id" attributeType="Integer 64" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="isActive" attributeType="Boolean" defaultValueString="YES" usesScalarValueType="YES"/>
<attribute name="notificationMethodType" optional="YES" attributeType="String"/>
<attribute name="notificationType" attributeType="String"/>
<attribute name="updatedAt" attributeType="Date" usesScalarValueType="NO"/>
<relationship name="scenario" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="Scenario" inverseName="notification" inverseEntity="Scenario"/>
<relationship name="timeNotifications" optional="YES" toMany="YES" deletionRule="Nullify" destinationEntity="TimeNotification" inverseName="notification" inverseEntity="TimeNotification"/>
</entity>
<entity name="Scenario" representedClassName="Scenario" syncable="YES" codeGenerationType="class">
<attribute name="createdAt" attributeType="Date" usesScalarValueType="NO"/>
<attribute name="id" attributeType="Integer 64" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="memo" optional="YES" attributeType="String"/>
<attribute name="scenarioName" attributeType="String"/>
<attribute name="scenarioOrder" attributeType="Integer 64" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="updatedAt" attributeType="Date" usesScalarValueType="NO"/>
<relationship name="member" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="Member" inverseName="scenarios" inverseEntity="Member"/>
<relationship name="missions" optional="YES" toMany="YES" deletionRule="Nullify" destinationEntity="Mission" inverseName="scenario" inverseEntity="Mission"/>
<relationship name="notification" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="Notification" inverseName="scenario" inverseEntity="Notification"/>
</entity>
<entity name="Terms" representedClassName="Terms" syncable="YES" codeGenerationType="class">
<attribute name="createdAt" attributeType="Date" usesScalarValueType="NO"/>
<attribute name="eventPushAgreed" attributeType="Boolean" usesScalarValueType="YES"/>
<attribute name="id" attributeType="Integer 64" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="isOverFourteen" attributeType="Boolean" usesScalarValueType="YES"/>
<attribute name="privacyPolicyAgreed" attributeType="Boolean" usesScalarValueType="YES"/>
<attribute name="termsOfServiceAgreed" attributeType="Boolean" usesScalarValueType="YES"/>
<attribute name="updatedAt" attributeType="Date" usesScalarValueType="NO"/>
<relationship name="member" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="Member" inverseName="term" inverseEntity="Member"/>
</entity>
<entity name="TimeNotification" representedClassName="TimeNotification" syncable="YES" codeGenerationType="class">
<attribute name="createdAt" attributeType="Date" usesScalarValueType="NO"/>
<attribute name="id" attributeType="Integer 64" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="startHour" attributeType="Integer 64" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="startMinute" attributeType="Integer 64" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="updatedAt" attributeType="Date" usesScalarValueType="NO"/>
<relationship name="notification" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="Notification" inverseName="timeNotifications" inverseEntity="Notification"/>
</entity>
</model>
23 changes: 8 additions & 15 deletions BeforeGoing/Data/DataDependencyAssembler.swift
Original file line number Diff line number Diff line change
Expand Up @@ -35,15 +35,7 @@ struct DataDependencyAssembler: DependencyAssembler {
DIContainer.shared.register(updateNicknameRequestMapper)

DIContainer.shared.register(type: AuthInterface.self) { _ in
AuthRepository(
networkService: networkService,
tokenReissuer: tokenReissuer,
keyChainService: keyChainService,
userDefaultsService: userDefaultsService,
nonceRequestMapper: nonceRequestMapper,
loginRequestMapper: loginRequestMapper,
tokenValidator: tokenValidator
)
AuthRepository(userDefaultsService: userDefaultsService)
}
DIContainer.shared.register(type: TermsInterface.self) { _ in
TermsRepository(
Expand All @@ -55,12 +47,13 @@ struct DataDependencyAssembler: DependencyAssembler {
)
}
DIContainer.shared.register(type: MemberInterface.self) { _ in
MemberRepository(
networkService: networkService,
keyChainService: keyChainService,
userDefaultsService: userDefaultsService,
updateNicknameRequestMapper: updateNicknameRequestMapper
)
// MemberRepository(
// networkService: networkService,
// keyChainService: keyChainService,
// userDefaultsService: userDefaultsService,
// updateNicknameRequestMapper: updateNicknameRequestMapper
// )
MemberStorage(userDefaultsService: userDefaultsService)
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

critical

MemberStorage를 초기화할 때 NSManagedObjectContext가 누락되었습니다. MemberStorageinitcontext를 필요로 하므로, 이대로는 런타임에 크래시가 발생할 것입니다. CoreDataStack.shared.context를 주입해야 합니다.

Suggested change
MemberStorage(userDefaultsService: userDefaultsService)
MemberStorage(userDefaultsService: userDefaultsService, context: CoreDataStack.shared.context)

}
DIContainer.shared.register(type: ScenarioInterface.self) { _ in
ScenarioRepository(
Expand Down
2 changes: 1 addition & 1 deletion BeforeGoing/Data/Model/Notification/NotificationsDTO.swift
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ struct ScenarioNotificationDTO: Decodable {
extension NotificationsDTO {
func toEntity() -> NotificationsEntity {
let scenarios = scenarios.map { $0.toEntity() }
return .init(scenarios: scenarios)
return .init(notifications: scenarios)
}
}

Expand Down
4 changes: 2 additions & 2 deletions BeforeGoing/Data/Network/Extensions/Notification.Name+.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,6 @@

import Foundation

extension Notification.Name {
static let loginExpired = Notification.Name("loginExpired")
extension Foundation.Notification.Name {
static let loginExpired = Foundation.Notification.Name("loginExpired")
}
51 changes: 51 additions & 0 deletions BeforeGoing/Data/Persistence/CoreData/CoreDataStack.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
//
// CoreDataStack.swift
// BeforeGoing
//
// Created by APPLE on 2/3/26.
//

import CoreData

final class CoreDataStack {

static let shared = CoreDataStack()
private init() {}

// 모델을 static으로 분리해서 외부에서 접근 가능하게
static let managedObjectModel: NSManagedObjectModel = {
let modelName = "BeforeGoingModel"
let uniqueBundles: [Bundle] = Bundle.allBundles
.compactMap { bundle -> (Bundle, URL)? in
guard let url = bundle.url(forResource: modelName, withExtension: "mom") else {
return nil
}
return (bundle, url)
}
.reduce(into: [(Bundle, URL)]()) { result, pair in
if !result.contains(where: { $0.1.path == pair.1.path }) {
result.append(pair)
}
}
.map { $0.0 }

guard let model = NSManagedObjectModel.mergedModel(from: uniqueBundles) else {
fatalError("NSManagedObjectModel을 로드할 수 없습니다.")
}
return model
}()

// 기존 container도 동일한 모델 인스턴스 사용
private let container: NSPersistentContainer = {
let container = NSPersistentContainer(
name: "BeforeGoingModel",
managedObjectModel: CoreDataStack.managedObjectModel
)
container.loadPersistentStores { _, error in
if let error { fatalError("Failed to load store: \(error)") }
}
return container
}()

var context: NSManagedObjectContext { container.viewContext }
}
1 change: 1 addition & 0 deletions BeforeGoing/Data/Persistence/Service/UserDefaultsKey.swift
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
//

enum UserDefaultsKey: String, CaseIterable {
case userID
case provider
case lastProvider
}
25 changes: 25 additions & 0 deletions BeforeGoing/Data/Persistence/Service/UserDefaultsService.swift
Original file line number Diff line number Diff line change
Expand Up @@ -46,3 +46,28 @@ struct UserDefaultsService: UserDefaultsProtocol {
return UserDefaults.standard.value(forKey: key.rawValue) == nil
}
}

final class MockUserDefaultsService: UserDefaultsProtocol {

private var storage: [UserDefaultsKey: Any] = [:]

func save(_ value: Any, key: UserDefaultsKey) -> Bool {
storage[key] = value
return true
}

func load<T>(key: UserDefaultsKey) -> T? {
storage[key] as? T
}

func delete(key: UserDefaultsKey) -> Bool {
guard let _ = storage.removeValue(forKey: key) else {
return false
}
return true
}

func deleteAll() {
storage.removeAll()
}
}
Loading
Loading