11import Foundation
22
3+ /// Сервис для обращений к серверу
34struct APIService {
5+ /// Сервис, отвечающий за обновление `UserDefaults`
46 private let defaults : DefaultsProtocol
7+ /// Базовый `url` сервера
58 private let baseUrlString : String
9+ /// Время таймаута для `URLSession`
610 private let timeoutInterval : TimeInterval
11+ /// `true` - нужна базовая аутентификация, `false` - не нужна
12+ private let needAuth : Bool
13+ /// `true` - можно принудительно деавторизовать пользователя, `false` - не можем
14+ ///
15+ /// Если значение `true`, деавторизуем пользователя при получении кода `401` от сервера
16+ private let canForceLogout : Bool
717
18+ /// Инициализирует `APIService` с заданными параметрами
19+ /// - Parameters:
20+ /// - defaults: Сервис, отвечающий за обновление `UserDefaults`
21+ /// - baseUrlString: Базовый `url` сервера. По умолчанию `https://workout.su/api/v3`
22+ /// - timeoutInterval: Время таймаута для `URLSession`. По умолчанию `15`
23+ /// - needAuth: Необходимость базовой аутентификации. По умолчанию `true`
24+ /// - canForceLogout: Доступность принудительной деавторизации. По умолчанию `true`
825 init (
926 with defaults: DefaultsProtocol ,
1027 baseUrlString: String = " https://workout.su/api/v3 " ,
11- timeoutInterval: TimeInterval = 15
28+ timeoutInterval: TimeInterval = 15 ,
29+ needAuth: Bool = true ,
30+ canForceLogout: Bool = true
1231 ) {
1332 self . defaults = defaults
1433 self . baseUrlString = baseUrlString
1534 self . timeoutInterval = timeoutInterval
35+ self . needAuth = needAuth
36+ self . canForceLogout = canForceLogout
1637 }
1738
1839 /// Выполняет регистрацию пользователя
1940 /// - Parameter model: необходимые для регистрации данные
2041 /// - Returns: Вся информация о пользователе
2142 func registration( with model: MainUserForm ) async throws {
2243 let endpoint = Endpoint . registration ( form: model)
23- let result = try await makeResult ( UserResponse . self, for: endpoint. urlRequest ( with: baseUrlString) , needAuth : false )
44+ let result = try await makeResult ( UserResponse . self, for: endpoint. urlRequest ( with: baseUrlString) )
2445 try await defaults. saveAuthData ( . init( login: model. userName, password: model. password) )
2546 try await defaults. saveUserInfo ( result)
2647 }
@@ -59,7 +80,7 @@ struct APIService {
5980 /// - Returns: `true` в случае успеха, `false` при ошибках
6081 func resetPassword( for login: String ) async throws -> Bool {
6182 let endpoint = Endpoint . resetPassword ( login: login)
62- let response = try await makeResult ( LoginResponse . self, for: endpoint. urlRequest ( with: baseUrlString) , needAuth : false )
83+ let response = try await makeResult ( LoginResponse . self, for: endpoint. urlRequest ( with: baseUrlString) )
6384 return response. userID != . zero
6485 }
6586
@@ -187,15 +208,15 @@ struct APIService {
187208 /// Загружает список всех площадок
188209 /// - Returns: Список всех площадок
189210 func getAllSportsGrounds( ) async throws -> [ SportsGround ] {
190- try await makeResult ( [ SportsGround ] . self, for: Endpoint . getAllSportsGrounds. urlRequest ( with: baseUrlString) , needAuth : false )
211+ try await makeResult ( [ SportsGround ] . self, for: Endpoint . getAllSportsGrounds. urlRequest ( with: baseUrlString) )
191212 }
192213
193214 /// Загружает список всех площадок, обновленных после указанной даты
194215 /// - Parameter stringDate: дата отсечки для поиска обновленных площадок
195216 /// - Returns: Список обновленных площадок
196217 func getUpdatedSportsGrounds( from stringDate: String ) async throws -> [ SportsGround ] {
197218 let endpoint = Endpoint . getUpdatedSportsGrounds ( from: stringDate)
198- return try await makeResult ( [ SportsGround ] . self, for: endpoint. urlRequest ( with: baseUrlString) , needAuth : false )
219+ return try await makeResult ( [ SportsGround ] . self, for: endpoint. urlRequest ( with: baseUrlString) )
199220 }
200221
201222 /// Загружает данные по отдельной площадке
@@ -204,8 +225,7 @@ struct APIService {
204225 func getSportsGround( id: Int ) async throws -> SportsGround {
205226 try await makeResult (
206227 SportsGround . self,
207- for: Endpoint . getSportsGround ( id: id) . urlRequest ( with: baseUrlString) ,
208- needAuth: false
228+ for: Endpoint . getSportsGround ( id: id) . urlRequest ( with: baseUrlString)
209229 )
210230 }
211231
@@ -324,7 +344,7 @@ struct APIService {
324344 /// - Returns: Список мероприятий
325345 func getEvents( of type: EventType ) async throws -> [ EventResponse ] {
326346 let endpoint : Endpoint = type == . future ? . getFutureEvents : . getPastEvents
327- return try await makeResult ( [ EventResponse ] . self, for: endpoint. urlRequest ( with: baseUrlString) , needAuth : false )
347+ return try await makeResult ( [ EventResponse ] . self, for: endpoint. urlRequest ( with: baseUrlString) )
328348 }
329349
330350 /// Запрашивает конкретное мероприятие
@@ -514,7 +534,8 @@ struct APIService {
514534}
515535
516536private extension APIService {
517- var codeOK : Int { 200 }
537+ var successCode : Int { 200 }
538+ var forceLogoutCode : Int { 401 }
518539
519540 var urlSession : URLSession {
520541 let config = URLSessionConfiguration . default
@@ -528,10 +549,10 @@ private extension APIService {
528549 /// - type: тип, который нужно загрузить
529550 /// - request: запрос, по которому нужно обратиться
530551 /// - Returns: Вся информация по запрошенному типу
531- func makeResult< T: Decodable > ( _ type: T . Type , for request: URLRequest ? , needAuth : Bool = true ) async throws -> T {
532- guard let request = await finalRequest ( request, needAuth : needAuth ) else { throw APIError . badRequest }
552+ func makeResult< T: Decodable > ( _ type: T . Type , for request: URLRequest ? ) async throws -> T {
553+ guard let request = await finalRequest ( request) else { throw APIError . badRequest }
533554 let ( data, response) = try await urlSession. data ( for: request)
534- return try handle ( type, data, response)
555+ return try await handle ( type, data, response)
535556 }
536557
537558 /// Выполняет действие, не требующее указания типа
@@ -542,15 +563,13 @@ private extension APIService {
542563 throw APIError . badRequest
543564 }
544565 let response = try await urlSession. data ( for: request) . 1
545- return try handle ( response)
566+ return try await handle ( response)
546567 }
547568
548569 /// Формирует итоговый запрос к серверу
549- /// - Parameters:
550- /// - request: первоначальный запрос
551- /// - needAuth: `true` - нужна базовая аутентификация, `false` - не нужна
570+ /// - Parameter request: первоначальный запрос
552571 /// - Returns: Итоговый запрос к серверу
553- func finalRequest( _ request: URLRequest ? , needAuth : Bool = true ) async -> URLRequest ? {
572+ func finalRequest( _ request: URLRequest ? ) async -> URLRequest ? {
554573 if needAuth,
555574 let encodedString = try ? await defaults. basicAuthInfo ( ) . base64Encoded {
556575 var requestWithBasicAuth = request
@@ -565,11 +584,15 @@ private extension APIService {
565584 }
566585
567586 /// Обрабатывает ответ сервера и возвращает данные в нужном формате
568- func handle< T: Decodable > ( _ type: T . Type , _ data: Data ? , _ response: URLResponse ? ) throws -> T {
587+ func handle< T: Decodable > ( _ type: T . Type , _ data: Data ? , _ response: URLResponse ? ) async throws -> T {
569588 guard let data, !data. isEmpty else {
570589 throw APIError . noData
571590 }
572- guard ( response as? HTTPURLResponse ) ? . statusCode == codeOK else {
591+ let responseCode = ( response as? HTTPURLResponse ) ? . statusCode
592+ guard responseCode == successCode else {
593+ if canForceLogout, responseCode == forceLogoutCode {
594+ await defaults. triggerLogout ( )
595+ }
573596 throw handleError ( from: data, response: response)
574597 }
575598#if DEBUG
@@ -585,13 +608,16 @@ private extension APIService {
585608 }
586609
587610 /// Обрабатывает ответ сервера, в котором важен только статус
588- func handle( _ response: URLResponse ? ) throws -> Bool {
611+ func handle( _ response: URLResponse ? ) async throws -> Bool {
589612 let responseCode = ( response as? HTTPURLResponse ) ? . statusCode
590613#if DEBUG
591614 print ( " --- Получили статус по запросу: " , ( response? . url? . absoluteString) . valueOrEmpty)
592615 print ( responseCode. valueOrZero)
593616#endif
594- guard responseCode == codeOK else {
617+ guard responseCode == successCode else {
618+ if canForceLogout, responseCode == forceLogoutCode {
619+ await defaults. triggerLogout ( )
620+ }
595621 throw APIError ( with: responseCode)
596622 }
597623 return true
0 commit comments