Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. Weโ€™ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

๐Ÿ”€ :: ์ถ”์ƒํ™” ๋ชจ๋“ˆ ์ž‘์—… #54

Merged
merged 4 commits into from
Jun 29, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ public extension TargetDependency {
}

public extension TargetDependency.SPM {
static let GRDB = TargetDependency.external(name: "GRDB")
static let IQKeyboardManagerSwift = TargetDependency.external(name: "IQKeyboardManagerSwift")
static let Store = TargetDependency.external(name: "Store")
static let Moordinator = TargetDependency.external(name: "Moordinator")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,8 @@ public extension ModulePaths {

public extension ModulePaths {
enum Core: String, MicroTargetPathConvertable {
case Database
case KeyValueStore
case JwtStore
}
}
Expand Down
1 change: 1 addition & 0 deletions Projects/App/Project.swift
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ let targets: [Target] = [
infoPlist: .file(path: "Support/Info.plist"),
sources: ["Sources/**"],
resources: ["Resources/**"],
entitlements: "Support/Dotori.entitlements",
scripts: scripts,
dependencies: [
.feature(target: .RootFeature),
Expand Down
10 changes: 10 additions & 0 deletions Projects/App/Support/Dotori.entitlements
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>com.apple.security.application-groups</key>
<array>
<string>group.msg.dotori</string>
</array>
</dict>
</plist>
76 changes: 76 additions & 0 deletions Projects/Core/Database/Interface/LocalDatabase.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
import Foundation
import GRDB

public protocol LocalDatabase {
func save(record: some FetchableRecord & PersistableRecord) throws

func save(records: [some FetchableRecord & PersistableRecord]) throws

func readRecords<Record: FetchableRecord & PersistableRecord>(
as record: Record.Type,
filter: [String: some DatabaseValueConvertible],
ordered: [some SQLOrderingTerm]
) throws -> [Record]

func readRecords<Record: FetchableRecord & PersistableRecord>(
as record: Record.Type,
filter: [String: some DatabaseValueConvertible],
ordered: [some SQLOrderingTerm],
limit: Int,
offset: Int?
) throws -> [Record]

func readRecord<Record: FetchableRecord & PersistableRecord>(
as record: Record.Type,
at key: some DatabaseValueConvertible
) throws -> Record?

func updateRecord<Record: FetchableRecord & PersistableRecord>(
as record: Record.Type,
at key: some DatabaseValueConvertible,
transform: (inout Record) -> Void
) throws

func delete(
as record: some FetchableRecord & PersistableRecord
) throws

func delete<Record: FetchableRecord & PersistableRecord>(
as record: Record.Type,
key: some DatabaseValueConvertible
) throws

func deleteAll<Record: FetchableRecord & PersistableRecord>(
as record: Record.Type
) throws
}

public extension LocalDatabase {
func readRecords<Record: FetchableRecord & PersistableRecord>(
as record: Record.Type,
filter: [String: some DatabaseValueConvertible],
ordered: [some SQLOrderingTerm],
limit: Int,
offset: Int?
) throws -> [Record] {
try self.readRecords(
as: record,
filter: filter,
ordered: ordered,
limit: limit,
offset: offset
)
}

func readRecords<Record: FetchableRecord & PersistableRecord>(
as record: Record.Type,
filter: [String: some DatabaseValueConvertible],
ordered: [some SQLOrderingTerm]
) throws -> [Record] {
try self.readRecords(
as: record,
filter: filter,
ordered: ordered
)
}
}
21 changes: 21 additions & 0 deletions Projects/Core/Database/Project.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import ProjectDescription
import ProjectDescriptionHelpers
import DependencyPlugin

let project = Project.module(
name: ModulePaths.Core.Database.rawValue,
targets: [
.interface(module: .core(.Database), dependencies: [
.SPM.GRDB
]),
.implements(module: .core(.Database), dependencies: [
.core(target: .Database, type: .interface),
]),
.testing(module: .core(.Database), dependencies: [
.core(target: .Database, type: .interface)
]),
.tests(module: .core(.Database), dependencies: [
.core(target: .Database)
])
]
)
147 changes: 147 additions & 0 deletions Projects/Core/Database/Sources/GRDBLocalDatabase.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
import DatabaseInterface
import Foundation
import GRDB

final class GRDBLocalDatabase: LocalDatabase {
private let dbQueue: DatabaseQueue
private let migrator: DatabaseMigrator

init(migrate: (inout DatabaseMigrator) -> Void) {
var url = FileManager.default
.containerURL(forSecurityApplicationGroupIdentifier: "group.msg.dotori")!

if #available(iOS 16, macOS 13.0, *) {
url.append(path: "Dotori")
} else {
url.appendPathComponent("Dotori")
}

try? FileManager.default.createDirectory(
at: url,
withIntermediateDirectories: false,
attributes: [
FileAttributeKey.protectionKey: URLFileProtection.none
]
)

if #available(iOS 16.0, macOS 13.0, *) {
url.append(path: "Dotori.sqlite")
} else {
url.appendPathComponent("Dotori.sqlite")
}

var dir = ""

if #available(iOS 16.0, macOS 13.0, *) {
dir = url.path()
} else {
dir = url.path
}

if #available(iOS 16.0, macOS 13.0, *) {
dir.replace("%20", with: " ")
} else {
dir = dir.replacingOccurrences(of: "%20", with: " ")
}

do {
self.dbQueue = try DatabaseQueue(path: dir)
} catch {
fatalError()
}

var migrator = DatabaseMigrator()
migrate(&migrator)
self.migrator = migrator
try? self.migrator.migrate(self.dbQueue)
}

func save(record: some FetchableRecord & PersistableRecord) throws {
try self.dbQueue.write { db in
try record.save(db)
}
}

func save(records: [some FetchableRecord & PersistableRecord]) throws {
try self.dbQueue.write { db in
try records.forEach {
try $0.save(db)
}
}
}

func readRecords<Record: FetchableRecord & PersistableRecord>(
as record: Record.Type,
filter: [String: some DatabaseValueConvertible],
ordered: [some SQLOrderingTerm]
) throws -> [Record] {
try self.dbQueue.read { db in
try record
.filter(key: filter)
.order(ordered)
.fetchAll(db)
}
}

func readRecords<Record: FetchableRecord & PersistableRecord>(
as record: Record.Type,
filter: [String: some DatabaseValueConvertible],
ordered: [some SQLOrderingTerm],
limit: Int,
offset: Int?
) throws -> [Record] {
try self.dbQueue.read { db in
try record
.filter(key: filter)
.order(ordered)
.limit(limit, offset: offset)
.fetchAll(db)
}
}

func readRecord<Record: FetchableRecord & PersistableRecord>(
as record: Record.Type,
at key: some DatabaseValueConvertible
) throws -> Record? {
try dbQueue.read { db in
try record.fetchOne(db, key: key)
}
}

func updateRecord<Record: FetchableRecord & PersistableRecord>(
as record: Record.Type,
at key: some DatabaseValueConvertible,
transform: (inout Record) -> Void
) throws {
try dbQueue.write { db in
if var value = try record.fetchOne(db, key: key) {
try value.updateChanges(db, modify: transform)
}
}
}

func delete(
as record: some FetchableRecord & PersistableRecord
) throws {
try dbQueue.write { db in
_ = try record.delete(db)
}
}

func delete<Record: FetchableRecord & PersistableRecord>(
as record: Record.Type,
key: some DatabaseValueConvertible
) throws {
try dbQueue.write { db in
_ = try record.deleteOne(db, key: key)
}
}

func deleteAll<Record: FetchableRecord & PersistableRecord>(
as record: Record.Type
) throws {
try dbQueue.write { db in
_ = try record.deleteAll(db)
}
}
}
76 changes: 76 additions & 0 deletions Projects/Core/Database/Testing/MockLocalDatabase.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
import DatabaseInterface
import GRDB

final class MockLocalDatabase: LocalDatabase {
var saveCallCount = 0
func save(record: some FetchableRecord & PersistableRecord) throws {
saveCallCount += 1
}

var allSaveCallCount = 0
func save(records: [some FetchableRecord & PersistableRecord]) throws {
allSaveCallCount += 1
}

var readRecordsCallCount = 0
func readRecords<Record: FetchableRecord & PersistableRecord>(
as record: Record.Type,
filter: [String: some DatabaseValueConvertible],
ordered: [some SQLOrderingTerm]
) throws -> [Record] {
readRecordsCallCount += 1
return []
}

var readRecordsWithLimitCallCount = 0
func readRecords<Record: FetchableRecord & PersistableRecord>(
as record: Record.Type,
filter: [String: some DatabaseValueConvertible],
ordered: [some SQLOrderingTerm],
limit: Int,
offset: Int?
) throws -> [Record] {
readRecordsWithLimitCallCount += 1
return []
}

var readRecordCallCount = 0
func readRecord<Record: FetchableRecord & PersistableRecord>(
as record: Record.Type,
at key: some DatabaseValueConvertible
) throws -> Record? {
readRecordCallCount += 1
return nil
}

var updateRecordCallCount = 0
func updateRecord<Record: FetchableRecord & PersistableRecord>(
as record: Record.Type,
at key: some DatabaseValueConvertible,
transform: (inout Record) -> Void
) throws {
updateRecordCallCount += 1
}

var deleteCallCount = 0
func delete(
as record: some FetchableRecord & PersistableRecord
) throws {
deleteCallCount += 1
}

var deleteKeyCallCount = 0
func delete<Record: FetchableRecord & PersistableRecord>(
as record: Record.Type,
key: some DatabaseValueConvertible
) throws {
deleteKeyCallCount += 1
}

var deleteAllCallCount = 0
func deleteAll<Record: FetchableRecord & PersistableRecord>(
as record: Record.Type
) throws {
deleteAllCallCount += 1
}
}
11 changes: 11 additions & 0 deletions Projects/Core/Database/Tests/DatabaseTest.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import XCTest

final class DatabaseTests: XCTestCase {
override func setUpWithError() throws {}

override func tearDownWithError() throws {}

func testExample() {
XCTAssertEqual(1, 1)
}
}
8 changes: 8 additions & 0 deletions Projects/Core/KeyValueStore/Interface/KeyValueStore.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import Foundation

public protocol KeyValueStore {
func save(key: String, value: Any)
func load(key: String) -> Any?
func load<T>(key: String) -> T?
func delete(key: String)
}
Loading