Skip to content

Commit d4aa69c

Browse files
committed
Add add reaction transaction
1 parent eb184e3 commit d4aa69c

File tree

3 files changed

+186
-3
lines changed

3 files changed

+186
-3
lines changed

apple/InlineKit/Sources/InlineKit/Models/Reaction.swift

+21-2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import Foundation
22
import GRDB
3+
import InlineProtocol
34

45
public struct ApiReaction: Codable, Sendable {
56
public var id: Int64
@@ -14,7 +15,7 @@ public struct Reaction: FetchableRecord, Identifiable, Codable, Hashable, Persis
1415
TableRecord,
1516
Sendable, Equatable
1617
{
17-
public var id: Int64
18+
public var id: Int64?
1819
public var messageId: Int64
1920
public var userId: Int64
2021
public var chatId: Int64
@@ -38,7 +39,7 @@ public struct Reaction: FetchableRecord, Identifiable, Codable, Hashable, Persis
3839
case chatId
3940
}
4041

41-
public init(id: Int64, messageId: Int64, userId: Int64, emoji: String, date: Date, chatId: Int64) {
42+
public init(id: Int64 = Int64.random(in: 1 ... 50_000), messageId: Int64, userId: Int64, emoji: String, date: Date, chatId: Int64) {
4243
self.id = id
4344
self.messageId = messageId
4445
self.userId = userId
@@ -64,3 +65,21 @@ public extension Reaction {
6465
Date(timeIntervalSince1970: Double(from) / 1_000)
6566
}
6667
}
68+
69+
public extension Reaction {
70+
init(from: InlineProtocol.Reaction) {
71+
messageId = from.messageID
72+
userId = from.userID
73+
chatId = from.chatID
74+
emoji = from.emoji
75+
date = Date(timeIntervalSince1970: TimeInterval(from.date))
76+
}
77+
78+
static func save(
79+
_ db: Database, protocolMessage: InlineProtocol.Reaction, publishChanges: Bool = false
80+
) throws -> Reaction {
81+
let reaction = Reaction(from: protocolMessage)
82+
try reaction.save(db)
83+
return reaction
84+
}
85+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,156 @@
1+
import Auth
2+
import Foundation
3+
import GRDB
4+
import InlineProtocol
5+
import Logger
6+
import MultipartFormDataKit
7+
import RealtimeAPI
8+
9+
public struct TransactionAddReaction: Transaction {
10+
// Properties
11+
var message: Message
12+
var emoji: String
13+
var userId: Int64
14+
var peerId: Peer
15+
16+
// Config
17+
public var id = UUID().uuidString
18+
var config = TransactionConfig.default
19+
var date = Date()
20+
21+
public init(message: Message, emoji: String, userId: Int64, peerId: Peer) {
22+
self.message = message
23+
self.emoji = emoji
24+
self.userId = userId
25+
self.peerId = peerId
26+
}
27+
28+
// Methods
29+
func optimistic() {
30+
Log.shared.debug("Optimistic add reaction \(message.messageId) \(peerId) \(message.chatId)")
31+
do {
32+
try AppDatabase.shared.dbWriter.write { db in
33+
let existing = try Reaction
34+
.filter(
35+
Column("messageId") == message.messageId && Column("chatId") == message
36+
.chatId && Column("emoji") == emoji
37+
).fetchOne(db)
38+
if existing != nil {
39+
Log.shared.info("Reaction with this emoji already exists")
40+
return
41+
} else {
42+
let reaction = Reaction(
43+
messageId: message.messageId,
44+
userId: userId,
45+
emoji: emoji,
46+
date: Date.now,
47+
chatId: message.chatId
48+
)
49+
try reaction.save(db)
50+
}
51+
}
52+
} catch {
53+
Log.shared.error("Failed to add reaction \(error)")
54+
}
55+
56+
Task { @MainActor in
57+
await MessagesPublisher.shared.messageUpdated(message: message, peer: peerId, animated: true)
58+
}
59+
}
60+
61+
func execute() async throws -> [InlineProtocol.Update] {
62+
let result = try await Realtime.shared.invoke(
63+
.addReaction,
64+
input: .addReaction(AddReactionInput.with {
65+
$0.peerID = peerId.toInputPeer()
66+
$0.messageID = message.messageId
67+
$0.emoji = emoji
68+
})
69+
)
70+
71+
guard case let .addReaction(response) = result else {
72+
throw AddReactionError.failed
73+
}
74+
75+
return response.updates
76+
}
77+
78+
func shouldRetryOnFail(error: Error) -> Bool {
79+
if let error = error as? RealtimeAPIError {
80+
switch error {
81+
case let .rpcError(_, _, code):
82+
switch code {
83+
case 400, 401:
84+
return false
85+
86+
default:
87+
return true
88+
}
89+
default:
90+
return true
91+
}
92+
}
93+
94+
return true
95+
}
96+
97+
func didSucceed(result: [InlineProtocol.Update]) async {
98+
await Realtime.shared.updates.applyBatch(updates: result)
99+
}
100+
101+
func didFail(error: Error?) async {
102+
Log.shared.error("Failed to delete message", error: error)
103+
104+
try? await AppDatabase.shared.dbWriter.write { db in
105+
let existing = try Reaction
106+
.filter(
107+
Column("messageId") == message.messageId && Column("chatId") == message
108+
.chatId && Column("emoji") == emoji
109+
).fetchOne(db)
110+
if existing != nil {
111+
Log.shared.info("Reaction with this emoji already exists")
112+
return
113+
} else {
114+
let reaction = Reaction(
115+
messageId: message.messageId,
116+
userId: userId,
117+
emoji: emoji,
118+
date: Date.now,
119+
chatId: message.chatId
120+
)
121+
try reaction.save(db)
122+
}
123+
}
124+
125+
Task { @MainActor in
126+
await MessagesPublisher.shared.messageUpdated(message: message, peer: peerId, animated: true)
127+
}
128+
}
129+
130+
func rollback() async {
131+
let _ = try? await AppDatabase.shared.dbWriter.write { db in
132+
let existing = try Reaction
133+
.filter(
134+
Column("messageId") == message.messageId && Column("chatId") == message
135+
.chatId && Column("emoji") == emoji
136+
).fetchOne(db)
137+
if existing != nil {
138+
Log.shared.info("Reaction with this emoji already exists")
139+
return
140+
} else {
141+
let reaction = Reaction(
142+
messageId: message.messageId,
143+
userId: userId,
144+
emoji: emoji,
145+
date: Date.now,
146+
chatId: message.chatId
147+
)
148+
try reaction.save(db)
149+
}
150+
}
151+
}
152+
153+
enum AddReactionError: Error {
154+
case failed
155+
}
156+
}

apple/InlineKit/Sources/InlineKit/Transactions/Transactions.swift

+9-1
Original file line numberDiff line numberDiff line change
@@ -97,7 +97,7 @@ public enum TransactionType: Codable {
9797
case sendMessage(TransactionSendMessage)
9898
case mockMessage(MockMessageTransaction)
9999
case deleteMessage(TransactionDeleteMessage)
100-
100+
case addReaction(TransactionAddReaction)
101101
var id: String {
102102
transaction.id
103103
// switch self {
@@ -114,6 +114,8 @@ public enum TransactionType: Codable {
114114
t
115115
case let .deleteMessage(t):
116116
t
117+
case let .addReaction(t):
118+
t
117119
}
118120
}
119121

@@ -146,6 +148,9 @@ public enum TransactionType: Codable {
146148
case let .deleteMessage(transaction):
147149
try container.encode("deleteMessage", forKey: .type)
148150
try container.encode(transaction, forKey: .transaction)
151+
case let .addReaction(transaction):
152+
try container.encode("addReaction", forKey: .type)
153+
try container.encode(transaction, forKey: .transaction)
149154
}
150155
}
151156

@@ -163,6 +168,9 @@ public enum TransactionType: Codable {
163168
case "deleteMessage":
164169
let transaction = try container.decode(TransactionDeleteMessage.self, forKey: .transaction)
165170
self = .deleteMessage(transaction)
171+
case "addReaction":
172+
let transaction = try container.decode(TransactionAddReaction.self, forKey: .transaction)
173+
self = .addReaction(transaction)
166174
default:
167175
throw DecodingError.dataCorruptedError(
168176
forKey: .type,

0 commit comments

Comments
 (0)