diff --git a/Sources/OversizeAppStoreServices/Auth/EnvAuthenticator.swift b/Sources/OversizeAppStoreServices/Auth/EnvAuthenticator.swift index 8a02f56..2d58de5 100644 --- a/Sources/OversizeAppStoreServices/Auth/EnvAuthenticator.swift +++ b/Sources/OversizeAppStoreServices/Auth/EnvAuthenticator.swift @@ -6,6 +6,7 @@ import AppStoreConnect import Factory import Foundation +import OversizeCore import OversizeServices public struct EnvAuthenticator: Authenticator { @@ -18,14 +19,17 @@ public struct EnvAuthenticator: Authenticator { ) throws { guard let keyLabel = UserDefaults.standard.string(forKey: "AppStore.Account") else { + logError("Get UserDefaults value for 'AppStore.Account'") throw Error.missingEnvironmentVariable("AppStore.Account") } guard let appStoreIssuerID = storage.getPassword(for: "AppConnector-IssuerID-" + keyLabel) else { + logError("Get Keychain value for IssuerID") throw Error.missingEnvironmentVariable("AppStore.Key.Default") } guard let appStoreCertificate = storage.getCredentials(with: "AppConnector-Certificate-" + keyLabel) else { + logError("Get Keychain value for Certificate") throw Error.missingEnvironmentVariable("AppStore.Key.Default") } diff --git "a/Sources/OversizeAppStoreServices/Cache/\320\241acheService.swift" "b/Sources/OversizeAppStoreServices/Cache/\320\241acheService.swift" index 6675352..315c4a8 100644 --- "a/Sources/OversizeAppStoreServices/Cache/\320\241acheService.swift" +++ "b/Sources/OversizeAppStoreServices/Cache/\320\241acheService.swift" @@ -54,7 +54,17 @@ public actor CacheService { } public func clearAll() async { - try? FileManager.default.removeItem(at: cacheDirectory) + do { + if FileManager.default.fileExists(atPath: cacheDirectory.path) { + try FileManager.default.removeItem(at: cacheDirectory) + } + + try FileManager.default.createDirectory(at: cacheDirectory, withIntermediateDirectories: true) + + logData("Cache cleared successfully") + } catch { + logError("Failed to clear cache: \(error)") + } } private func isCacheValid(for fileURL: URL) async -> Bool { diff --git a/Sources/OversizeAppStoreServices/Models/App.swift b/Sources/OversizeAppStoreServices/Models/App.swift index 69e6015..a30f0d6 100644 --- a/Sources/OversizeAppStoreServices/Models/App.swift +++ b/Sources/OversizeAppStoreServices/Models/App.swift @@ -21,6 +21,10 @@ public struct App: Identifiable, Sendable { public var included: Included? + public init?(response: AppStoreAPI.AppResponse) { + self.init(schema: response.data, included: response.included) + } + public init?(schema: AppStoreAPI.App) { guard let attributes = schema.attributes, let bundleID = attributes.bundleID, @@ -212,3 +216,23 @@ public extension App { name.components(separatedBy: [".", "-", "—"]).last ?? "" } } + +extension App { + static func from(response: AppStoreAPI.AppsResponse) -> [App] { + response.data.compactMap { schema in + let filteredIncluded = response.included?.filter { includedItem in + switch includedItem { + case let .appStoreVersion(appStoreVersion): + schema.relationships?.appStoreVersions?.data?.contains(where: { $0.id == appStoreVersion.id }) ?? false + case let .build(build): + schema.relationships?.builds?.data?.contains(where: { $0.id == build.id }) ?? false + case let .prereleaseVersion(prereleaseVersion): + schema.relationships?.preReleaseVersions?.data?.contains(where: { $0.id == prereleaseVersion.id }) ?? false + default: + false + } + } + return App(schema: schema, included: filteredIncluded) + } + } +} diff --git a/Sources/OversizeAppStoreServices/Models/SubscriptionLocalization.swift b/Sources/OversizeAppStoreServices/Models/SubscriptionLocalization.swift index 4c076db..85c0388 100644 --- a/Sources/OversizeAppStoreServices/Models/SubscriptionLocalization.swift +++ b/Sources/OversizeAppStoreServices/Models/SubscriptionLocalization.swift @@ -47,8 +47,7 @@ public struct SubscriptionLocalization: Codable, Equatable, Identifiable, Sendab // Computed property for status color public var statusColor: Color { switch self { - case .prepareForSubmission: .gray - case .waitingForReview: .yellow + case .waitingForReview, .prepareForSubmission: .yellow case .approved: .green case .rejected: .red } diff --git a/Sources/OversizeAppStoreServices/Services/AppsService.swift b/Sources/OversizeAppStoreServices/Services/AppsService.swift index 5497605..1edf570 100644 --- a/Sources/OversizeAppStoreServices/Services/AppsService.swift +++ b/Sources/OversizeAppStoreServices/Services/AppsService.swift @@ -26,10 +26,9 @@ public actor AppsService { guard let client else { return .failure(.network(type: .unauthorized)) } return await cacheService.fetchWithCache(key: "fetchApp\(id)", force: force) { let request = Resources.v1.apps.id(id).get() - let response = try await client.send(request) - return response.data - }.flatMap { data in - guard let app = App(schema: data) else { + return try await client.send(request) + }.flatMap { + guard let app = App(response: $0) else { return .failure(.network(type: .decode)) } return .success(app) @@ -38,7 +37,7 @@ public actor AppsService { public func fetchAppIncludeBuildsAndAppStoreVersions(id: String, force: Bool = false) async -> Result { guard let client else { return .failure(.network(type: .unauthorized)) } - return await cacheService.fetchWithCache(key: "fetchApp\(id)", force: force) { + return await cacheService.fetchWithCache(key: "fetchAppIncludeBuildsAndAppStoreVersions\(id)", force: force) { let request = Resources.v1.apps.id(id).get( include: [ .builds, @@ -47,7 +46,7 @@ public actor AppsService { ) return try await client.send(request) }.flatMap { - guard let app = App(schema: $0.data, included: $0.included) else { + guard let app = App(response: $0) else { return .failure(.network(type: .decode)) } return .success(app) @@ -80,39 +79,8 @@ public actor AppsService { guard let client else { return .failure(.network(type: .unauthorized)) } let request = Resources.v1.apps.get() do { - let data = try await client.send(request).data - let apps: [App] = data.compactMap { .init(schema: $0) } - return .success(apps) - } catch { - return .failure(.network(type: .noResponse)) - } - } - - public func fetchAppsIncludeAppStoreVersionsAndPreReleaseVersions() async -> Result<[App], AppError> { - guard let client else { - return .failure(.network(type: .unauthorized)) - } - let request = Resources.v1.apps.get( - include: [ - .appStoreVersions, - .preReleaseVersions, - ] - ) - do { - let result = try await client.send(request) - let apps: [App] = result.data.compactMap { schema in - let filteredIncluded = result.included?.filter { includedItem in - switch includedItem { - case let .appStoreVersion(appStoreVersion): - schema.relationships?.appStoreVersions?.data?.first(where: { $0.id == appStoreVersion.id }) != nil - case let .prereleaseVersion(prereleaseVersion): - schema.relationships?.preReleaseVersions?.data?.first(where: { $0.id == prereleaseVersion.id }) != nil - default: - false - } - } - return App(schema: schema, included: filteredIncluded) - } + let response = try await client.send(request) + let apps: [App] = App.from(response: response) return .success(apps) } catch { return .failure(.network(type: .noResponse)) @@ -130,60 +98,42 @@ public actor AppsService { ] ) return try await client.send(request) - }.flatMap { - .success(processAppsResponse($0)) - } + }.map { App.from(response: $0) } } - public func fetchAppsIncludeActualAppStoreVersionsAndBuilds(limitAppStoreVersions: Int? = nil) async -> Result<[App], AppError> { + public func fetchAppsIncludeActualAppStoreVersionsAndBuilds(limitAppStoreVersions: Int? = nil, forse: Bool = false) async -> Result<[App], AppError> { guard let client else { return .failure(.network(type: .unauthorized)) } - let request = Resources.v1.apps.get( - filterAppStoreVersionsAppStoreState: [ - .accepted, - .developerRemovedFromSale, - .developerRejected, - .inReview, - .invalidBinary, - .metadataRejected, - .pendingAppleRelease, - .pendingContract, - .pendingDeveloperRelease, - .prepareForSubmission, - .preorderReadyForSale, - .processingForAppStore, - .readyForReview, - .readyForSale, - .rejected, - .removedFromSale, - .waitingForExportCompliance, - .waitingForReview, - .notApplicable, - ], - include: [ - .builds, - .appStoreVersions, - ], - limitAppStoreVersions: limitAppStoreVersions - ) - do { - let result = try await client.send(request) - let apps: [App] = result.data.compactMap { schema in - let filteredIncluded = result.included?.filter { includedItem in - switch includedItem { - case let .appStoreVersion(appStoreVersion): - schema.relationships?.appStoreVersions?.data?.first(where: { $0.id == appStoreVersion.id }) != nil - case let .build(build): - schema.relationships?.builds?.data?.first(where: { $0.id == build.id }) != nil - default: - false - } - } - return App(schema: schema, included: filteredIncluded) - } - return .success(apps) - } catch { - return .failure(.network(type: .noResponse)) - } + return await cacheService.fetchWithCache(key: "fetchAppsIncludeActualAppStoreVersionsAndBuilds", force: forse) { + let request = Resources.v1.apps.get( + filterAppStoreVersionsAppStoreState: [ + .accepted, + .developerRemovedFromSale, + .developerRejected, + .inReview, + .invalidBinary, + .metadataRejected, + .pendingAppleRelease, + .pendingContract, + .pendingDeveloperRelease, + .prepareForSubmission, + .preorderReadyForSale, + .processingForAppStore, + .readyForReview, + .readyForSale, + .rejected, + .removedFromSale, + .waitingForExportCompliance, + .waitingForReview, + .notApplicable, + ], + include: [ + .builds, + .appStoreVersions, + ], + limitAppStoreVersions: limitAppStoreVersions + ) + return try await client.send(request) + }.map { App.from(response: $0) } } func postBundleId( @@ -240,24 +190,6 @@ public actor AppsService { } private extension AppsService { - func processAppsResponse(_ response: AppsResponse) -> [App] { - response.data.compactMap { schema in - let filteredIncluded = response.included?.filter { includedItem in - switch includedItem { - case let .appStoreVersion(appStoreVersion): - schema.relationships?.appStoreVersions?.data?.contains(where: { $0.id == appStoreVersion.id }) ?? false - case let .build(build): - schema.relationships?.builds?.data?.contains(where: { $0.id == build.id }) ?? false - case let .prereleaseVersion(prereleaseVersion): - schema.relationships?.preReleaseVersions?.data?.contains(where: { $0.id == prereleaseVersion.id }) ?? false - default: - false - } - } - return App(schema: schema, included: filteredIncluded) - } - } - func handleRequestFailure(error: Error, replaces: [String: String] = [:]) -> Result { if let responseError = error as? ResponseError { switch responseError { diff --git a/Sources/OversizeAppStoreServices/Services/SubscribtionsService.swift b/Sources/OversizeAppStoreServices/Services/SubscribtionsService.swift index 64a8e68..c21e84e 100644 --- a/Sources/OversizeAppStoreServices/Services/SubscribtionsService.swift +++ b/Sources/OversizeAppStoreServices/Services/SubscribtionsService.swift @@ -577,6 +577,17 @@ public actor SubscriptionsService { return handleRequestFailure(error: error, replaces: [:]) } } + + public func deleteSubscription(subscriptionsId: String) async -> Result { + guard let client else { return .failure(.network(type: .unauthorized)) } + let request = Resources.v1.subscriptions.id(subscriptionsId).delete + do { + let _ = try await client.send(request) + return .success(true) + } catch { + return .failure(.network(type: .noResponse)) + } + } } extension SubscriptionsService { diff --git a/Sources/OversizeAppStoreServices/Services/VersionsService.swift b/Sources/OversizeAppStoreServices/Services/VersionsService.swift index 814ff38..beb254e 100644 --- a/Sources/OversizeAppStoreServices/Services/VersionsService.swift +++ b/Sources/OversizeAppStoreServices/Services/VersionsService.swift @@ -35,14 +35,19 @@ public actor VersionsService { } } - public func fetchAppVersions(appId: String) async -> Result<[AppStoreVersion], AppError> { + public func fetchAppVersions(appId: String, platform: Platform? = nil, force: Bool = false) async -> Result<[AppStoreVersion], AppError> { guard let client else { return .failure(.network(type: .unauthorized)) } - let request = Resources.v1.apps.id(appId).appStoreVersions.get() - do { - let data = try await client.send(request).data - return .success(data.compactMap { .init(schema: $0) }) - } catch { - return .failure(.network(type: .noResponse)) + let filterPlatforms: [Resources.V1.Apps.WithID.AppStoreVersions.FilterPlatform]? = if let platform, let filteredPlatform: Resources.V1.Apps.WithID.AppStoreVersions.FilterPlatform = .init(rawValue: platform.rawValue) { + [filteredPlatform] + } else { + nil + } + return await cacheService.fetchWithCache(key: "fetchAppVersions\(appId)\(platform?.rawValue ?? "")", force: force) { + let request = Resources.v1.apps.id(appId).appStoreVersions.get(filterPlatform: filterPlatforms) + let response = try await client.send(request) + return response.data + }.map { data in + data.compactMap { .init(schema: $0) } } }