Models WIP: Notification, NotificationFeed

This commit is contained in:
Wilhelm Oks 2024-12-12 16:33:46 +01:00
parent fc331b32c3
commit 394e9a92c3
4 changed files with 236 additions and 0 deletions

View File

@ -0,0 +1,86 @@
import Foundation
/// A notification about activities in a rant or a comment.
public struct Notification: Hashable, Identifiable {
public enum Kind: String {
/// An upvote for a rant.
case rantUpvote = "content_vote"
/// An upvote for a comment.
case commentUpvote = "comment_vote"
/// A new comment in one of the logged in user's rants.
case newCommentInOwnRant = "comment_content"
/// A new comment in a rant that the logged in user has commented in.
case newComment = "comment_discuss"
/// A mention of the logged in user in a comment.
case mentionInComment = "comment_mention"
/// A new rant posted by someone that the logged in user is subscribed to.
case newRantOfSubscribedUser = "rant_sub"
}
/// The id of the rant associated with this notification.
public let rantId: Int
/// The id of the comment associated with this notification, if this notification is for a comment.
public let commentId: Int?
/// The time when this notification was created.
public let created: Date
/// True if the user has already read this notification.
public let read: Bool
/// The type of this notification.
public let kind: Kind
/// The id of the user who triggered the notification.
public let userId: Int
public var id: String {
[
String(rantId),
commentId.flatMap{ String($0) } ?? "-",
String(Int(created.timeIntervalSince1970)),
String(read),
kind.rawValue,
String(userId)
].joined(separator: "|")
}
public init(rantId: Int, commentId: Int?, created: Date, read: Bool, kind: Notification.Kind, userId: Int) {
self.rantId = rantId
self.commentId = commentId
self.created = created
self.read = read
self.kind = kind
self.userId = userId
}
}
extension Notification {
struct CodingData: Codable {
let rant_id: Int
let comment_id: Int?
let created_time: Int
let read: Int
let type: String
let uid: Int
}
}
extension Notification.CodingData {
var decoded: Notification {
.init(
rantId: rant_id,
commentId: comment_id,
created: Date(timeIntervalSince1970: TimeInterval(created_time)),
read: read != 0,
kind: .init(rawValue: type) ?? .newComment,
userId: uid
)
}
}

View File

@ -0,0 +1,50 @@
public extension NotificationFeed {
/// Holds numbers of unread notifications for each type of notification.
struct UnreadNumbers: Decodable, Hashable {
/// The total number of unread notifications
public let all: Int
/// The number of unread commets.
public let comments: Int
/// The number of unread mentions.
public let mentions: Int
/// The number of unread rants from users which the logged in user is subscribed to.
public let subscriptions: Int
/// The number of unread upvotes.
public let upvotes: Int
public init(all: Int, comments: Int, mentions: Int, subscriptions: Int, upvotes: Int) {
self.all = all
self.comments = comments
self.mentions = mentions
self.subscriptions = subscriptions
self.upvotes = upvotes
}
}
}
extension NotificationFeed.UnreadNumbers {
struct CodingData: Codable {
let all: Int
let comments: Int
let mentions: Int
let subs: Int
let upvotes: Int
//let total: Int //Not needed because it's the same as `all`.
}
}
extension NotificationFeed.UnreadNumbers.CodingData {
var decoded: NotificationFeed.UnreadNumbers {
.init(
all: all,
comments: comments,
mentions: mentions,
subscriptions: subs,
upvotes: upvotes
)
}
}

View File

@ -0,0 +1,49 @@
public extension NotificationFeed {
struct UserInfo: Hashable {
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 init(avatar: User.Avatar, username: String, userId: String) {
self.avatar = avatar
self.username = username
self.userId = userId
}
}
}
extension NotificationFeed.UserInfo {
struct CodingData: Decodable {
struct Container: Decodable {
let array: [CodingData]
}
let avatar: User.Avatar.CodingData
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
)
}
}

View File

@ -0,0 +1,51 @@
import Foundation
/// Contains a list of all notifications for the logged in user and the numbers of unread notifications.
public struct NotificationFeed: Hashable {
public enum Categories: String, CaseIterable {
case all = ""
case upvotes = "upvotes"
case mentions = "mentions"
case comments = "comments"
case subscriptions = "subs"
}
/// The time when the notifications were last checked.
public let lastChecked: Date
/// The list of all notifications for the logged in user.
public let notifications: [Notification]
/// The numbers of unread notifications.
public let unreadNumbers: UnreadNumbers
/// Infos about the user name and avatar for each user id.
public let userInfos: [UserInfo]
public init(lastChecked: Date, notifications: [Notification], unreadNumbers: NotificationFeed.UnreadNumbers, userInfos: [UserInfo]) {
self.lastChecked = lastChecked
self.notifications = notifications
self.unreadNumbers = unreadNumbers
self.userInfos = userInfos
}
}
extension NotificationFeed {
struct CodingData: Decodable {
let check_time: Int
let items: [Notification.CodingData]
let unread: UnreadNumbers.CodingData
let username_map: UserInfo.CodingData.Container
}
}
extension NotificationFeed.CodingData {
var decoded: NotificationFeed {
.init(
lastChecked: Date(timeIntervalSince1970: TimeInterval(check_time)),
notifications: items.map(\.decoded),
unreadNumbers: unread.decoded,
userInfos: username_map.array.map(\.decoded)
)
}
}