Implementing SwiftData storage service

This commit is contained in:
Selim Mustafaev 2025-06-12 19:09:48 +03:00
parent df15a154be
commit e91fd89eff
7 changed files with 349 additions and 0 deletions

View File

@ -28,6 +28,10 @@
7A1441662C297EDE00E79018 /* NotesScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A1441652C297EDE00E79018 /* NotesScreen.swift */; }; 7A1441662C297EDE00E79018 /* NotesScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A1441652C297EDE00E79018 /* NotesScreen.swift */; };
7A1441682C297EFD00E79018 /* NotesViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A1441672C297EFD00E79018 /* NotesViewModel.swift */; }; 7A1441682C297EFD00E79018 /* NotesViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A1441672C297EFD00E79018 /* NotesViewModel.swift */; };
7A17ADA02DC9F4A5002BA02A /* ScreenInput.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A17AD9F2DC9F4A5002BA02A /* ScreenInput.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 */; }; 7A1CF81629A42117007962DA /* Realm.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A1CF81529A42117007962DA /* Realm.swift */; };
7A1E78F62CE900330004B740 /* ReportScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A1E78F52CE900330004B740 /* ReportScreen.swift */; }; 7A1E78F62CE900330004B740 /* ReportScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A1E78F52CE900330004B740 /* ReportScreen.swift */; };
7A1E78F82CE900440004B740 /* ReportViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A1E78F72CE900440004B740 /* ReportViewModel.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 */; }; 7AD857262DF876C9009E4B72 /* SDVehicleNote.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7AD857252DF876C9009E4B72 /* SDVehicleNote.swift */; };
7AD857282DF87733009E4B72 /* SDDebugInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7AD857272DF87733009E4B72 /* SDDebugInfo.swift */; }; 7AD857282DF87733009E4B72 /* SDDebugInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7AD857272DF87733009E4B72 /* SDDebugInfo.swift */; };
7AD8572A2DF87928009E4B72 /* SDAudioRecord.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7AD857292DF87928009E4B72 /* SDAudioRecord.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 */; }; 7ADCBC572DB51739002522C0 /* AutoCatApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7ADCBC562DB51739002522C0 /* AutoCatApp.swift */; };
7ADF6C99250F872C00F237B2 /* RoadNumbers.otf in Resources */ = {isa = PBXBuildFile; fileRef = 7ADF6C98250F872C00F237B2 /* RoadNumbers.otf */; }; 7ADF6C99250F872C00F237B2 /* RoadNumbers.otf in Resources */ = {isa = PBXBuildFile; fileRef = 7ADF6C98250F872C00F237B2 /* RoadNumbers.otf */; };
7ADF6CA12512244400F237B2 /* MapExt.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7ADF6CA02512244400F237B2 /* MapExt.swift */; }; 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>"; }; 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>"; }; 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>"; }; 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>"; }; 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>"; }; 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>"; }; 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>"; }; 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>"; }; 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>"; }; 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>"; }; 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>"; }; 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>"; }; 7ADF6CA02512244400F237B2 /* MapExt.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MapExt.swift; sourceTree = "<group>"; };
@ -766,6 +776,7 @@
7A60D24B2C5A9D2700D13F7B /* LocationService */, 7A60D24B2C5A9D2700D13F7B /* LocationService */,
7AB5873D2C42FF4000FA7B66 /* ApiService */, 7AB5873D2C42FF4000FA7B66 /* ApiService */,
7AB587302C42D35900FA7B66 /* StorageService */, 7AB587302C42D35900FA7B66 /* StorageService */,
7AD8572B2DF95F1E009E4B72 /* SwiftDataStorageService */,
); );
path = Services; path = Services;
sourceTree = "<group>"; sourceTree = "<group>";
@ -1053,6 +1064,18 @@
path = NumberEditView; path = NumberEditView;
sourceTree = "<group>"; 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 */ = { 7ADFC9552DAD026C001A43E3 /* GoogleAuthScreen */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
@ -1490,7 +1513,9 @@
7A64A21E2C19E8D500284124 /* VehicleAdDto.swift in Sources */, 7A64A21E2C19E8D500284124 /* VehicleAdDto.swift in Sources */,
7AD857202DF874F8009E4B72 /* SDVehicleEvent.swift in Sources */, 7AD857202DF874F8009E4B72 /* SDVehicleEvent.swift in Sources */,
7AF6D2202677C1680086EA64 /* Filter.swift in Sources */, 7AF6D2202677C1680086EA64 /* Filter.swift in Sources */,
7A1AE6552DFB2E9D00ECFC4D /* SwiftDataStorageService+AudioRecords.swift in Sources */,
7A761C042677F18E0005F28F /* ApiService.swift in Sources */, 7A761C042677F18E0005F28F /* ApiService.swift in Sources */,
7A1AE6532DFB2AD900ECFC4D /* SwiftDataStorageService+Events.swift in Sources */,
7A95197B2D80B41600E69883 /* AudioRecordServiceProtocol.swift in Sources */, 7A95197B2D80B41600E69883 /* AudioRecordServiceProtocol.swift in Sources */,
7AF6D21C2677C1680086EA64 /* DebugInfo.swift in Sources */, 7AF6D21C2677C1680086EA64 /* DebugInfo.swift in Sources */,
7A0818762DF83DB4000219FE /* SDVehicleName.swift in Sources */, 7A0818762DF83DB4000219FE /* SDVehicleName.swift in Sources */,
@ -1502,6 +1527,7 @@
7A599C392C18B22900D47C18 /* FbRefreshTokenModel.swift in Sources */, 7A599C392C18B22900D47C18 /* FbRefreshTokenModel.swift in Sources */,
7AF6D2172677C1680086EA64 /* VehicleRegion.swift in Sources */, 7AF6D2172677C1680086EA64 /* VehicleRegion.swift in Sources */,
7A6F096026DBF588003A965D /* VehicleNote.swift in Sources */, 7A6F096026DBF588003A965D /* VehicleNote.swift in Sources */,
7AD8572D2DF95F72009E4B72 /* SwiftDataStorageService.swift in Sources */,
7AF6D21E2677C1680086EA64 /* PlateNumber.swift in Sources */, 7AF6D21E2677C1680086EA64 /* PlateNumber.swift in Sources */,
7A5D84C62C1AE72E00C2209B /* VehicleName.swift in Sources */, 7A5D84C62C1AE72E00C2209B /* VehicleName.swift in Sources */,
7AD8572A2DF87928009E4B72 /* SDAudioRecord.swift in Sources */, 7AD8572A2DF87928009E4B72 /* SDAudioRecord.swift in Sources */,
@ -1542,12 +1568,14 @@
7AD857222DF875B2009E4B72 /* SDOsago.swift in Sources */, 7AD857222DF875B2009E4B72 /* SDOsago.swift in Sources */,
7ABDA80F2D8723F90083C715 /* StorageService+AudioRecords.swift in Sources */, 7ABDA80F2D8723F90083C715 /* StorageService+AudioRecords.swift in Sources */,
7A64A2142C19E3B700284124 /* VehicleEngineDto.swift in Sources */, 7A64A2142C19E3B700284124 /* VehicleEngineDto.swift in Sources */,
7A1AE64F2DFB22EC00ECFC4D /* SwiftDataStorageService+Utils.swift in Sources */,
7A761C052677F1BC0005F28F /* CocoaError.swift in Sources */, 7A761C052677F1BC0005F28F /* CocoaError.swift in Sources */,
7AF6D2132677C15A0086EA64 /* AudioRecord.swift in Sources */, 7AF6D2132677C15A0086EA64 /* AudioRecord.swift in Sources */,
7A64A21A2C19E6B300284124 /* VehicleEventDto.swift in Sources */, 7A64A21A2C19E6B300284124 /* VehicleEventDto.swift in Sources */,
7AB587342C42D3FA00FA7B66 /* StorageService+Notes.swift in Sources */, 7AB587342C42D3FA00FA7B66 /* StorageService+Notes.swift in Sources */,
7AF6D21B2677C1680086EA64 /* Vehicle.swift in Sources */, 7AF6D21B2677C1680086EA64 /* Vehicle.swift in Sources */,
7AB587412C42FFE200FA7B66 /* ApiServiceProtocol.swift in Sources */, 7AB587412C42FFE200FA7B66 /* ApiServiceProtocol.swift in Sources */,
7A1AE6512DFB235300ECFC4D /* SwiftDataStorageService+Notes.swift in Sources */,
7A5D84C42C1AE65C00C2209B /* VehicleBrand.swift in Sources */, 7A5D84C42C1AE65C00C2209B /* VehicleBrand.swift in Sources */,
7A599C362C18AC7F00D47C18 /* ApiError.swift in Sources */, 7A599C362C18AC7F00D47C18 /* ApiError.swift in Sources */,
7A45FB382C27073700618694 /* StorageService.swift in Sources */, 7A45FB382C27073700618694 /* StorageService.swift in Sources */,

View File

@ -30,6 +30,16 @@ final class SDVehicleNote {
self.date = date self.date = date
self.text = text self.text = text
} }
convenience init(text: String, user: String) {
self.init(
id: UUID().uuidString,
user: user,
date: Date().timeIntervalSince1970,
text: text
)
}
} }
extension SDVehicleNote: DtoConvertible { extension SDVehicleNote: DtoConvertible {

View File

@ -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
}
}

View File

@ -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
}
}

View File

@ -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
}
}

View File

@ -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
}
}

View File

@ -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()
}
}