Implementing SwiftData storage service
This commit is contained in:
parent
df15a154be
commit
e91fd89eff
@ -28,6 +28,10 @@
|
||||
7A1441662C297EDE00E79018 /* NotesScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A1441652C297EDE00E79018 /* NotesScreen.swift */; };
|
||||
7A1441682C297EFD00E79018 /* NotesViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A1441672C297EFD00E79018 /* NotesViewModel.swift */; };
|
||||
7A17ADA02DC9F4A5002BA02A /* ScreenInput.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A17AD9F2DC9F4A5002BA02A /* ScreenInput.swift */; };
|
||||
7A1AE64F2DFB22EC00ECFC4D /* SwiftDataStorageService+Utils.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A1AE64E2DFB22EC00ECFC4D /* SwiftDataStorageService+Utils.swift */; };
|
||||
7A1AE6512DFB235300ECFC4D /* SwiftDataStorageService+Notes.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A1AE6502DFB235300ECFC4D /* SwiftDataStorageService+Notes.swift */; };
|
||||
7A1AE6532DFB2AD900ECFC4D /* SwiftDataStorageService+Events.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A1AE6522DFB2AD900ECFC4D /* SwiftDataStorageService+Events.swift */; };
|
||||
7A1AE6552DFB2E9D00ECFC4D /* SwiftDataStorageService+AudioRecords.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A1AE6542DFB2E9D00ECFC4D /* SwiftDataStorageService+AudioRecords.swift */; };
|
||||
7A1CF81629A42117007962DA /* Realm.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A1CF81529A42117007962DA /* Realm.swift */; };
|
||||
7A1E78F62CE900330004B740 /* ReportScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A1E78F52CE900330004B740 /* ReportScreen.swift */; };
|
||||
7A1E78F82CE900440004B740 /* ReportViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A1E78F72CE900440004B740 /* ReportViewModel.swift */; };
|
||||
@ -167,6 +171,7 @@
|
||||
7AD857262DF876C9009E4B72 /* SDVehicleNote.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7AD857252DF876C9009E4B72 /* SDVehicleNote.swift */; };
|
||||
7AD857282DF87733009E4B72 /* SDDebugInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7AD857272DF87733009E4B72 /* SDDebugInfo.swift */; };
|
||||
7AD8572A2DF87928009E4B72 /* SDAudioRecord.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7AD857292DF87928009E4B72 /* SDAudioRecord.swift */; };
|
||||
7AD8572D2DF95F72009E4B72 /* SwiftDataStorageService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7AD8572C2DF95F72009E4B72 /* SwiftDataStorageService.swift */; };
|
||||
7ADCBC572DB51739002522C0 /* AutoCatApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7ADCBC562DB51739002522C0 /* AutoCatApp.swift */; };
|
||||
7ADF6C99250F872C00F237B2 /* RoadNumbers.otf in Resources */ = {isa = PBXBuildFile; fileRef = 7ADF6C98250F872C00F237B2 /* RoadNumbers.otf */; };
|
||||
7ADF6CA12512244400F237B2 /* MapExt.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7ADF6CA02512244400F237B2 /* MapExt.swift */; };
|
||||
@ -289,6 +294,10 @@
|
||||
7A1441672C297EFD00E79018 /* NotesViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotesViewModel.swift; sourceTree = "<group>"; };
|
||||
7A15051124DB3E3000F39631 /* AnyEncodable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AnyEncodable.swift; sourceTree = "<group>"; };
|
||||
7A17AD9F2DC9F4A5002BA02A /* ScreenInput.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ScreenInput.swift; sourceTree = "<group>"; };
|
||||
7A1AE64E2DFB22EC00ECFC4D /* SwiftDataStorageService+Utils.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "SwiftDataStorageService+Utils.swift"; sourceTree = "<group>"; };
|
||||
7A1AE6502DFB235300ECFC4D /* SwiftDataStorageService+Notes.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "SwiftDataStorageService+Notes.swift"; sourceTree = "<group>"; };
|
||||
7A1AE6522DFB2AD900ECFC4D /* SwiftDataStorageService+Events.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "SwiftDataStorageService+Events.swift"; sourceTree = "<group>"; };
|
||||
7A1AE6542DFB2E9D00ECFC4D /* SwiftDataStorageService+AudioRecords.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "SwiftDataStorageService+AudioRecords.swift"; sourceTree = "<group>"; };
|
||||
7A1CF81529A42117007962DA /* Realm.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Realm.swift; sourceTree = "<group>"; };
|
||||
7A1E78F52CE900330004B740 /* ReportScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReportScreen.swift; sourceTree = "<group>"; };
|
||||
7A1E78F72CE900440004B740 /* ReportViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReportViewModel.swift; sourceTree = "<group>"; };
|
||||
@ -432,6 +441,7 @@
|
||||
7AD857252DF876C9009E4B72 /* SDVehicleNote.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SDVehicleNote.swift; sourceTree = "<group>"; };
|
||||
7AD857272DF87733009E4B72 /* SDDebugInfo.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SDDebugInfo.swift; sourceTree = "<group>"; };
|
||||
7AD857292DF87928009E4B72 /* SDAudioRecord.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SDAudioRecord.swift; sourceTree = "<group>"; };
|
||||
7AD8572C2DF95F72009E4B72 /* SwiftDataStorageService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SwiftDataStorageService.swift; sourceTree = "<group>"; };
|
||||
7ADCBC562DB51739002522C0 /* AutoCatApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AutoCatApp.swift; sourceTree = "<group>"; };
|
||||
7ADF6C98250F872C00F237B2 /* RoadNumbers.otf */ = {isa = PBXFileReference; lastKnownFileType = file; path = RoadNumbers.otf; sourceTree = "<group>"; };
|
||||
7ADF6CA02512244400F237B2 /* MapExt.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MapExt.swift; sourceTree = "<group>"; };
|
||||
@ -766,6 +776,7 @@
|
||||
7A60D24B2C5A9D2700D13F7B /* LocationService */,
|
||||
7AB5873D2C42FF4000FA7B66 /* ApiService */,
|
||||
7AB587302C42D35900FA7B66 /* StorageService */,
|
||||
7AD8572B2DF95F1E009E4B72 /* SwiftDataStorageService */,
|
||||
);
|
||||
path = Services;
|
||||
sourceTree = "<group>";
|
||||
@ -1053,6 +1064,18 @@
|
||||
path = NumberEditView;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
7AD8572B2DF95F1E009E4B72 /* SwiftDataStorageService */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
7AD8572C2DF95F72009E4B72 /* SwiftDataStorageService.swift */,
|
||||
7A1AE64E2DFB22EC00ECFC4D /* SwiftDataStorageService+Utils.swift */,
|
||||
7A1AE6502DFB235300ECFC4D /* SwiftDataStorageService+Notes.swift */,
|
||||
7A1AE6522DFB2AD900ECFC4D /* SwiftDataStorageService+Events.swift */,
|
||||
7A1AE6542DFB2E9D00ECFC4D /* SwiftDataStorageService+AudioRecords.swift */,
|
||||
);
|
||||
path = SwiftDataStorageService;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
7ADFC9552DAD026C001A43E3 /* GoogleAuthScreen */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
@ -1490,7 +1513,9 @@
|
||||
7A64A21E2C19E8D500284124 /* VehicleAdDto.swift in Sources */,
|
||||
7AD857202DF874F8009E4B72 /* SDVehicleEvent.swift in Sources */,
|
||||
7AF6D2202677C1680086EA64 /* Filter.swift in Sources */,
|
||||
7A1AE6552DFB2E9D00ECFC4D /* SwiftDataStorageService+AudioRecords.swift in Sources */,
|
||||
7A761C042677F18E0005F28F /* ApiService.swift in Sources */,
|
||||
7A1AE6532DFB2AD900ECFC4D /* SwiftDataStorageService+Events.swift in Sources */,
|
||||
7A95197B2D80B41600E69883 /* AudioRecordServiceProtocol.swift in Sources */,
|
||||
7AF6D21C2677C1680086EA64 /* DebugInfo.swift in Sources */,
|
||||
7A0818762DF83DB4000219FE /* SDVehicleName.swift in Sources */,
|
||||
@ -1502,6 +1527,7 @@
|
||||
7A599C392C18B22900D47C18 /* FbRefreshTokenModel.swift in Sources */,
|
||||
7AF6D2172677C1680086EA64 /* VehicleRegion.swift in Sources */,
|
||||
7A6F096026DBF588003A965D /* VehicleNote.swift in Sources */,
|
||||
7AD8572D2DF95F72009E4B72 /* SwiftDataStorageService.swift in Sources */,
|
||||
7AF6D21E2677C1680086EA64 /* PlateNumber.swift in Sources */,
|
||||
7A5D84C62C1AE72E00C2209B /* VehicleName.swift in Sources */,
|
||||
7AD8572A2DF87928009E4B72 /* SDAudioRecord.swift in Sources */,
|
||||
@ -1542,12 +1568,14 @@
|
||||
7AD857222DF875B2009E4B72 /* SDOsago.swift in Sources */,
|
||||
7ABDA80F2D8723F90083C715 /* StorageService+AudioRecords.swift in Sources */,
|
||||
7A64A2142C19E3B700284124 /* VehicleEngineDto.swift in Sources */,
|
||||
7A1AE64F2DFB22EC00ECFC4D /* SwiftDataStorageService+Utils.swift in Sources */,
|
||||
7A761C052677F1BC0005F28F /* CocoaError.swift in Sources */,
|
||||
7AF6D2132677C15A0086EA64 /* AudioRecord.swift in Sources */,
|
||||
7A64A21A2C19E6B300284124 /* VehicleEventDto.swift in Sources */,
|
||||
7AB587342C42D3FA00FA7B66 /* StorageService+Notes.swift in Sources */,
|
||||
7AF6D21B2677C1680086EA64 /* Vehicle.swift in Sources */,
|
||||
7AB587412C42FFE200FA7B66 /* ApiServiceProtocol.swift in Sources */,
|
||||
7A1AE6512DFB235300ECFC4D /* SwiftDataStorageService+Notes.swift in Sources */,
|
||||
7A5D84C42C1AE65C00C2209B /* VehicleBrand.swift in Sources */,
|
||||
7A599C362C18AC7F00D47C18 /* ApiError.swift in Sources */,
|
||||
7A45FB382C27073700618694 /* StorageService.swift in Sources */,
|
||||
|
||||
@ -30,6 +30,16 @@ final class SDVehicleNote {
|
||||
self.date = date
|
||||
self.text = text
|
||||
}
|
||||
|
||||
convenience init(text: String, user: String) {
|
||||
|
||||
self.init(
|
||||
id: UUID().uuidString,
|
||||
user: user,
|
||||
date: Date().timeIntervalSince1970,
|
||||
text: text
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
extension SDVehicleNote: DtoConvertible {
|
||||
|
||||
@ -0,0 +1,53 @@
|
||||
//
|
||||
// SwiftDataStorageService+AudioRecords.swift
|
||||
// AutoCatCore
|
||||
//
|
||||
// Created by Selim Mustafaev on 12.06.2025.
|
||||
// Copyright © 2025 Selim Mustafaev. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import SwiftData
|
||||
|
||||
extension SwiftDataStorageService {
|
||||
|
||||
public func add(record: AudioRecordDto) async throws {
|
||||
|
||||
context.insert(SDAudioRecord(dto: record))
|
||||
try context.save()
|
||||
}
|
||||
|
||||
public func loadRecords() async throws -> [AudioRecordDto] {
|
||||
|
||||
do {
|
||||
return try context.fetch(
|
||||
FetchDescriptor<SDAudioRecord>(
|
||||
sortBy: [SortDescriptor(\.addedDate, order: .reverse)]
|
||||
)
|
||||
)
|
||||
.map(\.dto)
|
||||
} catch {
|
||||
return []
|
||||
}
|
||||
}
|
||||
|
||||
public func deleteRecord(id: String) async throws {
|
||||
guard let record = try? fetchAudioRecord(id: id) else {
|
||||
throw StorageError.recordNotFound
|
||||
}
|
||||
|
||||
context.delete(record)
|
||||
try context.save()
|
||||
}
|
||||
|
||||
public func updateRecord(id: String, number: String) async throws -> AudioRecordDto {
|
||||
guard let record = try? fetchAudioRecord(id: id) else {
|
||||
throw StorageError.recordNotFound
|
||||
}
|
||||
|
||||
record.number = number
|
||||
try context.save()
|
||||
|
||||
return record.dto
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,57 @@
|
||||
//
|
||||
// SwiftDataStorageService+Events.swift
|
||||
// AutoCatCore
|
||||
//
|
||||
// Created by Selim Mustafaev on 12.06.2025.
|
||||
// Copyright © 2025 Selim Mustafaev. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import SwiftData
|
||||
|
||||
extension SwiftDataStorageService {
|
||||
|
||||
public func add(event: VehicleEventDto, to number: String) async throws -> VehicleDto {
|
||||
guard let vehicle = try? fetchVehicle(number: number) else {
|
||||
throw StorageError.vehicleNotFound
|
||||
}
|
||||
|
||||
vehicle.events.append(SDVehicleEvent(dto: event))
|
||||
vehicle.updatedDate = Date().timeIntervalSince1970
|
||||
try context.save()
|
||||
|
||||
return vehicle.dto
|
||||
}
|
||||
|
||||
public func remove(event id: String, from number: String) async throws -> VehicleDto {
|
||||
guard let vehicle = try? fetchVehicle(number: number) else {
|
||||
throw StorageError.vehicleNotFound
|
||||
}
|
||||
|
||||
if let index = vehicle.events.firstIndex(where: { $0.id == id }) {
|
||||
vehicle.events.remove(at: index)
|
||||
vehicle.updatedDate = Date().timeIntervalSince1970
|
||||
} else {
|
||||
throw StorageError.eventNotFound
|
||||
}
|
||||
|
||||
try context.save()
|
||||
return vehicle.dto
|
||||
}
|
||||
|
||||
public func edit(event: VehicleEventDto, for number: String) async throws -> VehicleDto {
|
||||
guard let vehicle = try? fetchVehicle(number: number) else {
|
||||
throw StorageError.vehicleNotFound
|
||||
}
|
||||
|
||||
if let index = vehicle.events.firstIndex(where: { $0.id == event.id }) {
|
||||
vehicle.events[index] = SDVehicleEvent(dto: event)
|
||||
vehicle.updatedDate = Date().timeIntervalSince1970
|
||||
try context.save()
|
||||
} else {
|
||||
throw StorageError.eventNotFound
|
||||
}
|
||||
|
||||
return vehicle.dto
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,59 @@
|
||||
//
|
||||
// SwiftDataStorageService+Notes.swift
|
||||
// AutoCatCore
|
||||
//
|
||||
// Created by Selim Mustafaev on 12.06.2025.
|
||||
// Copyright © 2025 Selim Mustafaev. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import SwiftData
|
||||
|
||||
extension SwiftDataStorageService {
|
||||
|
||||
public func addNote(text: String, to number: String) async throws -> VehicleDto {
|
||||
guard let vehicle = try? fetchVehicle(number: number) else {
|
||||
throw StorageError.vehicleNotFound
|
||||
}
|
||||
|
||||
let note = SDVehicleNote(text: text, user: settingsService.user.email)
|
||||
|
||||
vehicle.notes.append(note)
|
||||
vehicle.updatedDate = Date().timeIntervalSince1970
|
||||
try context.save()
|
||||
|
||||
return vehicle.dto
|
||||
}
|
||||
|
||||
public func deleteNote(id: String, for number: String) async throws -> VehicleDto {
|
||||
guard let vehicle = try? fetchVehicle(number: number) else {
|
||||
throw StorageError.vehicleNotFound
|
||||
}
|
||||
|
||||
guard let note = try? fetchNote(id: id) else {
|
||||
throw StorageError.noteNotFound
|
||||
}
|
||||
|
||||
vehicle.updatedDate = Date().timeIntervalSince1970
|
||||
context.delete(note)
|
||||
try context.save()
|
||||
|
||||
return vehicle.dto
|
||||
}
|
||||
|
||||
public func editNote(id: String, text: String, for number: String) async throws -> VehicleDto {
|
||||
guard let vehicle = try? fetchVehicle(number: number) else {
|
||||
throw StorageError.vehicleNotFound
|
||||
}
|
||||
|
||||
guard let note = try? fetchNote(id: id) else {
|
||||
throw StorageError.noteNotFound
|
||||
}
|
||||
|
||||
note.text = text
|
||||
vehicle.updatedDate = Date().timeIntervalSince1970
|
||||
try context.save()
|
||||
|
||||
return vehicle.dto
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,49 @@
|
||||
//
|
||||
// SwiftDataStorageService+Utils.swift
|
||||
// AutoCatCore
|
||||
//
|
||||
// Created by Selim Mustafaev on 12.06.2025.
|
||||
// Copyright © 2025 Selim Mustafaev. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import SwiftData
|
||||
|
||||
extension SwiftDataStorageService {
|
||||
|
||||
func vehicleExists(number: String) throws -> Bool {
|
||||
|
||||
let count = try context.fetchCount(
|
||||
FetchDescriptor<SDVehicle>(predicate: #Predicate { $0.number == number })
|
||||
)
|
||||
|
||||
return count > 0
|
||||
}
|
||||
|
||||
func fetchVehicle(number: String) throws -> SDVehicle? {
|
||||
|
||||
let vehicles = try context.fetch(
|
||||
FetchDescriptor<SDVehicle>(predicate: #Predicate { $0.number == number })
|
||||
)
|
||||
|
||||
return vehicles.first
|
||||
}
|
||||
|
||||
func fetchNote(id: String) throws -> SDVehicleNote? {
|
||||
|
||||
let notes = try context.fetch(
|
||||
FetchDescriptor<SDVehicleNote>(predicate: #Predicate { $0.id == id })
|
||||
)
|
||||
|
||||
return notes.first
|
||||
}
|
||||
|
||||
func fetchAudioRecord(id: String) throws -> SDAudioRecord? {
|
||||
|
||||
let records = try context.fetch(
|
||||
FetchDescriptor<SDAudioRecord>(predicate: #Predicate { $0.path == id })
|
||||
)
|
||||
|
||||
return records.first
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,93 @@
|
||||
//
|
||||
// SwiftDataStorageService.swift
|
||||
// AutoCatCore
|
||||
//
|
||||
// Created by Selim Mustafaev on 11.06.2025.
|
||||
// Copyright © 2025 Selim Mustafaev. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import SwiftData
|
||||
|
||||
@MainActor
|
||||
public final class SwiftDataStorageService: StorageServiceProtocol {
|
||||
|
||||
let settingsService: SettingsServiceProtocol
|
||||
let container: ModelContainer
|
||||
let context: ModelContext
|
||||
|
||||
public init(settingsService: SettingsServiceProtocol, isTest: Bool = false) throws {
|
||||
|
||||
self.settingsService = settingsService
|
||||
|
||||
self.container = try ModelContainer(
|
||||
for: SDVehicle.self, SDVehicleNote.self, SDAudioRecord.self,
|
||||
configurations: .init(isStoredInMemoryOnly: isTest)
|
||||
)
|
||||
|
||||
self.context = container.mainContext
|
||||
}
|
||||
|
||||
public var dbFileURL: URL? {
|
||||
|
||||
// TODO: Set specific URL in ModelConfiguration
|
||||
get async { nil }
|
||||
}
|
||||
|
||||
public func deleteAll() async throws {
|
||||
|
||||
container.deleteAllData()
|
||||
}
|
||||
|
||||
@discardableResult
|
||||
public func updateVehicle(dto: VehicleDto, policy: DbUpdatePolicy) async throws -> Bool {
|
||||
|
||||
let shouldUpdate = switch policy {
|
||||
case .always:
|
||||
true
|
||||
case .ifExists:
|
||||
try vehicleExists(number: dto.number)
|
||||
}
|
||||
|
||||
guard shouldUpdate else {
|
||||
return false
|
||||
}
|
||||
|
||||
context.insert(SDVehicle(dto: dto))
|
||||
try context.save()
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
public func loadVehicle(number: String) async throws -> VehicleDto {
|
||||
|
||||
if let vehicle = try fetchVehicle(number: number) {
|
||||
return vehicle.dto
|
||||
} else {
|
||||
throw StorageError.vehicleNotFound
|
||||
}
|
||||
}
|
||||
|
||||
public func loadVehicles() async -> [VehicleDto] {
|
||||
|
||||
do {
|
||||
return try context.fetch(
|
||||
FetchDescriptor<SDVehicle>(
|
||||
sortBy: [SortDescriptor(\.updatedDate, order: .reverse)]
|
||||
)
|
||||
)
|
||||
.map(\.shallowDto)
|
||||
} catch {
|
||||
return []
|
||||
}
|
||||
}
|
||||
|
||||
public func deleteVehicle(number: String) async throws {
|
||||
guard let vehicle = try? fetchVehicle(number: number) else {
|
||||
throw StorageError.vehicleNotFound
|
||||
}
|
||||
|
||||
context.delete(vehicle)
|
||||
try context.save()
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user