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

[FEAT] Define external dependencies #4

Open
wants to merge 13 commits into
base: master
Choose a base branch
from
4 changes: 4 additions & 0 deletions FindAMentor/Application/AppDelegate.swift
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,10 @@ class AppDelegate: UIResponder, UIApplicationDelegate, RootInstaller {
return true
}

func application(_ app: UIApplication, open url: URL, options: [UIApplication.OpenURLOptionsKey : Any] = [:]) -> Bool {
self.store.dependencies.authManager.resumeAuthentication(from: url, with: options)
}

}

extension AppDelegate {
Expand Down
71 changes: 71 additions & 0 deletions FindAMentor/Dependencies/Auth0Manager.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
//
// Auth0Manager.swift
// FindAMentor
//
// Created by Riccardo Cipolleschi on 12/04/2020.
//

import Foundation
import Auth0
import Hydra

/// Protocol that completely defines and masks the Auth0 API.
protocol Auth0Manager {
/// Function that begins the authentication flow
/// - parameter scope: The scope of this specific authentication flow
func startAuthentication(with scope: String) -> Promise<Credentials>

/// Function tha begins the logout process for the user
func logout() -> Promise<Void>

/// Resumes the login process after the user logged in
/// - parameter from: The URL that is asked to be opened
/// - parameter options: options passed to the app
func resumeAuthentication(from url: URL, with options: [UIApplication.OpenURLOptionsKey : Any]) -> Bool
}

/// Implementation for the Auth0 Manager
class Auth0ManagerImpl: Auth0Manager {

/// the backend endpoint
var audience: String

/// Default initializer
init(audience: String) {
self.audience = audience
}

func startAuthentication(with scope: String) -> Promise<Credentials> {
return Promise<Credentials>(in: .background) { [unowned self] resolve, reject, _ in
// is it safe to use unowned here because self refers to the manager and
// the manager is kept alive by the Katana Store. No risk to access it when nil

Auth0.webAuth().scope(scope).audience(self.audience).start { result in
switch result {
case .failure(let error):
reject(error)
case .success(let credentials):
resolve(credentials)
}
}
}
}

func logout() -> Promise<Void> {
return Promise<Void>(in: .background) { resolve, reject, _ in
Auth0.webAuth().clearSession(federated: false) { successful in
guard successful else {
return reject(Models.Auth0Error.logoutFailed)
}
resolve(())
}
}
}

func resumeAuthentication(from url: URL, with options: [UIApplication.OpenURLOptionsKey : Any]) -> Bool {
return Auth0.resumeAuth(url, options: options)
}



}
83 changes: 83 additions & 0 deletions FindAMentor/Dependencies/BackendManager.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
//
// BackendManager.swift
// FindAMentor
//
// Created by Riccardo Cipolleschi on 12/04/2020.
//

import Foundation
import Hydra

/// This protocol defines the public interface of the Backend Manager
/// It contains all the functions that masks the APIs call
protocol BackendManager {

// MARK: - Mentors

/// Retrieve the list of mentors
func getMentors() -> Promise<[Models.User]>

/// Send a request for mentorship from a user to another.
/// - parameter from: the requesting user identifier
/// - parameter to: the mentor identifier
/// - parameter requestInfo: the set of informations that are attached to the request
// TODO: improve the requestInfo object
func requestMentorship(from userId: String, to mentorId: String, requestInfo: String) -> Promise<Void>

/// Retrieve the list of mentorhips for a specific mentor
/// - parameter mentorId: the identifier of the mentor
func mentorships(for mentorId: String) -> Promise<Models.Mentorship>

/// Updates a mentorship with a new set of application info
/// - parameter applicationId: the identifier of the application
/// - parameter applicationInfo: a the model of the application
func updateMentorshipStatus(for applicationId: String, with applicationInfo: Models.Mentorship) -> Promise<Void>


// MARK: - Users

/// Retrieve the model of the user currently logged in
func getCurrentUser() -> Promise<Models.User>

/// Retrieve the model of the user given a specific id
/// - parameter with: The identifier of the user
func getUser(with id: String) -> Promise<Models.User>

/// Updates the model of the user, given a new model.
/// This method can be used to update the goals of the user or any profile information
/// - parameter with: the new Model for the user
func updateUser(with model: Models.User) -> Promise<Void>

}

class BackendManagerImpl: BackendManager {
func getMentors() -> Promise<[Models.User]> {
fatalError("Not implemented yet")
}

func requestMentorship(from userId: String, to mentorId: String, requestInfo: String) -> Promise<Void> {
fatalError("Not implemented yet")
}

func mentorships(for mentorId: String) -> Promise<Models.Mentorship> {
fatalError("Not implemented yet")
}

func updateMentorshipStatus(for applicationId: String, with applicationInfo: Models.Mentorship) -> Promise<Void> {
fatalError("Not implemented yet")
}

func getCurrentUser() -> Promise<Models.User> {
fatalError("Not implemented yet")
}

func getUser(with id: String) -> Promise<Models.User> {
fatalError("Not implemented yet")
}

func updateUser(with model: Models.User) -> Promise<Void> {
fatalError("Not implemented yet")
}


}
19 changes: 19 additions & 0 deletions FindAMentor/Dependencies/DependenciesContainer.swift
Original file line number Diff line number Diff line change
Expand Up @@ -11,17 +11,36 @@ class DependenciesContainer:
SideEffectDependencyContainer,
NavigationProvider
{

// Use the right backend url in Debug and in Release
#if DEBUG
static let backendURL: String = "http://https://api-staging.codingcoach.io/"
#else
static let backendURL: String = "https://https://api.codingcoach.io/"
#endif

// The function that let the dependencies to dispatch other Dispatchable
var dispatch: PromisableStoreDispatch

// The function that let the dependencies to retrieve the most updated version of the State
var getState: GetState

// The dependencies responsible to handle the navigation in the app
var navigator: Navigator

// the manager that hide the interactions with the backend
var backendManager: BackendManager

// the manager that handles the login and logout operation
var authManager: Auth0Manager

// Initializer of all the dependencies
required init(dispatch: @escaping PromisableStoreDispatch, getState: @escaping GetState) {
self.dispatch = dispatch
self.getState = getState

self.navigator = Navigator()
self.backendManager = BackendManagerImpl()
self.authManager = Auth0ManagerImpl(audience: Self.backendURL)
}
}
13 changes: 13 additions & 0 deletions FindAMentor/Models/Errors.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
//
// Errors.swift
// FindAMentor
//
// Created by Riccardo Cipolleschi on 12/04/2020.
//

import Foundation
extension Models {
enum Auth0Error: Error {
case logoutFailed
}
}
29 changes: 29 additions & 0 deletions FindAMentor/Models/Mentorship.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
//
// Mentorship.swift
// FindAMentor
//
// Created by Riccardo Cipolleschi on 12/04/2020.
//

import Foundation

extension Models {
/// Model for the mentorhips
struct Mentorship: Codable {
/// Possible statuses of a request
enum Status: String, Codable {
case accepted
case pending
case rejected
}

/// The current status of the request
var status: Status

/// The description of the request
var description: String

/// The reason why the mentor has asked for the mentorship
var reason: String
}
}
18 changes: 18 additions & 0 deletions FindAMentor/Models/User.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
//
// UserModel.swift
// FindAMentor
//
// Created by Riccardo Cipolleschi on 12/04/2020.
//

import Foundation

/// Namespace for the Models
enum Models {

/// User model
// TODO: improve the model from the backend documentation
struct User: Codable {

}
}
14 changes: 14 additions & 0 deletions FindAMentor/State/AppState.swift
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,18 @@ import Katana
/// to work at runtime
struct AppState: State, Codable {

enum CodingKeys: String, CodingKey {
case userState
case mentorsState
case loginState
}

// MARK: - Persisted Slices
var loginState: LoginState = LoginState()
var userState: UserState = UserState()
var mentorsState: MentorsState = MentorsState()

// MARK: - Non persisted Slices
var environmentState: EnvironmentState = EnvironmentState()

}
89 changes: 89 additions & 0 deletions FindAMentor/State/EnvironmentState.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
//
// EnvironmentState.swift
// FindAMentor
//
// Created by Riccardo Cipolleschi on 11/04/2020.
//

import Foundation
import Katana
import UserNotifications

extension AppState {
/// This structure contains the environment information
/// for the current session of the user.
struct EnvironmentState: State {
var keyboardState: KeyboardState = KeyboardState()
var pushNotificationState: PushNotificationState = PushNotificationState()
}

// MARK: - Keyboard State

/// This struct contains the current state of the keyboard.
/// The keyboard can be hidden or visible.
/// When visible, it is presented with some animation options
/// that need to imitated by the rest of the UI for a smooth animation
struct KeyboardState: State, Equatable {
static let defaultAnimationDuration: Double = 0.3
static let defaultAnimationCurve: UIView.AnimationCurve = .easeInOut

enum Visibility: Equatable {
case hidden
case visible(frame: CGRect)
}

var visibility: Visibility = .hidden
var animationDuration = defaultAnimationDuration
var animationCurve = defaultAnimationCurve

/// The current height of the keyboard. 0 if the keyboard is hidden
var height: CGFloat {
switch self.visibility {
case .hidden:
return 0
case .visible(let frame):
return frame.height
}
}
}

// MARK: - Push Notification State

/// This structure contains the current state of the Push Notification
/// It also define an enumeration with all the supported notification types
struct PushNotificationState: State, Equatable {

var pushNotificationStatuses: [PushNotificationType: UNAuthorizationStatus] = [:]

/// Enumeration with all the supported type. This is required because the
/// UNAuthorizationOptions does not conform to Hashable, therefore it
/// has to be converted from an Obj-C type to a Swift type.
enum PushNotificationType: Int {
case alert
case badge
case carPlay
case criticalAlert
case provisional
case sound

init(from option: UNAuthorizationOptions) {
switch option {
case .alert:
self = .alert
case .badge:
self = .badge
case .carPlay:
self = .carPlay
case .criticalAlert:
self = .criticalAlert
case .provisional:
self = .provisional
case .sound:
self = .sound
default:
fatalError("Cannot create a PushNotificationType")
}
}
}
}
}
21 changes: 21 additions & 0 deletions FindAMentor/State/LoginState.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
//
// LoginState.swift
// FindAMentor
//
// Created by Riccardo Cipolleschi on 11/04/2020.
//

import Foundation
import Katana

extension AppState {

/// This structure contains all the informations related to the user's login.
/// The backend is using Auth0 for authentication.
struct LoginState: State, Codable {
// TODO: Study Auth0 to understand what to put here.
// In general we would like to keep the token and everything we need to
// eventually refresh it

}
}
Loading