Skip to content

Commit

Permalink
Merge pull request #5 from helloItsHEssam/feature/modularization-data
Browse files Browse the repository at this point in the history
Feature/modularization data
  • Loading branch information
helloItsHEssam authored Nov 5, 2023
2 parents 4dd9b83 + 7998b84 commit f9ae381
Show file tree
Hide file tree
Showing 58 changed files with 2,382 additions and 9 deletions.
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

0 comments on commit f9ae381

Please sign in to comment.