Skip to content

Commit 3535069

Browse files
authored
Merge pull request #57 from OlegEremenko991/develop/refactor
Force logout
2 parents 54d2a64 + 7c02f1c commit 3535069

File tree

11 files changed

+81
-38
lines changed

11 files changed

+81
-38
lines changed

SwiftUI-WorkoutApp.xcodeproj/project.pbxproj

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1451,7 +1451,7 @@
14511451
"$(inherited)",
14521452
"@executable_path/Frameworks",
14531453
);
1454-
MARKETING_VERSION = 3.1.2;
1454+
MARKETING_VERSION = 3.1.3;
14551455
PRODUCT_BUNDLE_IDENTIFIER = com.FGU.WorkOut;
14561456
PRODUCT_NAME = WorkoutApp;
14571457
SUPPORTED_PLATFORMS = "iphoneos iphonesimulator";
@@ -1487,7 +1487,7 @@
14871487
"$(inherited)",
14881488
"@executable_path/Frameworks",
14891489
);
1490-
MARKETING_VERSION = 3.1.2;
1490+
MARKETING_VERSION = 3.1.3;
14911491
PRODUCT_BUNDLE_IDENTIFIER = com.FGU.WorkOut;
14921492
PRODUCT_NAME = WorkoutApp;
14931493
SUPPORTED_PLATFORMS = "iphoneos iphonesimulator";

SwiftUI-WorkoutApp/Constants.swift

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,11 @@ enum Constants {
55
static let photosLimit = 15
66
static let minUserAge = Calendar.current.date(byAdding: .year, value: -13, to: .now) ?? .now
77
static let maxEventFutureDate = Calendar.current.date(byAdding: .year, value: 1, to: .now) ?? .now
8-
static let incognitoInfoText = "Зарегистрируйтесь или авторизуйтесь, чтобы иметь доступ ко всем возможностям приложения"
8+
static let incognitoInfoText = "Зарегистрируйтесь или авторизуйтесь, чтобы иметь доступ ко всем возможностям"
99
static let appVersion = (Bundle.main.object(forInfoDictionaryKey: "CFBundleShortVersionString") as? String).valueOrEmpty
1010
static let appReviewURL = URL(string: "https://apps.apple.com/app/id1035159361?action=write-review")!
1111
static let workoutShopURL = URL(string: "https://workoutshop.ru")!
12+
static let developerProfileButton = URL(string: "https://boosty.to/oleg991")!
1213
static let officialSiteURL = URL(string: "https://workout.su")!
1314
static let accountCreationURL = URL(string: "https://m.workout.su/users/register")!
1415
static let feedbackRecipient = ["[email protected]"]

SwiftUI-WorkoutApp/Screens/Events/List/EventsListViewModel.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ final class EventsListViewModel: ObservableObject {
1414
{ return }
1515
if !refresh { isLoading.toggle() }
1616
do {
17-
let list = try await APIService(with: defaults).getEvents(of: type)
17+
let list = try await APIService(with: defaults, needAuth: false).getEvents(of: type)
1818
switch type {
1919
case .future: futureEvents = list
2020
case .past: pastEvents = list

SwiftUI-WorkoutApp/Screens/Messages/DialogList/DialogListViewModel.swift

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,8 @@ final class DialogListViewModel: ObservableObject {
1111
if !refresh { isLoading.toggle() }
1212
do {
1313
list = try await APIService(with: defaults).getDialogs()
14-
defaults.unreadMessagesCount = list.map(\.unreadMessagesCount).reduce(0, +)
14+
let unreadMessagesCount = list.map(\.unreadMessagesCount).reduce(0, +)
15+
defaults.saveUnreadMessagesCount(unreadMessagesCount)
1516
} catch {
1617
errorMessage = ErrorFilterService.message(from: error)
1718
}
@@ -45,7 +46,8 @@ final class DialogListViewModel: ObservableObject {
4546
guard dialog.unreadMessagesCount > 0,
4647
defaults.unreadMessagesCount >= dialog.unreadMessagesCount
4748
else { return }
48-
defaults.unreadMessagesCount -= dialog.unreadMessagesCount
49+
let newValue = defaults.unreadMessagesCount - dialog.unreadMessagesCount
50+
defaults.saveUnreadMessagesCount(newValue)
4951
}
5052

5153
func clearErrorMessage() { errorMessage = "" }

SwiftUI-WorkoutApp/Screens/Profile/Settings/AccountInfo/AccountInfoViewModel.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ final class AccountInfoViewModel: ObservableObject {
4646
if isLoading { return }
4747
isLoading.toggle()
4848
do {
49-
try await APIService(with: defaults).registration(with: userForm)
49+
try await APIService(with: defaults, needAuth: false).registration(with: userForm)
5050
} catch {
5151
errorMessage = ErrorFilterService.message(from: error)
5252
}

SwiftUI-WorkoutApp/Screens/Profile/Settings/Login/LoginViewModel.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ final class LoginViewModel: ObservableObject {
1313
if !canLogIn { return }
1414
isLoading.toggle()
1515
do {
16-
try await APIService(with: defaults).logInWith(login, password)
16+
try await APIService(with: defaults, canForceLogout: false).logInWith(login, password)
1717
} catch {
1818
errorMessage = ErrorFilterService.message(from: error)
1919
}
@@ -27,7 +27,7 @@ final class LoginViewModel: ObservableObject {
2727
}
2828
isLoading.toggle()
2929
do {
30-
showResetSuccessfulAlert = try await APIService(with: defaults).resetPassword(for: login)
30+
showResetSuccessfulAlert = try await APIService(with: defaults, needAuth: false).resetPassword(for: login)
3131
} catch {
3232
errorMessage = ErrorFilterService.message(from: error)
3333
}

SwiftUI-WorkoutApp/Screens/Profile/Settings/ProfileSettingsView.swift

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -34,9 +34,12 @@ struct ProfileSettingsView: View {
3434
officialSiteButton
3535
appVersionView
3636
}
37-
Section(mode.supportProjectSectionTitle) {
37+
Section("Поддержать проект") {
3838
workoutShopButton
3939
}
40+
Section("Поддержать разработчика") {
41+
developerProfileButton
42+
}
4043
}
4144
.overlay {
4245
ProgressView()
@@ -79,8 +82,6 @@ private extension ProfileSettingsView.Mode {
7982
var appInfoSectionTitle: String {
8083
self == .authorized ? "Информация о приложении" : "О приложении"
8184
}
82-
83-
var supportProjectSectionTitle: String { "Поддержать проект" }
8485
}
8586

8687
private extension ProfileSettingsView {
@@ -184,6 +185,12 @@ private extension ProfileSettingsView {
184185
}
185186
}
186187

188+
var developerProfileButton: some View {
189+
Link(destination: Constants.developerProfileButton) {
190+
Label("Oleg991 на boosty", systemImage: "figure.wave")
191+
}
192+
}
193+
187194
func setupErrorAlert(with message: String) {
188195
showErrorAlert = !message.isEmpty
189196
alertMessage = message

SwiftUI-WorkoutApp/Screens/SportsGrounds/Detail/SportsGroundDetailViewModel.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ final class SportsGroundDetailViewModel: ObservableObject {
1919
if isLoading || ground.isFull, !refresh { return }
2020
if !refresh { isLoading.toggle() }
2121
do {
22-
ground = try await APIService(with: defaults).getSportsGround(id: ground.id)
22+
ground = try await APIService(with: defaults, needAuth: false).getSportsGround(id: ground.id)
2323
} catch {
2424
errorMessage = ErrorFilterService.message(from: error)
2525
}

SwiftUI-WorkoutApp/Screens/SportsGrounds/Map/SportsGroundsMapViewModel.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ final class SportsGroundsMapViewModel: NSObject, ObservableObject {
5252
}
5353
isLoading.toggle()
5454
do {
55-
defaultList = try await APIService(with: defaults).getAllSportsGrounds()
55+
defaultList = try await APIService(with: defaults, needAuth: false).getAllSportsGrounds()
5656
} catch {
5757
fillDefaultList()
5858
errorMessage = ErrorFilterService.message(from: error)
@@ -65,7 +65,7 @@ final class SportsGroundsMapViewModel: NSObject, ObservableObject {
6565
if isLoading { return }
6666
isLoading.toggle()
6767
do {
68-
let updatedGrounds = try await APIService(with: defaults).getUpdatedSportsGrounds(
68+
let updatedGrounds = try await APIService(with: defaults, needAuth: false).getUpdatedSportsGrounds(
6969
from: DateFormatterService.halfMinuteAgoDateString
7070
)
7171
updatedGrounds.forEach { ground in

SwiftUI-WorkoutApp/Services/APIService.swift

Lines changed: 47 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,47 @@
11
import Foundation
22

3+
/// Сервис для обращений к серверу
34
struct 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

516536
private 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

Comments
 (0)