Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature/modularization data #5

Merged
merged 12 commits into from
Nov 5, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 29 additions & 0 deletions .github/workflows/Data.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
name: Data Layer

on:
pull_request:
branches:
- '*'
- '*/*'

jobs:
build:
runs-on: macos-13

steps:
- name: Checkout code
uses: actions/checkout@v3

- name: Set up SwiftLint
run: brew install swiftlint

- name: Lint code
run: |
cd Data/Sources/
swiftlint
- name: run unit test
run: |
cd Data/
swift build
swift test
4 changes: 3 additions & 1 deletion .github/workflows/Domain.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,9 @@ jobs:
run: brew install swiftlint

- name: Lint code
run: swiftlint
run: |
cd Domain/Sources/
swiftlint

- name: run unit test
run: |
Expand Down
9 changes: 9 additions & 0 deletions Data/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
.DS_Store
/.build
/Packages
/*.xcodeproj
xcuserdata/
DerivedData/
.swiftpm/config/registries.json
.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata
.netrc
31 changes: 31 additions & 0 deletions Data/Package.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
// swift-tools-version: 5.8
// The swift-tools-version declares the minimum version of Swift required to build this package.

import PackageDescription

let package = Package(
name: "Data",
platforms: [.iOS(.v14), .macOS(.v10_15)],
products: [
// Products define the executables and libraries a package produces, and make them visible to other packages.
.library(
name: "Data",
targets: ["Data"]),
],
dependencies: [
// Dependencies declare other packages that this package depends on.
// .package(url: /* package url */, from: "1.0.0"),
.package(path: "../Domain"),
.package(url: "https://github.com/Alamofire/Alamofire.git", .upToNextMajor(from: "5.8.1"))
],
targets: [
// Targets are the basic building blocks of a package. A target can define a module or a test suite.
// Targets can depend on other targets in this package, and on products in packages this package depends on.
.target(
name: "Data",
dependencies: ["Alamofire", "Domain"]),
.testTarget(
name: "DataTests",
dependencies: ["Data"]),
]
)
3 changes: 3 additions & 0 deletions Data/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# Data

A description of this package.
29 changes: 29 additions & 0 deletions Data/Sources/Data/Common/Mapper.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
//
// Mapper.swift
//
//
// Created by Hessam Mahdiabadi on 11/5/23.
//

import Foundation

public protocol Mapper {

associatedtype Entity
associatedtype Dto

func mapEntityToDto(input: Entity) -> Dto
func mapDtoToEntity(input: Dto) -> Entity
}

public extension Mapper {

func mapEntitiesToDtos(input: [Entity]) -> [Dto] {
return input.map { mapEntityToDto(input: $0) }
}

func mapDtosToEntities(input: [Dto]) -> [Entity] {
return input.map { mapDtoToEntity(input: $0) }
}
}

13 changes: 13 additions & 0 deletions Data/Sources/Data/Http/Api.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
//
// Api.swift
//
//
// Created by Hessam Mahdiabadi on 11/5/23.
//

import Foundation

public protocol Api {

func callApi<T: Decodable>(route: ApiRouter, decodeType type: T.Type) async throws -> T
}
76 changes: 76 additions & 0 deletions Data/Sources/Data/Http/ApiImpl.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
//
// ApiImpl.swift
//
//
// Created by Hessam Mahdiabadi on 11/5/23.
//

import Foundation
import Alamofire

final public class ApiImpl: Api {

private var sessionManager: Session
private var decoder: JSONDecoder!

#if DEBUG
public init(configuration: URLSessionConfiguration) {
sessionManager = Session(configuration: configuration)
setupDecoder()
}
#endif

public init() {
sessionManager = Session()
setupDecoder()
}

private func setupDecoder() {
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss'Z'"

decoder = JSONDecoder()
decoder.dateDecodingStrategy = .formatted(dateFormatter)
}

public func callApi<T: Decodable>(route: ApiRouter, decodeType type: T.Type) async throws -> T {
return try await withCheckedThrowingContinuation { [weak self] continuation in
guard let self else {
continuation.resume(throwing: NetworkError.cannotConnectToServer)
return
}

sessionManager.request(route)
.validate(statusCode: 200 ..< 300)
.responseData { [weak self] responseData in
guard let self else {
continuation.resume(throwing: NetworkError.cannotParseJson)
return
}

switch responseData.result {
case .success(let data):
do {
let retVal = try decoder.decode(type, from: data)
continuation.resume(returning: retVal)
} catch {
continuation.resume(throwing: NetworkError.cannotParseJson)
}

case .failure:
guard let data = responseData.data else {
continuation.resume(throwing: NetworkError.cannotConnectToServer)
return
}

guard let responseError = try? decoder.decode(ResponseError.self, from: data) else {
continuation.resume(throwing: NetworkError.cannotConnectToServer)
return
}

continuation.resume(throwing: NetworkError.serverError(message: responseError))
}
}
}
}
}
64 changes: 64 additions & 0 deletions Data/Sources/Data/Http/ApiRouter.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
//
// ApiRouter.swift
//
//
// Created by Hessam Mahdiabadi on 11/5/23.
//

import Foundation
import Alamofire

public enum ApiRouter: URLRequestConvertible {

public typealias Params = [String: Any]

case transferList(offset: Int)

public func asURLRequest() throws -> URLRequest {

let httpMethod = getHttpMethod()
let url = createURL()
var urlRequest = URLRequest(url: url)
urlRequest.httpMethod = httpMethod.rawValue
urlRequest.timeoutInterval = 20.0
urlRequest.cachePolicy = .reloadIgnoringLocalCacheData

let encoding: ParameterEncoding = {
switch httpMethod {
default:
return URLEncoding.queryString
}
}()

return try encoding.encode(urlRequest, with: self.getParams())
}
}

public extension ApiRouter {

func getHttpMethod() -> HTTPMethod {
switch self {
case .transferList:
return .get
}
}

func getParams() -> Params? {
return nil
}

var urlPath: String {
switch self {
case .transferList(let offset):
return "/transfer-list/\(offset)"
}
}

func createURL() -> URL {
var component = URLComponents()
component.scheme = "https"
component.host = "191da1ac-768c-4c6a-80ad-b533beafec25.mock.pstmn.io"
component.path = urlPath
return component.url!
}
}
30 changes: 30 additions & 0 deletions Data/Sources/Data/Http/DataResponse/CardDTO.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
//
// CardDTO.swift
//
//
// Created by Hessam Mahdiabadi on 11/5/23.
//

import Foundation

struct CardDTO: Decodable {

var cardNumber: String?
var cardType: String?

init(cardNumber: String? = nil, cardType: String? = nil) {
self.cardNumber = cardNumber
self.cardType = cardType
}

enum CodingKeys: String, CodingKey {
case cardNumber = "card_number"
case cardType = "card_type"
}

init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
self.cardNumber = try container.decodeIfPresent(String.self, forKey: .cardNumber)
self.cardType = try container.decodeIfPresent(String.self, forKey: .cardType)
}
}
45 changes: 45 additions & 0 deletions Data/Sources/Data/Http/DataResponse/CardTransferCountDTO.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
//
// CardTransferCountDTO.swift
//
//
// Created by Hessam Mahdiabadi on 11/5/23.
//

import Foundation

class CardTransferCountDTO: NSObject, NSSecureCoding, Decodable {

static var supportsSecureCoding: Bool = true
var numberOfTransfers: Int?
var totalTransfer: Int?

init(numberOfTransfers: Int? = nil, totalTransfer: Int? = nil) {
self.numberOfTransfers = numberOfTransfers
self.totalTransfer = totalTransfer
}

func encode(with coder: NSCoder) {
coder.encode(numberOfTransfers, forKey: CodingKeys.numberOfTransfers.rawValue)
coder.encode(totalTransfer, forKey: CodingKeys.totalTransfer.rawValue)
}

enum CodingKeys: String, CodingKey {
case numberOfTransfers = "number_of_transfers"
case totalTransfer = "total_transfer"
}

required init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
self.numberOfTransfers = try container.decodeIfPresent(Int.self, forKey: .numberOfTransfers)
self.totalTransfer = try container.decodeIfPresent(Int.self, forKey: .totalTransfer)
}

required convenience init?(coder: NSCoder) {
let numberOfTransfers = coder.decodeObject(of: NSNumber.self,
forKey: CodingKeys.numberOfTransfers.rawValue)?.intValue
let totalTransfer = coder.decodeObject(of: NSNumber.self,
forKey: CodingKeys.totalTransfer.rawValue)?.intValue

self.init(numberOfTransfers: numberOfTransfers, totalTransfer: totalTransfer)
}
}
45 changes: 45 additions & 0 deletions Data/Sources/Data/Http/DataResponse/PersonBankAccountDTO.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
//
// PersonBankAccountDTO.swift
//
//
// Created by Hessam Mahdiabadi on 11/5/23.
//

import Foundation

struct PersonBankAccountDTO: Decodable {

var person: PersonDTO?
var card: CardDTO?
var moreInfo: CardTransferCountDTO?
var note: String?
var lastTransfer: Date?
var isFavorite: Bool = false

init(person: PersonDTO?, card: CardDTO?, moreInfo: CardTransferCountDTO?, note: String?,
lastTransfer: Date?, isFavorite: Bool = false) {
self.person = person
self.card = card
self.moreInfo = moreInfo
self.note = note
self.lastTransfer = lastTransfer
self.isFavorite = isFavorite
}

enum CodingKeys: String, CodingKey {
case person = "person"
case card = "card"
case moreInfo = "more_info"
case note = "note"
case lastTransfer = "lastTransfer"
}

init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
self.person = try container.decodeIfPresent(PersonDTO.self, forKey: .person)
self.card = try container.decodeIfPresent(CardDTO.self, forKey: .card)
self.moreInfo = try container.decodeIfPresent(CardTransferCountDTO.self, forKey: .moreInfo)
self.note = try container.decodeIfPresent(String.self, forKey: .note)
self.lastTransfer = try container.decodeIfPresent(Date.self, forKey: .lastTransfer)
}
}
Loading
Loading