From 6a254cc2ae4447de68b7243ff9b5c554bdba0323 Mon Sep 17 00:00:00 2001 From: Wilhelm Oks Date: Mon, 23 Dec 2024 15:42:59 +0100 Subject: [PATCH] some fixes for param passing and decoding --- ...wiftDevRant.swift => DevRantRequest.swift} | 76 +++++++++++++------ .../Models/NotificationFeed+Mapping.swift | 2 +- .../Models/NotificationFeed.UserInfo.swift | 37 +-------- .../Models/NotificationFeed.swift | 6 +- 4 files changed, 60 insertions(+), 61 deletions(-) rename Sources/SwiftDevRant/{SwiftDevRant.swift => DevRantRequest.swift} (88%) diff --git a/Sources/SwiftDevRant/SwiftDevRant.swift b/Sources/SwiftDevRant/DevRantRequest.swift similarity index 88% rename from Sources/SwiftDevRant/SwiftDevRant.swift rename to Sources/SwiftDevRant/DevRantRequest.swift index ee95c20..dc3cb6f 100644 --- a/Sources/SwiftDevRant/SwiftDevRant.swift +++ b/Sources/SwiftDevRant/DevRantRequest.swift @@ -1,7 +1,7 @@ import Foundation import KreeRequest -public struct SwiftDevRant { //TODO: rename to something else to not collide with the module name +public struct DevRantRequest { let request: KreeRequest let backend = DevRantBackend() @@ -65,9 +65,15 @@ public struct SwiftDevRant { //TODO: rename to something else to not collide wit return body } + + /// For endpoints with the POST method in the devRant API the url parameters need to be passed as a string in the http body rather than in the URL. + /// The url encoding works but it might also work with other encodings like json or multipart form data. + private func stringBody(fromUrlParameters urlParameters: [String: String]) -> String { + String(KreeRequest.urlEncodedQueryString(from: urlParameters).dropFirst()) // dropping the first character "?" + } } -public extension SwiftDevRant { +public extension DevRantRequest { func logIn(username: String, password: String) async throws -> AuthToken { var parameters: [String: String] = [:] parameters["app"] = "3" @@ -76,8 +82,7 @@ public extension SwiftDevRant { let config = makeConfig(.post, path: "users/auth-token") - // For the log in request the url encoded parameters are passed as a string in the http body instead of in the URL. - let body = String(KreeRequest.urlEncodedQueryString(from: parameters).dropFirst()) // dropping the first character "?" + let body = stringBody(fromUrlParameters: parameters) let response: AuthToken.CodingData.Container = try await request.requestJson(config: config, string: body, apiError: DevRantApiError.CodingData.self) @@ -263,7 +268,9 @@ public extension SwiftDevRant { /// - rantId: The id of the rant. /// - vote: The vote for this rant. func voteOnRant(token: AuthToken, rantId: Int, vote: VoteState, downvoteReason: DownvoteReason = .notForMe) async throws -> Rant { - var parameters: [String: String] = [:] + let config = makeConfig(.post, path: "devrant/rants/\(rantId)/vote", token: token) + + var parameters = config.urlParameters parameters["vote"] = String(vote.rawValue) @@ -271,14 +278,13 @@ public extension SwiftDevRant { parameters["reason"] = String(downvoteReason.rawValue) } - let config = makeConfig(.post, path: "devrant/rants/\(rantId)/vote", urlParameters: parameters, token: token) + let body = stringBody(fromUrlParameters: parameters) struct Response: Codable { let rant: Rant.CodingData - //let comments: [Comment.CodingData]? //probably not needed } - let response: Response = try await request.requestJson(config: config, apiError: DevRantApiError.CodingData.self) + let response: Response = try await request.requestJson(config: config, string: body, apiError: DevRantApiError.CodingData.self) return response.rant.decoded } @@ -290,7 +296,9 @@ public extension SwiftDevRant { /// - commentId: The id of the comment. /// - vote: The vote for this comment. func voteOnComment(token: AuthToken, commentId: Int, vote: VoteState, downvoteReason: DownvoteReason = .notForMe) async throws -> Comment { - var parameters: [String: String] = [:] + let config = makeConfig(.post, path: "comments/\(commentId)/vote", token: token) + + var parameters = config.urlParameters parameters["vote"] = String(vote.rawValue) @@ -298,13 +306,13 @@ public extension SwiftDevRant { parameters["reason"] = String(downvoteReason.rawValue) } - let config = makeConfig(.post, path: "comments/\(commentId)/vote", urlParameters: parameters, token: token) + let body = stringBody(fromUrlParameters: parameters) struct Response: Decodable { let comment: Comment.CodingData } - let response: Response = try await request.requestJson(config: config, apiError: DevRantApiError.CodingData.self) + let response: Response = try await request.requestJson(config: config, string: body, apiError: DevRantApiError.CodingData.self) return response.comment.decoded } @@ -319,7 +327,9 @@ public extension SwiftDevRant { /// - location: The user's geographic location. /// - website: The user's personal website. func editUserProfile(token: AuthToken, about: String, skills: String, github: String, location: String, website: String) async throws { - var parameters: [String: String] = [:] + let config = makeConfig(.post, path: "users/me/edit-profile", token: token) + + var parameters = config.urlParameters parameters["profile_about"] = about parameters["profile_skills"] = skills @@ -327,9 +337,9 @@ public extension SwiftDevRant { parameters["profile_location"] = location parameters["profile_website"] = website - let config = makeConfig(.post, path: "users/me/edit-profile", urlParameters: parameters, token: token) + let body = stringBody(fromUrlParameters: parameters) - try await request.requestJson(config: config, apiError: DevRantApiError.CodingData.self) + try await request.requestJson(config: config, string: body, apiError: DevRantApiError.CodingData.self) } /// Creates and posts a rant. @@ -346,7 +356,7 @@ public extension SwiftDevRant { func postRant(token: AuthToken, kind: Rant.Kind, text: String, tags: String, image: Data?, imageConversion: [ImageDataConverter] = [.unsupportedToJpeg]) async throws -> Int { let boundary = UUID().uuidString - let config = makeMultipartConfig(.post, path: "devrant/rants", boundary: boundary) + let config = makeMultipartConfig(.post, path: "devrant/rants", boundary: boundary, token: token) var parameters = config.urlParameters @@ -389,7 +399,11 @@ public extension SwiftDevRant { let config = makeConfig(.post, path: "devrant/rants/\(rantId)/\(favoritePath)", token: token) - try await request.requestJson(config: config, apiError: DevRantApiError.CodingData.self) + let parameters = config.urlParameters + + let body = stringBody(fromUrlParameters: parameters) + + try await request.requestJson(config: config, string: body, apiError: DevRantApiError.CodingData.self) } /// Edits a posted rant. @@ -404,7 +418,7 @@ public extension SwiftDevRant { func editRant(token: AuthToken, rantId: Int, kind: Rant.Kind, text: String, tags: String, image: Data?, imageConversion: [ImageDataConverter] = [.unsupportedToJpeg]) async throws { let boundary = UUID().uuidString - let config = makeMultipartConfig(.post, path: "devrant/rants/\(rantId)", boundary: boundary) + let config = makeMultipartConfig(.post, path: "devrant/rants/\(rantId)", boundary: boundary, token: token) var parameters = config.urlParameters @@ -429,7 +443,7 @@ public extension SwiftDevRant { func postComment(token: AuthToken, rantId: Int, text: String, image: Data?, imageConversion: [ImageDataConverter] = [.unsupportedToJpeg]) async throws { let boundary = UUID().uuidString - let config = makeMultipartConfig(.post, path: "devrant/rants/\(rantId)/comments", boundary: boundary) + let config = makeMultipartConfig(.post, path: "devrant/rants/\(rantId)/comments", boundary: boundary, token: token) var parameters = config.urlParameters @@ -452,7 +466,7 @@ public extension SwiftDevRant { func editComment(token: AuthToken, commentId: Int, text: String, image: Data?, imageConversion: [ImageDataConverter] = [.unsupportedToJpeg]) async throws { let boundary = UUID().uuidString - let config = makeMultipartConfig(.post, path: "comments/\(commentId)", boundary: boundary) + let config = makeMultipartConfig(.post, path: "comments/\(commentId)", boundary: boundary, token: token) var parameters = config.urlParameters @@ -486,16 +500,28 @@ public extension SwiftDevRant { try await request.requestJson(config: config, apiError: DevRantApiError.CodingData.self) } - /// Subscribes to or unsubscribes from a user. + /// Subscribes to a user. /// /// - Parameters: /// - token: The token from the `logIn` call response. - /// - userId: The id of the user to subscribe to or to unsubscribe from. - /// - subscribe: `true` subscribes to the user, `false` unsubscribes from the user. - func subscribeToUser(token: AuthToken, userId: Int, subscribe: Bool) async throws { - let method: KreeRequest.Method = subscribe ? .post : .delete + /// - userId: The id of the user to subscribe to. + func subscribeToUser(token: AuthToken, userId: Int) async throws { + let config = makeConfig(.post, path: "users/\(userId)/subscribe", token: token) - let config = makeConfig(method, path: "users/\(userId)/subscribe", token: token) + let parameters = config.urlParameters + + let body = stringBody(fromUrlParameters: parameters) + + try await request.requestJson(config: config, string: body, apiError: DevRantApiError.CodingData.self) + } + + /// Unsubscribes from a user. + /// + /// - Parameters: + /// - token: The token from the `logIn` call response. + /// - userId: The id of the user to unsubscribe from. + func unsubscribeFromUser(token: AuthToken, userId: Int) async throws { + let config = makeConfig(.delete, path: "users/\(userId)/subscribe", token: token) try await request.requestJson(config: config, apiError: DevRantApiError.CodingData.self) } diff --git a/Sources/SwiftDevRant/Models/NotificationFeed+Mapping.swift b/Sources/SwiftDevRant/Models/NotificationFeed+Mapping.swift index acd620f..59ffcb2 100644 --- a/Sources/SwiftDevRant/Models/NotificationFeed+Mapping.swift +++ b/Sources/SwiftDevRant/Models/NotificationFeed+Mapping.swift @@ -28,7 +28,7 @@ public extension NotificationFeed { let rantId = notification.rantId let commentId = notification.commentId let userId = notification.userId - let userInfo = userInfos.first { $0.userId == String(userId) } + let userInfo = userInfos.first { $0.userId == userId } let userAvatar = userInfo?.avatar ?? .init(colorHex: "cccccc", imageUrlPath: nil) let userName = userInfo?.username ?? "" diff --git a/Sources/SwiftDevRant/Models/NotificationFeed.UserInfo.swift b/Sources/SwiftDevRant/Models/NotificationFeed.UserInfo.swift index 42785fc..ceb36c1 100644 --- a/Sources/SwiftDevRant/Models/NotificationFeed.UserInfo.swift +++ b/Sources/SwiftDevRant/Models/NotificationFeed.UserInfo.swift @@ -2,9 +2,9 @@ public extension NotificationFeed { struct UserInfo: Hashable, Sendable { public let avatar: User.Avatar public let username: String - public let userId: String //TODO: why is this String? The other user ids are Int. + public let userId: Int - public init(avatar: User.Avatar, username: String, userId: String) { + public init(avatar: User.Avatar, username: String, userId: Int) { self.avatar = avatar self.username = username self.userId = userId @@ -13,37 +13,8 @@ public extension NotificationFeed { } extension NotificationFeed.UserInfo { - struct CodingData: Decodable { - struct Container: Decodable { - let array: [CodingData] - } - - let avatar: User.Avatar.CodingData + struct UsernameMapEntryCodingData: Decodable { let name: String - let uidForUsername: String //TODO: why is this String? The other user ids are Int. - - private enum CodingKeys: CodingKey { - case avatar - case name - } - - init(from decoder: Decoder) throws { - let values = try decoder.container(keyedBy: CodingKeys.self) - - avatar = try values.decode(User.Avatar.CodingData.self, forKey: .avatar) - name = try values.decode(String.self, forKey: .name) - - uidForUsername = values.codingPath[values.codingPath.endIndex - 1].stringValue //TODO: wtf is this? Check if it can be made simpler and easier to understand. - } - } -} - -extension NotificationFeed.UserInfo.CodingData { - var decoded: NotificationFeed.UserInfo { - .init( - avatar: avatar.decoded, - username: name, - userId: uidForUsername - ) + let avatar: User.Avatar.CodingData } } diff --git a/Sources/SwiftDevRant/Models/NotificationFeed.swift b/Sources/SwiftDevRant/Models/NotificationFeed.swift index 988d559..4b3b7d8 100644 --- a/Sources/SwiftDevRant/Models/NotificationFeed.swift +++ b/Sources/SwiftDevRant/Models/NotificationFeed.swift @@ -39,7 +39,7 @@ extension NotificationFeed { let check_time: Int let items: [Notification.CodingData] let unread: UnreadNumbers.CodingData - let username_map: UserInfo.CodingData.Container + let username_map: [String: NotificationFeed.UserInfo.UsernameMapEntryCodingData] } } @@ -49,7 +49,9 @@ extension NotificationFeed.CodingData { lastChecked: Date(timeIntervalSince1970: TimeInterval(check_time)), notifications: items.map(\.decoded), unreadNumbers: unread.decoded, - userInfos: username_map.array.map(\.decoded) + userInfos: username_map.map { (key, value) in + .init(avatar: value.avatar.decoded, username: value.name, userId: Int(key) ?? 0) + } ) } }