-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
c246212
commit 3528f8c
Showing
12 changed files
with
852 additions
and
40 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,70 @@ | ||
// | ||
// Copyright © 2024 Alexander Romanov | ||
// FileCache.swift, created on 28.11.2024 | ||
// | ||
|
||
import Foundation | ||
import OversizeCore | ||
|
||
public final class СacheService { | ||
private let cacheDirectory: URL | ||
private let cacheExpiration: TimeInterval | ||
|
||
public init(expiration: TimeInterval = 300) { // Default expiration: 5 minutes | ||
let path = FileManager.default.urls(for: .cachesDirectory, in: .userDomainMask).first | ||
cacheDirectory = path ?? FileManager.default.temporaryDirectory.appendingPathComponent("Default") | ||
cacheExpiration = expiration | ||
} | ||
|
||
private func cacheFilePath(for key: String) -> URL { | ||
cacheDirectory.appendingPathComponent(key) | ||
} | ||
|
||
func save(_ data: some Encodable, for key: String = #function) { | ||
let fileURL = cacheFilePath(for: key) | ||
do { | ||
let jsonData = try JSONEncoder().encode(data) | ||
try jsonData.write(to: fileURL, options: .atomic) | ||
logData("Saved cache: \(key)") | ||
} catch { | ||
logError("Failed to save cache for key \(key): \(error)") | ||
} | ||
} | ||
|
||
func load<T: Decodable>(for key: String = #function, as _: T.Type) -> T? { | ||
let fileURL = cacheFilePath(for: key) | ||
|
||
guard FileManager.default.fileExists(atPath: fileURL.path), | ||
isCacheValid(for: fileURL) | ||
else { | ||
return nil | ||
} | ||
|
||
do { | ||
let data = try Data(contentsOf: fileURL) | ||
logNotice("Readed cache: \(key)") | ||
return try JSONDecoder().decode(T.self, from: data) | ||
} catch { | ||
logError("Failed to load cache for key \(key): \(error)") | ||
return nil | ||
} | ||
} | ||
|
||
func remove(for key: String = #function) { | ||
let fileURL = cacheFilePath(for: key) | ||
try? FileManager.default.removeItem(at: fileURL) | ||
} | ||
|
||
func clearAll() { | ||
try? FileManager.default.removeItem(at: cacheDirectory) | ||
} | ||
|
||
private func isCacheValid(for fileURL: URL) -> Bool { | ||
guard let attributes = try? FileManager.default.attributesOfItem(atPath: fileURL.path), | ||
let modificationDate = attributes[.modificationDate] as? Date | ||
else { | ||
return false | ||
} | ||
return Date().timeIntervalSince(modificationDate) < cacheExpiration | ||
} | ||
} |
32 changes: 32 additions & 0 deletions
32
Sources/OversizeAppStoreServices/Models/AnalyticsReport.swift
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,32 @@ | ||
// | ||
// Copyright © 2024 Alexander Romanov | ||
// AnalyticsReport.swift, created on 25.11.2024 | ||
// | ||
|
||
import AppStoreAPI | ||
import AppStoreConnect | ||
import OversizeCore | ||
|
||
public struct AnalyticsReport: Identifiable, Hashable, Sendable { | ||
public let id: String | ||
public var name: String | ||
public var category: Category | ||
|
||
init?(schema: AppStoreAPI.AnalyticsReport) { | ||
guard let categoryRawValue = schema.attributes?.category?.rawValue, | ||
let category: Category = .init(rawValue: categoryRawValue), | ||
let name: String = schema.attributes?.name | ||
else { return nil } | ||
id = schema.id | ||
self.category = category | ||
self.name = name | ||
} | ||
|
||
public enum Category: String, CaseIterable, Codable, Sendable { | ||
case appUsage = "APP_USAGE" | ||
case appStoreEngagement = "APP_STORE_ENGAGEMENT" | ||
case commerce = "COMMERCE" | ||
case frameworkUsage = "FRAMEWORK_USAGE" | ||
case performance = "PERFORMANCE" | ||
} | ||
} |
40 changes: 40 additions & 0 deletions
40
Sources/OversizeAppStoreServices/Models/AnalyticsReportRequest.swift
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,40 @@ | ||
// | ||
// Copyright © 2024 Alexander Romanov | ||
// AnalyticsReportRequest.swift, created on 25.11.2024 | ||
// | ||
|
||
import AppStoreAPI | ||
import AppStoreConnect | ||
import Foundation | ||
import OversizeCore | ||
|
||
public struct AnalyticsReportRequest: Identifiable, Sendable { | ||
public let id: String | ||
public var accessType: AccessType? | ||
public var isStoppedDueToInactivity: Bool? | ||
|
||
public let included: Included? | ||
|
||
init?(schema: AppStoreAPI.AnalyticsReportRequest, included: [AppStoreAPI.AnalyticsReport]? = nil) { | ||
guard let accessTypeRawValue = schema.attributes?.accessType?.rawValue, | ||
let accessType: AccessType = .init(rawValue: accessTypeRawValue) else { return nil } | ||
id = schema.id | ||
self.accessType = accessType | ||
isStoppedDueToInactivity = schema.attributes?.isStoppedDueToInactivity | ||
|
||
if let analyticsReport = included?.filter { $0.id == schema.relationships?.reports?.data?.first?.id } { | ||
self.included = .init(analyticsReports: analyticsReport.compactMap { .init(schema: $0) }) | ||
} else { | ||
self.included = nil | ||
} | ||
} | ||
|
||
public enum AccessType: String, CaseIterable, Codable, Sendable { | ||
case oneTimeSnapshot = "ONE_TIME_SNAPSHOT" | ||
case ongoing = "ONGOING" | ||
} | ||
|
||
public struct Included: Sendable { | ||
public let analyticsReports: [AnalyticsReport]? | ||
} | ||
} |
152 changes: 152 additions & 0 deletions
152
Sources/OversizeAppStoreServices/Models/FinanceReport.swift
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,152 @@ | ||
import CodableCSV | ||
import Foundation | ||
import OversizeCore | ||
|
||
public struct FinanceReports: Sendable { | ||
public let reports: [Report] | ||
public let totalRows: Int? | ||
public let totalAmount: Double? | ||
public let totalUnits: Int? | ||
|
||
public struct Report: Sendable { | ||
public let startDate: String | ||
public let endDate: String | ||
public let upc: String? | ||
public let isrcIsbn: String? | ||
public let vendorIdentifier: String | ||
public let quantity: String? | ||
public let partnerShare: String? | ||
public let extendedPartnerShare: String? | ||
public let partnerShareCurrency: String? | ||
public let salesOrReturn: String? | ||
public let appleIdentifier: String? | ||
public let artistShowDeveloperAuthor: String? | ||
public let title: String? | ||
public let labelStudioNetworkPublisher: String? | ||
public let grid: String? | ||
public let productTypeIdentifier: String? | ||
public let isanOtherIdentifier: String? | ||
public let countryOfSale: String? | ||
public let preOrderFlag: String? | ||
public let promoCode: String? | ||
public let customerPrice: String | ||
public let customerCurrency: String? | ||
|
||
public init( | ||
startDate: String, | ||
endDate: String, | ||
upc: String? = nil, | ||
isrcIsbn: String? = nil, | ||
vendorIdentifier: String, | ||
quantity: String? = nil, | ||
partnerShare: String? = nil, | ||
extendedPartnerShare: String? = nil, | ||
partnerShareCurrency: String? = nil, | ||
salesOrReturn: String? = nil, | ||
appleIdentifier: String? = nil, | ||
artistShowDeveloperAuthor: String? = nil, | ||
title: String? = nil, | ||
labelStudioNetworkPublisher: String? = nil, | ||
grid: String? = nil, | ||
productTypeIdentifier: String? = nil, | ||
isanOtherIdentifier: String? = nil, | ||
countryOfSale: String? = nil, | ||
preOrderFlag: String? = nil, | ||
promoCode: String? = nil, | ||
customerPrice: String, | ||
customerCurrency: String? = nil | ||
) { | ||
self.startDate = startDate | ||
self.endDate = endDate | ||
self.upc = upc | ||
self.isrcIsbn = isrcIsbn | ||
self.vendorIdentifier = vendorIdentifier | ||
self.quantity = quantity | ||
self.partnerShare = partnerShare | ||
self.extendedPartnerShare = extendedPartnerShare | ||
self.partnerShareCurrency = partnerShareCurrency | ||
self.salesOrReturn = salesOrReturn | ||
self.appleIdentifier = appleIdentifier | ||
self.artistShowDeveloperAuthor = artistShowDeveloperAuthor | ||
self.title = title | ||
self.labelStudioNetworkPublisher = labelStudioNetworkPublisher | ||
self.grid = grid | ||
self.productTypeIdentifier = productTypeIdentifier | ||
self.isanOtherIdentifier = isanOtherIdentifier | ||
self.countryOfSale = countryOfSale | ||
self.preOrderFlag = preOrderFlag | ||
self.promoCode = promoCode | ||
self.customerPrice = customerPrice | ||
self.customerCurrency = customerCurrency | ||
} | ||
} | ||
|
||
init?(data: Data) { | ||
guard let csvString = String(data: data, encoding: .utf8) else { return nil } | ||
|
||
do { | ||
let unwantedKeywords = ["Total_Rows", "Total_Amount", "Total_Units"] | ||
|
||
let lines = csvString.components(separatedBy: "\n") | ||
|
||
var totalRows: Int? = nil | ||
var totalAmount: Double? = nil | ||
var totalUnits: Int? = nil | ||
|
||
var filteredLines: [String] = [] | ||
for line in lines { | ||
if line.contains("Total_Rows") { | ||
totalRows = Int(line.split(separator: "\t")[1].trimmingCharacters(in: .whitespacesAndNewlines)) | ||
} else if line.contains("Total_Amount") { | ||
totalAmount = Double(line.split(separator: "\t")[1].trimmingCharacters(in: .whitespacesAndNewlines)) | ||
} else if line.contains("Total_Units") { | ||
totalUnits = Int(line.split(separator: "\t")[1].trimmingCharacters(in: .whitespacesAndNewlines)) | ||
} else { | ||
filteredLines.append(line) | ||
} | ||
} | ||
|
||
let filteredCSV = filteredLines.joined(separator: "\n") | ||
|
||
let reader = try CSVReader(input: filteredCSV) { | ||
$0.headerStrategy = .firstLine | ||
$0.delimiters.field = "\t" | ||
$0.presample = true | ||
} | ||
|
||
reports = reader.compactMap { | ||
.init( | ||
startDate: $0.element(0).valueOrEmpty, | ||
endDate: $0.element(1).valueOrEmpty, | ||
upc: $0.element(2), | ||
isrcIsbn: $0.element(3), | ||
vendorIdentifier: $0.element(4).valueOrEmpty, | ||
quantity: $0.element(5), | ||
partnerShare: $0.element(6), | ||
extendedPartnerShare: $0.element(7), | ||
partnerShareCurrency: $0.element(8), | ||
salesOrReturn: $0.element(9), | ||
appleIdentifier: $0.element(10), | ||
artistShowDeveloperAuthor: $0.element(11), | ||
title: $0.element(12), | ||
labelStudioNetworkPublisher: $0.element(13), | ||
grid: $0.element(14), | ||
productTypeIdentifier: $0.element(15), | ||
isanOtherIdentifier: $0.element(16), | ||
countryOfSale: $0.element(17), | ||
preOrderFlag: $0.element(18), | ||
promoCode: $0.element(19), | ||
customerPrice: $0.element(20).valueOrEmpty, | ||
customerCurrency: $0.element(21) | ||
) | ||
} | ||
|
||
self.totalRows = totalRows | ||
self.totalAmount = totalAmount | ||
self.totalUnits = totalUnits | ||
|
||
} catch { | ||
return nil | ||
} | ||
} | ||
} |
Oops, something went wrong.