Files
singlechat/ios/YpChat/Data/RestAPIClient.swift
Torsten Schulz (local) 810b084e10 Refactor application structure and configuration
- Updated the application namespace and ID from "net.ypchat.app" to "de.ypchat.android" for better alignment with branding.
- Increased Gradle heap size settings to optimize build performance.
- Disabled dependency constraints to simplify dependency management.
- Removed obsolete files related to the previous application structure, including MainActivity, YpChatApp, and various core components, streamlining the codebase.

These changes collectively enhance the application's configuration and structure, improving maintainability and performance.
2026-05-12 14:25:55 +02:00

183 lines
6.9 KiB
Swift

import Foundation
enum RestAPIError: Error, LocalizedError {
case invalidURL(String)
case badStatus(Int, String?)
case decoding(Error)
var errorDescription: String? {
switch self {
case .invalidURL(let s): return "Ungültige URL: \(s)"
case .badStatus(let code, let body): return "HTTP \(code): \(body ?? "")"
case .decoding(let e): return "JSON: \(e.localizedDescription)"
}
}
}
/// REST-Schicht analog `RestApi.kt`.
final class RestAPIClient: @unchecked Sendable {
private let baseURLString: String
private let session: URLSession
private let decoder: JSONDecoder
private let encoder: JSONEncoder
init(baseURLString: String, session: URLSession) {
self.baseURLString = baseURLString.trimmingCharacters(in: CharacterSet(charactersIn: "/"))
self.session = session
self.decoder = JSONDecoder()
self.encoder = JSONEncoder()
}
func sessionStatus() async throws -> SessionResponse {
try await request(path: "api/session", method: "GET", body: nil)
}
func logout() async throws -> LogoutResponse {
let url = try url(for: "api/logout")
var req = URLRequest(url: url)
req.httpMethod = "POST"
req.setValue("application/json", forHTTPHeaderField: "Accept")
let (data, response) = try await session.data(for: req)
guard let http = response as? HTTPURLResponse else {
throw RestAPIError.badStatus(-1, nil)
}
guard (200 ... 299).contains(http.statusCode) else {
let text = String(data: data, encoding: .utf8)
throw RestAPIError.badStatus(http.statusCode, text)
}
if data.isEmpty {
return LogoutResponse(success: true)
}
do {
return try decoder.decode(LogoutResponse.self, from: data)
} catch {
throw RestAPIError.decoding(error)
}
}
func countries() async throws -> [String: String] {
try await request(path: "api/countries", method: "GET", body: nil)
}
func feedback() async throws -> FeedbackResponse {
try await request(path: "api/feedback", method: "GET", body: nil)
}
func feedbackAdminStatus() async throws -> FeedbackAdminStatusResponse {
try await request(path: "api/feedback/admin-status", method: "GET", body: nil)
}
func submitFeedback(_ requestBody: FeedbackRequest) async throws {
let data = try encoder.encode(requestBody)
try await requestVoid(path: "api/feedback", method: "POST", body: data)
}
func feedbackAdminLogin(_ requestBody: FeedbackAdminLoginRequest) async throws -> FeedbackAdminStatusResponse {
let data = try encoder.encode(requestBody)
return try await request(path: "api/feedback/admin-login", method: "POST", body: data)
}
func feedbackAdminLogout() async throws {
try await requestVoid(path: "api/feedback/admin-logout", method: "POST", body: Data())
}
func deleteFeedback(id: String) async throws {
let encoded = id.addingPercentEncoding(withAllowedCharacters: .urlPathAllowed) ?? id
try await requestVoid(path: "api/feedback/\(encoded)", method: "DELETE", body: nil)
}
func partners() async throws -> [PartnerLinkDto] {
try await request(path: "api/partners", method: "GET", body: nil)
}
/// Multipart-Feld `image` wie OkHttp `MultipartBody.Part`.
func uploadImage(data: Data, fileName: String, mimeType: String) async throws -> (response: ImageUploadResponse, httpStatus: Int) {
let url = try url(for: "api/upload-image")
let boundary = "Boundary-\(UUID().uuidString)"
var req = URLRequest(url: url)
req.httpMethod = "POST"
req.setValue("multipart/form-data; boundary=\(boundary)", forHTTPHeaderField: "Content-Type")
var body = Data()
body.append("--\(boundary)\r\n".data(using: .utf8)!)
body.append(
"Content-Disposition: form-data; name=\"image\"; filename=\"\(fileName)\"\r\n".data(using: .utf8)!
)
body.append("Content-Type: \(mimeType)\r\n\r\n".data(using: .utf8)!)
body.append(data)
body.append("\r\n--\(boundary)--\r\n".data(using: .utf8)!)
req.httpBody = body
let (respData, response) = try await session.data(for: req)
guard let http = response as? HTTPURLResponse else {
throw RestAPIError.badStatus(-1, nil)
}
guard (200 ... 299).contains(http.statusCode) else {
let text = String(data: respData, encoding: .utf8)
throw RestAPIError.badStatus(http.statusCode, text)
}
do {
let decoded = try decoder.decode(ImageUploadResponse.self, from: respData)
return (decoded, http.statusCode)
} catch {
throw RestAPIError.decoding(error)
}
}
// MARK: - Request
private func url(for path: String) throws -> URL {
let trimmed = path.trimmingCharacters(in: CharacterSet(charactersIn: "/"))
guard let url = URL(string: "\(baseURLString)/\(trimmed)") else {
throw RestAPIError.invalidURL("\(baseURLString)/\(trimmed)")
}
return url
}
private func requestVoid(path: String, method: String, body: Data?) async throws {
let url = try url(for: path)
var req = URLRequest(url: url)
req.httpMethod = method
req.httpBody = body
if let body, !body.isEmpty {
req.setValue("application/json", forHTTPHeaderField: "Content-Type")
}
req.setValue("application/json", forHTTPHeaderField: "Accept")
let (data, response) = try await session.data(for: req)
guard let http = response as? HTTPURLResponse else {
throw RestAPIError.badStatus(-1, nil)
}
guard (200 ... 299).contains(http.statusCode) else {
let text = String(data: data, encoding: .utf8)
throw RestAPIError.badStatus(http.statusCode, text)
}
}
private func request<T: Decodable>(path: String, method: String, body: Data?) async throws -> T {
let url = try url(for: path)
var req = URLRequest(url: url)
req.httpMethod = method
req.httpBody = body
if let body, !body.isEmpty {
req.setValue("application/json", forHTTPHeaderField: "Content-Type")
}
req.setValue("application/json", forHTTPHeaderField: "Accept")
let (data, response) = try await session.data(for: req)
guard let http = response as? HTTPURLResponse else {
throw RestAPIError.badStatus(-1, nil)
}
guard (200 ... 299).contains(http.statusCode) else {
let text = String(data: data, encoding: .utf8)
throw RestAPIError.badStatus(http.statusCode, text)
}
do {
return try decoder.decode(T.self, from: data)
} catch {
throw RestAPIError.decoding(error)
}
}
}