diff --git a/DemoApp/REES46.xcodeproj/project.pbxproj b/DemoApp/REES46.xcodeproj/project.pbxproj index d1849c64..1545dea3 100644 --- a/DemoApp/REES46.xcodeproj/project.pbxproj +++ b/DemoApp/REES46.xcodeproj/project.pbxproj @@ -21,6 +21,7 @@ A1B2C3D40000000000000001 /* GetLastOrderProductsTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = A1B2C3D40000000000000002 /* GetLastOrderProductsTest.swift */; }; A1B2C3D40000000000000003 /* GetUserOrdersTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = A1B2C3D40000000000000004 /* GetUserOrdersTest.swift */; }; A1B2C3D40000000000000005 /* LoyaltyTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = A1B2C3D40000000000000006 /* LoyaltyTests.swift */; }; + A1B2C3D40000000000000007 /* CatalogReadTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = A1B2C3D40000000000000008 /* CatalogReadTests.swift */; }; 75E237932C334AD200119197 /* DeviceIdTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 75E237912C334AD200119197 /* DeviceIdTest.swift */; }; D1C0FFEF2F0A1B2C3D4E5F60 /* TrackEventCustomFieldsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D1C0FFEE2F0A1B2C3D4E5F60 /* TrackEventCustomFieldsTests.swift */; }; 9A7A95DF25BC40E900FFA40A /* UserNotifications.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9ADC31DE25A1C6F700D9D1AA /* UserNotifications.framework */; }; @@ -113,6 +114,7 @@ A1B2C3D40000000000000002 /* GetLastOrderProductsTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GetLastOrderProductsTest.swift; sourceTree = ""; }; A1B2C3D40000000000000004 /* GetUserOrdersTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GetUserOrdersTest.swift; sourceTree = ""; }; A1B2C3D40000000000000006 /* LoyaltyTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoyaltyTests.swift; sourceTree = ""; }; + A1B2C3D40000000000000008 /* CatalogReadTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CatalogReadTests.swift; sourceTree = ""; }; 75E237912C334AD200119197 /* DeviceIdTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeviceIdTest.swift; sourceTree = ""; }; D1C0FFEE2F0A1B2C3D4E5F60 /* TrackEventCustomFieldsTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TrackEventCustomFieldsTests.swift; sourceTree = ""; }; 9A58AAB128E80C33004EA84C /* REESNotificationServiceExtension.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = REESNotificationServiceExtension.entitlements; sourceTree = ""; }; @@ -323,6 +325,7 @@ A1B2C3D40000000000000002 /* GetLastOrderProductsTest.swift */, A1B2C3D40000000000000004 /* GetUserOrdersTest.swift */, A1B2C3D40000000000000006 /* LoyaltyTests.swift */, + A1B2C3D40000000000000008 /* CatalogReadTests.swift */, D1C0FFEE2F0A1B2C3D4E5F60 /* TrackEventCustomFieldsTests.swift */, ); name = sdk; @@ -694,6 +697,7 @@ A1B2C3D40000000000000001 /* GetLastOrderProductsTest.swift in Sources */, A1B2C3D40000000000000003 /* GetUserOrdersTest.swift in Sources */, A1B2C3D40000000000000005 /* LoyaltyTests.swift in Sources */, + A1B2C3D40000000000000007 /* CatalogReadTests.swift in Sources */, D1C0FFEF2F0A1B2C3D4E5F60 /* TrackEventCustomFieldsTests.swift in Sources */, D0B70C172D522DAF0001782A /* SearchServiceTests.swift in Sources */, D0B99F9F2DB169D80012E4F0 /* UnsubscribeBackInStock.swift in Sources */, diff --git a/DemoApp/REES46Demo/MainViewController.swift b/DemoApp/REES46Demo/MainViewController.swift index 9f4f01f2..c4c0ef37 100644 --- a/DemoApp/REES46Demo/MainViewController.swift +++ b/DemoApp/REES46Demo/MainViewController.swift @@ -63,6 +63,10 @@ class MainViewController: UIViewController, UIScrollViewDelegate { private var getUserOrdersButton: UIButton! private var loyaltyJoinButton: UIButton! private var loyaltyStatusButton: UIButton! + private var getProfileButton: UIButton! + private var getProductCountersButton: UIButton! + private var getCategoryButton: UIButton! + private var getCollectionButton: UIButton! public var waitIndicator: SdkActivityIndicator! @@ -287,6 +291,10 @@ class MainViewController: UIViewController, UIScrollViewDelegate { setupGetUserOrdersButton() setupLoyaltyJoinButton() setupLoyaltyStatusButton() + setupGetProfileButton() + setupGetProductCountersButton() + setupGetCategoryButton() + setupGetCollectionButton() fontInterPreload() DispatchQueue.main.asyncAfter(deadline: .now() + 2.0) { @@ -623,6 +631,162 @@ class MainViewController: UIViewController, UIScrollViewDelegate { } } + func setupGetProfileButton() { + getProfileButton = DemoShopButton(type: .system) + getProfileButton.setTitle("Profile: get", for: .normal) + getProfileButton.setTitleColor(.white, for: .normal) + getProfileButton.translatesAutoresizingMaskIntoConstraints = false + scrollView.addSubview(getProfileButton) + + NSLayoutConstraint.activate([ + getProfileButton.topAnchor.constraint(equalTo: loyaltyStatusButton.bottomAnchor, constant: 24), + getProfileButton.leadingAnchor.constraint(equalTo: showStoriesButton.leadingAnchor), + getProfileButton.widthAnchor.constraint(equalTo: showStoriesButton.widthAnchor), + getProfileButton.heightAnchor.constraint(equalTo: showStoriesButton.heightAnchor), + ]) + + getProfileButton.addTarget(self, action: #selector(didTapGetProfile), for: .touchUpInside) + } + + func setupGetProductCountersButton() { + getProductCountersButton = DemoShopButton(type: .system) + getProductCountersButton.setTitle("Products: counters", for: .normal) + getProductCountersButton.setTitleColor(.white, for: .normal) + getProductCountersButton.translatesAutoresizingMaskIntoConstraints = false + scrollView.addSubview(getProductCountersButton) + + NSLayoutConstraint.activate([ + getProductCountersButton.topAnchor.constraint(equalTo: getProfileButton.bottomAnchor, constant: 10), + getProductCountersButton.leadingAnchor.constraint(equalTo: showStoriesButton.leadingAnchor), + getProductCountersButton.widthAnchor.constraint(equalTo: showStoriesButton.widthAnchor), + getProductCountersButton.heightAnchor.constraint(equalTo: showStoriesButton.heightAnchor), + ]) + + getProductCountersButton.addTarget(self, action: #selector(didTapGetProductCounters), for: .touchUpInside) + } + + func setupGetCategoryButton() { + getCategoryButton = DemoShopButton(type: .system) + getCategoryButton.setTitle("Category: list", for: .normal) + getCategoryButton.setTitleColor(.white, for: .normal) + getCategoryButton.translatesAutoresizingMaskIntoConstraints = false + scrollView.addSubview(getCategoryButton) + + NSLayoutConstraint.activate([ + getCategoryButton.topAnchor.constraint(equalTo: getProductCountersButton.bottomAnchor, constant: 10), + getCategoryButton.leadingAnchor.constraint(equalTo: showStoriesButton.leadingAnchor), + getCategoryButton.widthAnchor.constraint(equalTo: showStoriesButton.widthAnchor), + getCategoryButton.heightAnchor.constraint(equalTo: showStoriesButton.heightAnchor), + ]) + + getCategoryButton.addTarget(self, action: #selector(didTapGetCategory), for: .touchUpInside) + } + + @objc + private func didTapGetProfile() { + guard let sdk = globalSDK else { + presentTrackEventDemoAlert(title: "SDK", message: "globalSDK is not initialized.") + return + } + sdk.getProfile { result in + switch result { + case .success(let response): + self.presentTrackEventDemoAlert( + title: "Profile", + message: "id: \(response.id ?? "—"), hasEmail: \(response.hasEmail.map(String.init) ?? "—"), gender: \(response.gender ?? "—")" + ) + case .failure(let error): + self.presentTrackEventDemoAlert( + title: "getProfile failed", + message: Self.sdkErrorDescription(error) + ) + } + } + } + + @objc + private func didTapGetProductCounters() { + guard let sdk = globalSDK else { + presentTrackEventDemoAlert(title: "SDK", message: "globalSDK is not initialized.") + return + } + sdk.getProductCounters(item: "300275") { result in + switch result { + case .success(let response): + self.presentTrackEventDemoAlert( + title: "Product counters", + message: "now.view: \(response.now?.view ?? 0), price_drop: \(response.triggers?.priceDrop ?? 0)" + ) + case .failure(let error): + self.presentTrackEventDemoAlert( + title: "getProductCounters failed", + message: Self.sdkErrorDescription(error) + ) + } + } + } + + @objc + private func didTapGetCategory() { + guard let sdk = globalSDK else { + presentTrackEventDemoAlert(title: "SDK", message: "globalSDK is not initialized.") + return + } + sdk.getCategory(category: "smartfony-i-gadzhety", limit: 5) { result in + switch result { + case .success(let response): + self.presentTrackEventDemoAlert( + title: "Category", + message: "total: \(response.productsTotal), products: \(response.products.count)" + ) + case .failure(let error): + self.presentTrackEventDemoAlert( + title: "getCategory failed", + message: Self.sdkErrorDescription(error) + ) + } + } + } + + func setupGetCollectionButton() { + getCollectionButton = DemoShopButton(type: .system) + getCollectionButton.setTitle("Collection: get", for: .normal) + getCollectionButton.setTitleColor(.white, for: .normal) + getCollectionButton.translatesAutoresizingMaskIntoConstraints = false + scrollView.addSubview(getCollectionButton) + + NSLayoutConstraint.activate([ + getCollectionButton.topAnchor.constraint(equalTo: getCategoryButton.bottomAnchor, constant: 10), + getCollectionButton.leadingAnchor.constraint(equalTo: showStoriesButton.leadingAnchor), + getCollectionButton.widthAnchor.constraint(equalTo: showStoriesButton.widthAnchor), + getCollectionButton.heightAnchor.constraint(equalTo: showStoriesButton.heightAnchor), + ]) + + getCollectionButton.addTarget(self, action: #selector(didTapGetCollection), for: .touchUpInside) + } + + @objc + private func didTapGetCollection() { + guard let sdk = globalSDK else { + presentTrackEventDemoAlert(title: "SDK", message: "globalSDK is not initialized.") + return + } + sdk.getCollection(collectionId: "1") { result in + switch result { + case .success(let response): + self.presentTrackEventDemoAlert( + title: "Collection", + message: "products: \(response.products.count)" + ) + case .failure(let error): + self.presentTrackEventDemoAlert( + title: "getCollection failed", + message: Self.sdkErrorDescription(error) + ) + } + } + } + private func presentTrackEventDemoAlert(title: String, message: String) { DispatchQueue.main.async { let alert = UIAlertController(title: title, message: message, preferredStyle: .alert) diff --git a/DemoApp/Rees46DemoTests/CatalogReadTests.swift b/DemoApp/Rees46DemoTests/CatalogReadTests.swift new file mode 100644 index 00000000..e1ea5a09 --- /dev/null +++ b/DemoApp/Rees46DemoTests/CatalogReadTests.swift @@ -0,0 +1,107 @@ +import XCTest +@testable import REES46 + +/// Integration tests for the catalog/profile read methods +/// (`GET /profile`, `GET /products/counters`, `GET /category/{slug}`). +/// +/// Mirrors `LoyaltyTests`: the shop is taken from the `TEST_SHOP_ID` / `TEST_API_URL` +/// environment (injected by CI) with a fallback to the shared constants. +class CatalogReadTests: XCTestCase { + + var sdk: PersonalizationSDK! + + var shopId: String { + return ProcessInfo.processInfo.environment[Constants.testShopIdKey] ?? Constants.testShopId + } + + var apiDomain: String { + return ProcessInfo.processInfo.environment[Constants.testApiUrlKey] ?? Constants.testApiDomain + } + + /// Category slug present in the integration test shop's catalog. + private let categorySlug = "smartfony-i-gadzhety" + /// Item id present in the integration test shop's catalog. + private let itemId = "300275" + /// Collection id configured in the integration test shop's dashboard. + private let collectionId = "1" + + override func setUp() { + super.setUp() + sdk = createPersonalizationSDK( + shopId: shopId, + apiDomain: apiDomain, + parentViewController: nil + ) + } + + override func tearDown() { + sdk = nil + super.tearDown() + } + + func testGetProfile_returnsSuccess() { + let expectation = self.expectation(description: "getProfile completion") + + sdk?.getProfile { result in + switch result { + case .success(let response): + // Profile is freeform; custom_properties always parses (at least to [:]). + XCTAssertNotNil(response.customProperties) + case .failure(let error): + XCTFail("getProfile failed with error: \(error)") + } + expectation.fulfill() + } + + waitForExpectations(timeout: 10, handler: nil) + } + + func testGetProductCounters_returnsSuccess() { + let expectation = self.expectation(description: "getProductCounters completion") + + sdk?.getProductCounters(item: itemId) { result in + switch result { + case .success(let response): + XCTAssertNotNil(response.triggers) + case .failure(let error): + XCTFail("getProductCounters failed with error: \(error)") + } + expectation.fulfill() + } + + waitForExpectations(timeout: 10, handler: nil) + } + + func testGetCategory_returnsProducts() { + let expectation = self.expectation(description: "getCategory completion") + + sdk?.getCategory(category: categorySlug, limit: 5) { result in + switch result { + case .success(let response): + XCTAssertGreaterThan(response.productsTotal, 0) + XCTAssertFalse(response.products.isEmpty) + case .failure(let error): + XCTFail("getCategory failed with error: \(error)") + } + expectation.fulfill() + } + + waitForExpectations(timeout: 10, handler: nil) + } + + func testGetCollection_returnsProducts() { + let expectation = self.expectation(description: "getCollection completion") + + sdk?.getCollection(collectionId: collectionId) { result in + switch result { + case .success(let response): + XCTAssertFalse(response.products.isEmpty) + case .failure(let error): + XCTFail("getCollection failed with error: \(error)") + } + expectation.fulfill() + } + + waitForExpectations(timeout: 10, handler: nil) + } +} diff --git a/DemoApp/Rees46DemoTests/TrackEventCustomFieldsTests.swift b/DemoApp/Rees46DemoTests/TrackEventCustomFieldsTests.swift index ada33e6d..99e655ba 100644 --- a/DemoApp/Rees46DemoTests/TrackEventCustomFieldsTests.swift +++ b/DemoApp/Rees46DemoTests/TrackEventCustomFieldsTests.swift @@ -193,6 +193,22 @@ private final class MockPersonalizationSDK: PersonalizationSDK { fatalError("Not needed for these tests") } + func getProfile(completion: @escaping (Result) -> Void) { + fatalError("Not needed for these tests") + } + + func getProductCounters(item: String, completion: @escaping (Result) -> Void) { + fatalError("Not needed for these tests") + } + + func getCategory(category: String, limit: Int?, page: Int?, brands: String?, locations: String?, filters: [String: Any]?, completion: @escaping (Result) -> Void) { + fatalError("Not needed for these tests") + } + + func getCollection(collectionId: String, location: String?, email: String?, phone: String?, externalId: String?, loyaltyId: String?, completion: @escaping (Result) -> Void) { + fatalError("Not needed for these tests") + } + func getDeviceId() -> String { deviceId } func setParentViewController(controller: UIViewController, completion: @escaping () -> Void) { diff --git a/REES46/Classes/Model/CategoryResponse.swift b/REES46/Classes/Model/CategoryResponse.swift new file mode 100644 index 00000000..2589633e --- /dev/null +++ b/REES46/Classes/Model/CategoryResponse.swift @@ -0,0 +1,56 @@ +import Foundation + +/// Response for the `GET /category/{category}` request. +/// +/// Mirrors the full-search payload: the matched products plus faceting metadata. +/// Products are exposed via a light [CategoryProduct] model; consumers needing the +/// full catalog shape can fetch a product via `getProductInfo`. +public struct CategoryResponse { + public var productsTotal: Int + public var products: [CategoryProduct] + public var brands: [CategoryBrand] + + init(json: [String: Any]) { + productsTotal = json["products_total"] as? Int ?? 0 + products = (json["products"] as? [[String: Any]] ?? []).map { CategoryProduct(json: $0) } + brands = (json["brands"] as? [[String: Any]] ?? []).map { CategoryBrand(json: $0) } + } +} + +public struct CategoryProduct { + public var id: String? + public var name: String? + public var brand: String? + public var price: Double? + public var priceFormatted: String? + public var priceFull: Double? + public var priceFullFormatted: String? + public var currency: String? + public var imageUrl: String? + public var url: String? + public var picture: String? + + init(json: [String: Any]) { + id = json["id"] as? String + name = json["name"] as? String + brand = json["brand"] as? String + price = json["price"] as? Double + priceFormatted = json["price_formatted"] as? String + priceFull = json["price_full"] as? Double + priceFullFormatted = json["price_full_formatted"] as? String + currency = json["currency"] as? String + imageUrl = json["image_url"] as? String + url = json["url"] as? String + picture = json["picture"] as? String + } +} + +public struct CategoryBrand { + public var name: String? + public var count: Int? + + init(json: [String: Any]) { + name = json["name"] as? String + count = json["count"] as? Int + } +} diff --git a/REES46/Classes/Model/CollectionResponse.swift b/REES46/Classes/Model/CollectionResponse.swift new file mode 100644 index 00000000..c8ee8b09 --- /dev/null +++ b/REES46/Classes/Model/CollectionResponse.swift @@ -0,0 +1,13 @@ +import Foundation + +/// Response for the `GET /collection/{id}` request. +/// +/// A configured product collection: `{ "products": [...] }`. Products share the +/// light [CategoryProduct] shape used by the category listing. +public struct CollectionResponse { + public var products: [CategoryProduct] + + init(json: [String: Any]) { + products = (json["products"] as? [[String: Any]] ?? []).map { CategoryProduct(json: $0) } + } +} diff --git a/REES46/Classes/Model/GetProfileResponse.swift b/REES46/Classes/Model/GetProfileResponse.swift new file mode 100644 index 00000000..ee134887 --- /dev/null +++ b/REES46/Classes/Model/GetProfileResponse.swift @@ -0,0 +1,34 @@ +import Foundation + +/// Response for the `GET /profile` request. +/// +/// Returns the stored user profile. Only the most common fields are typed; the +/// open-ended `custom_properties` map is kept as a raw dictionary because its +/// shape is shop-specific. +public struct GetProfileResponse { + public var id: String? + public var email: String? + public var phone: String? + public var firstName: String? + public var lastName: String? + public var hasEmail: Bool? + public var emailRegisteredAt: String? + public var gender: String? + public var computedGender: String? + public var boughtSomething: Bool? + public var customProperties: [String: Any] + + init(json: [String: Any]) { + id = json["id"] as? String + email = json["email"] as? String + phone = json["phone"] as? String + firstName = json["first_name"] as? String + lastName = json["last_name"] as? String + hasEmail = json["has_email"] as? Bool + emailRegisteredAt = json["email_registered_at"] as? String + gender = json["gender"] as? String + computedGender = json["computed_gender"] as? String + boughtSomething = json["bought_something"] as? Bool + customProperties = json["custom_properties"] as? [String: Any] ?? [:] + } +} diff --git a/REES46/Classes/Model/ProductCountersResponse.swift b/REES46/Classes/Model/ProductCountersResponse.swift new file mode 100644 index 00000000..dd5353e4 --- /dev/null +++ b/REES46/Classes/Model/ProductCountersResponse.swift @@ -0,0 +1,44 @@ +import Foundation + +/// Response for the `GET /products/counters` request. +/// +/// Envelope: `{ "daily": {...}, "now": {...}, "triggers": {...} }`. +public struct ProductCountersResponse { + public var daily: ProductCounter? + public var now: ProductCounter? + public var triggers: ProductCounterTriggers? + + init(json: [String: Any]) { + if let daily = json["daily"] as? [String: Any] { + self.daily = ProductCounter(json: daily) + } + if let now = json["now"] as? [String: Any] { + self.now = ProductCounter(json: now) + } + if let triggers = json["triggers"] as? [String: Any] { + self.triggers = ProductCounterTriggers(json: triggers) + } + } +} + +public struct ProductCounter { + public var view: Int + public var cart: Int + public var purchase: Int + + init(json: [String: Any]) { + view = json["view"] as? Int ?? 0 + cart = json["cart"] as? Int ?? 0 + purchase = json["purchase"] as? Int ?? 0 + } +} + +public struct ProductCounterTriggers { + public var backInStock: Int + public var priceDrop: Int + + init(json: [String: Any]) { + backInStock = json["back_in_stock"] as? Int ?? 0 + priceDrop = json["price_drop"] as? Int ?? 0 + } +} diff --git a/REES46/Classes/Sdk/impl/SimplePersonalizationSDK.swift b/REES46/Classes/Sdk/impl/SimplePersonalizationSDK.swift index 66fef655..be0644ca 100644 --- a/REES46/Classes/Sdk/impl/SimplePersonalizationSDK.swift +++ b/REES46/Classes/Sdk/impl/SimplePersonalizationSDK.swift @@ -826,6 +826,118 @@ class SimplePersonalizationSDK: PersonalizationSDK { } } + func getProfile(completion: @escaping (Result) -> Void) { + sessionQueue.addOperation { + let params: [String: String] = [ + "shop_id": self.shopId, + "did": self.deviceId + ] + + let sessionConfig = URLSessionConfiguration.default + sessionConfig.timeoutIntervalForRequest = 1 + sessionConfig.waitsForConnectivity = true + sessionConfig.shouldUseExtendedBackgroundIdleMode = true + self.urlSession = URLSession(configuration: sessionConfig) + + self.getRequest(path: "profile", params: params) { result in + switch result { + case let .success(json): + completion(.success(GetProfileResponse(json: json))) + case let .failure(error): + completion(.failure(error)) + } + } + } + } + + func getProductCounters(item: String, completion: @escaping (Result) -> Void) { + sessionQueue.addOperation { + let params: [String: String] = [ + "shop_id": self.shopId, + "item": item + ] + + let sessionConfig = URLSessionConfiguration.default + sessionConfig.timeoutIntervalForRequest = 1 + sessionConfig.waitsForConnectivity = true + sessionConfig.shouldUseExtendedBackgroundIdleMode = true + self.urlSession = URLSession(configuration: sessionConfig) + + self.getRequest(path: "products/counters", params: params) { result in + switch result { + case let .success(json): + completion(.success(ProductCountersResponse(json: json))) + case let .failure(error): + completion(.failure(error)) + } + } + } + } + + func getCategory(category: String, limit: Int?, page: Int?, brands: String?, locations: String?, filters: [String: Any]?, completion: @escaping (Result) -> Void) { + sessionQueue.addOperation { + var params: [String: String] = [ + "shop_id": self.shopId, + "did": self.deviceId, + "seance": self.userSeance, + "sid": self.userSeance + ] + if let limit = limit { params["limit"] = String(limit) } + if let page = page { params["page"] = String(page) } + if let brands = brands { params["brands"] = brands } + if let locations = locations { params["locations"] = locations } + if let filters = filters, + let data = try? JSONSerialization.data(withJSONObject: filters, options: []), + let text = String(data: data, encoding: .utf8) { + params["filters"] = text + } + + let sessionConfig = URLSessionConfiguration.default + sessionConfig.timeoutIntervalForRequest = 1 + sessionConfig.waitsForConnectivity = true + sessionConfig.shouldUseExtendedBackgroundIdleMode = true + self.urlSession = URLSession(configuration: sessionConfig) + + self.getRequest(path: "category/\(category)", params: params) { result in + switch result { + case let .success(json): + completion(.success(CategoryResponse(json: json))) + case let .failure(error): + completion(.failure(error)) + } + } + } + } + + func getCollection(collectionId: String, location: String?, email: String?, phone: String?, externalId: String?, loyaltyId: String?, completion: @escaping (Result) -> Void) { + sessionQueue.addOperation { + var params: [String: String] = [ + "shop_id": self.shopId, + "did": self.deviceId + ] + if let location = location { params["location"] = location } + if let email = email { params["email"] = email } + if let phone = phone { params["phone"] = phone } + if let externalId = externalId { params["external_id"] = externalId } + if let loyaltyId = loyaltyId { params["loyalty_id"] = loyaltyId } + + let sessionConfig = URLSessionConfiguration.default + sessionConfig.timeoutIntervalForRequest = 1 + sessionConfig.waitsForConnectivity = true + sessionConfig.shouldUseExtendedBackgroundIdleMode = true + self.urlSession = URLSession(configuration: sessionConfig) + + self.getRequest(path: "collection/\(collectionId)", params: params) { result in + switch result { + case let .success(json): + completion(.success(CollectionResponse(json: json))) + case let .failure(error): + completion(.failure(error)) + } + } + } + } + func notificationClicked(type: String, code: String, completion: @escaping (Result) -> Void) { notificationService.trackNotification( path: "track/clicked", diff --git a/REES46/Classes/Sdk/protocol/PersonalizationSDK.protocol.swift b/REES46/Classes/Sdk/protocol/PersonalizationSDK.protocol.swift index 0754d0b3..314805ee 100644 --- a/REES46/Classes/Sdk/protocol/PersonalizationSDK.protocol.swift +++ b/REES46/Classes/Sdk/protocol/PersonalizationSDK.protocol.swift @@ -46,6 +46,10 @@ public protocol PersonalizationSDK { func getUserOrders(shopSecret: String, did: String?, email: String?, phone: String?, loyaltyId: String?, externalId: String?, dateFrom: String?, completion: @escaping(Result<[Order], SdkError>) -> Void) func joinLoyalty(phone: String, email: String?, firstName: String?, lastName: String?, completion: @escaping(Result) -> Void) func getLoyaltyStatus(identifier: String, completion: @escaping(Result) -> Void) + func getProfile(completion: @escaping(Result) -> Void) + func getProductCounters(item: String, completion: @escaping(Result) -> Void) + func getCategory(category: String, limit: Int?, page: Int?, brands: String?, locations: String?, filters: [String: Any]?, completion: @escaping(Result) -> Void) + func getCollection(collectionId: String, location: String?, email: String?, phone: String?, externalId: String?, loyaltyId: String?, completion: @escaping(Result) -> Void) func getDeviceId() -> String @available(*, deprecated, message: "Use enableAutoPopupPresentation or popupPresentationDelegate instead") func setParentViewController(controller: UIViewController, completion: @escaping () -> Void) @@ -124,7 +128,15 @@ public extension PersonalizationSDK { func joinLoyalty(phone: String, email: String? = nil, firstName: String? = nil, lastName: String? = nil, completion: @escaping(Result) -> Void) { joinLoyalty(phone: phone, email: email, firstName: firstName, lastName: lastName, completion: completion) } - + + func getCategory(category: String, limit: Int? = nil, page: Int? = nil, brands: String? = nil, locations: String? = nil, filters: [String: Any]? = nil, completion: @escaping(Result) -> Void) { + getCategory(category: category, limit: limit, page: page, brands: brands, locations: locations, filters: filters, completion: completion) + } + + func getCollection(collectionId: String, location: String? = nil, email: String? = nil, phone: String? = nil, externalId: String? = nil, loyaltyId: String? = nil, completion: @escaping(Result) -> Void) { + getCollection(collectionId: collectionId, location: location, email: email, phone: phone, externalId: externalId, loyaltyId: loyaltyId, completion: completion) + } + func search( query: String, limit: Int? = nil,