some fixes for param passing and decoding

This commit is contained in:
Wilhelm Oks 2024-12-23 15:42:59 +01:00
parent 3824735fe1
commit 6a254cc2ae
4 changed files with 60 additions and 61 deletions

View File

@ -1,7 +1,7 @@
import Foundation import Foundation
import KreeRequest import KreeRequest
public struct SwiftDevRant { //TODO: rename to something else to not collide with the module name public struct DevRantRequest {
let request: KreeRequest let request: KreeRequest
let backend = DevRantBackend() let backend = DevRantBackend()
@ -65,9 +65,15 @@ public struct SwiftDevRant { //TODO: rename to something else to not collide wit
return body 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 { func logIn(username: String, password: String) async throws -> AuthToken {
var parameters: [String: String] = [:] var parameters: [String: String] = [:]
parameters["app"] = "3" parameters["app"] = "3"
@ -76,8 +82,7 @@ public extension SwiftDevRant {
let config = makeConfig(.post, path: "users/auth-token") 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 = stringBody(fromUrlParameters: parameters)
let body = String(KreeRequest.urlEncodedQueryString(from: parameters).dropFirst()) // dropping the first character "?"
let response: AuthToken.CodingData.Container = try await request.requestJson(config: config, string: body, apiError: DevRantApiError.CodingData.self) 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. /// - rantId: The id of the rant.
/// - vote: The vote for this rant. /// - vote: The vote for this rant.
func voteOnRant(token: AuthToken, rantId: Int, vote: VoteState, downvoteReason: DownvoteReason = .notForMe) async throws -> 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) parameters["vote"] = String(vote.rawValue)
@ -271,14 +278,13 @@ public extension SwiftDevRant {
parameters["reason"] = String(downvoteReason.rawValue) 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 { struct Response: Codable {
let rant: Rant.CodingData 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 return response.rant.decoded
} }
@ -290,7 +296,9 @@ public extension SwiftDevRant {
/// - commentId: The id of the comment. /// - commentId: The id of the comment.
/// - vote: The vote for this comment. /// - vote: The vote for this comment.
func voteOnComment(token: AuthToken, commentId: Int, vote: VoteState, downvoteReason: DownvoteReason = .notForMe) async throws -> 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) parameters["vote"] = String(vote.rawValue)
@ -298,13 +306,13 @@ public extension SwiftDevRant {
parameters["reason"] = String(downvoteReason.rawValue) 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 { struct Response: Decodable {
let comment: Comment.CodingData 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 return response.comment.decoded
} }
@ -319,7 +327,9 @@ public extension SwiftDevRant {
/// - location: The user's geographic location. /// - location: The user's geographic location.
/// - website: The user's personal website. /// - website: The user's personal website.
func editUserProfile(token: AuthToken, about: String, skills: String, github: String, location: String, website: String) async throws { 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_about"] = about
parameters["profile_skills"] = skills parameters["profile_skills"] = skills
@ -327,9 +337,9 @@ public extension SwiftDevRant {
parameters["profile_location"] = location parameters["profile_location"] = location
parameters["profile_website"] = website 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. /// 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 { func postRant(token: AuthToken, kind: Rant.Kind, text: String, tags: String, image: Data?, imageConversion: [ImageDataConverter] = [.unsupportedToJpeg]) async throws -> Int {
let boundary = UUID().uuidString 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 var parameters = config.urlParameters
@ -389,7 +399,11 @@ public extension SwiftDevRant {
let config = makeConfig(.post, path: "devrant/rants/\(rantId)/\(favoritePath)", token: token) 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. /// 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 { 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 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 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 { func postComment(token: AuthToken, rantId: Int, text: String, image: Data?, imageConversion: [ImageDataConverter] = [.unsupportedToJpeg]) async throws {
let boundary = UUID().uuidString 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 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 { func editComment(token: AuthToken, commentId: Int, text: String, image: Data?, imageConversion: [ImageDataConverter] = [.unsupportedToJpeg]) async throws {
let boundary = UUID().uuidString 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 var parameters = config.urlParameters
@ -486,16 +500,28 @@ public extension SwiftDevRant {
try await request.requestJson(config: config, apiError: DevRantApiError.CodingData.self) try await request.requestJson(config: config, apiError: DevRantApiError.CodingData.self)
} }
/// Subscribes to or unsubscribes from a user. /// Subscribes to a user.
/// ///
/// - Parameters: /// - Parameters:
/// - token: The token from the `logIn` call response. /// - token: The token from the `logIn` call response.
/// - userId: The id of the user to subscribe to or to unsubscribe from. /// - userId: The id of the user to subscribe to.
/// - subscribe: `true` subscribes to the user, `false` unsubscribes from the user. func subscribeToUser(token: AuthToken, userId: Int) async throws {
func subscribeToUser(token: AuthToken, userId: Int, subscribe: Bool) async throws { let config = makeConfig(.post, path: "users/\(userId)/subscribe", token: token)
let method: KreeRequest.Method = subscribe ? .post : .delete
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) try await request.requestJson(config: config, apiError: DevRantApiError.CodingData.self)
} }

View File

@ -28,7 +28,7 @@ public extension NotificationFeed {
let rantId = notification.rantId let rantId = notification.rantId
let commentId = notification.commentId let commentId = notification.commentId
let userId = notification.userId 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 userAvatar = userInfo?.avatar ?? .init(colorHex: "cccccc", imageUrlPath: nil)
let userName = userInfo?.username ?? "" let userName = userInfo?.username ?? ""

View File

@ -2,9 +2,9 @@ public extension NotificationFeed {
struct UserInfo: Hashable, Sendable { struct UserInfo: Hashable, Sendable {
public let avatar: User.Avatar public let avatar: User.Avatar
public let username: String 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.avatar = avatar
self.username = username self.username = username
self.userId = userId self.userId = userId
@ -13,37 +13,8 @@ public extension NotificationFeed {
} }
extension NotificationFeed.UserInfo { extension NotificationFeed.UserInfo {
struct CodingData: Decodable { struct UsernameMapEntryCodingData: Decodable {
struct Container: Decodable {
let array: [CodingData]
}
let avatar: User.Avatar.CodingData
let name: String let name: String
let uidForUsername: String //TODO: why is this String? The other user ids are Int. let avatar: User.Avatar.CodingData
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
)
} }
} }

View File

@ -39,7 +39,7 @@ extension NotificationFeed {
let check_time: Int let check_time: Int
let items: [Notification.CodingData] let items: [Notification.CodingData]
let unread: UnreadNumbers.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)), lastChecked: Date(timeIntervalSince1970: TimeInterval(check_time)),
notifications: items.map(\.decoded), notifications: items.map(\.decoded),
unreadNumbers: unread.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)
}
) )
} }
} }