Skip to content

Commit 12f4a73

Browse files
committed
chore(services/flipcash): update protos and service implementations
Signed-off-by: Brandon McAnsh <git@bmcreations.dev>
1 parent e427e9a commit 12f4a73

11 files changed

Lines changed: 208 additions & 2 deletions

File tree

definitions/flipcash/protos/src/main/proto/activity/v1/activity_feed_service.proto

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@ service ActivityFeed {
1212
rpc GetLatestNotifications(GetLatestNotificationsRequest) returns (GetLatestNotificationsResponse);
1313
// GetPagedNotifications gets all notifications using a paging API.
1414
rpc GetPagedNotifications(GetPagedNotificationsRequest) returns (GetPagedNotificationsResponse);
15+
// GetBatchNotifications gets a batch of notifications by ID.
16+
rpc GetBatchNotifications(GetBatchNotificationsRequest) returns (GetBatchNotificationsResponse);
1517
}
1618
message GetLatestNotificationsRequest {
1719
// The activity feed to fetch notifications from
@@ -34,11 +36,24 @@ message GetPagedNotificationsRequest {
3436
common.v1.QueryOptions query_options = 2;
3537
common.v1.Auth auth = 3;
3638
}
37-
message GetPagedNotificationsResponse {
39+
message GetPagedNotificationsResponse {
3840
Result result = 1;
3941
enum Result {
4042
OK = 0;
4143
DENIED = 1;
4244
}
4345
repeated Notification notifications = 2 ;
4446
}
47+
message GetBatchNotificationsRequest {
48+
repeated NotificationId ids = 1 ;
49+
common.v1.Auth auth = 2;
50+
}
51+
message GetBatchNotificationsResponse {
52+
Result result = 1;
53+
enum Result {
54+
OK = 0;
55+
DENIED = 1;
56+
NOT_FOUND = 2;
57+
}
58+
repeated Notification notifications = 2 ;
59+
}

definitions/flipcash/protos/src/main/proto/activity/v1/model.proto

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@ message Notification {
2020
common.v1.PaymentAmount payment_amount = 3;
2121
// The timestamp of this notification
2222
google.protobuf.Timestamp ts = 4;
23+
// The state of this notification
24+
NotificationState state = 10;
2325
// Additional metadata for this notification specific to the notification
2426
oneof additional_metadata {
2527
WelcomeBonusNotificationMetadata welcome_bonus = 5;
@@ -49,3 +51,12 @@ enum ActivityFeedType {
4951
UNKNOWN = 0;
5052
TRANSACTION_HISTORY = 1; // Activity feed displayed under the Balance tab
5153
}
54+
// NotificationState determines the mutability of a notification, and whether
55+
// client should attempt to refetch state.
56+
enum NotificationState {
57+
NOTIFICATION_STATE_UNKNOWN = 0;
58+
// Notification state will change based on some app action in the future
59+
NOTIFICATION_STATE_PENDING = 1;
60+
// Notification state will not change
61+
NOTIFICATION_STATE_COMPLETED = 2;
62+
}

services/flipcash/src/main/kotlin/com/flipcash/services/internal/domain/ActivityFeedMessageMapper.kt

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import com.flipcash.services.internal.extensions.toPublicKey
66
import com.flipcash.services.internal.network.extensions.toId
77
import com.flipcash.services.models.ActivityFeedMessage
88
import com.flipcash.services.models.FeedMessageMetadata
9+
import com.flipcash.services.models.FeedMessageState
910
import com.getcode.opencode.internal.domain.mapper.Mapper
1011
import com.getcode.opencode.model.financial.CurrencyCode
1112
import com.getcode.opencode.model.financial.Fiat
@@ -28,6 +29,13 @@ internal class ActivityFeedMessageMapper @Inject constructor(
2829
)
2930
},
3031
timestamp = Instant.fromEpochSeconds(from.ts.seconds, 0),
32+
state = when (from.state) {
33+
Model.NotificationState.NOTIFICATION_STATE_PENDING -> FeedMessageState.PENDING
34+
Model.NotificationState.NOTIFICATION_STATE_COMPLETED -> FeedMessageState.COMPLETED
35+
Model.NotificationState.NOTIFICATION_STATE_UNKNOWN,
36+
Model.NotificationState.UNRECOGNIZED,
37+
null -> FeedMessageState.UNKNOWN
38+
},
3139
metadata = when (from.additionalMetadataCase) {
3240
Model.Notification.AdditionalMetadataCase.WELCOME_BONUS -> FeedMessageMetadata.WelcomeBonus
3341
Model.Notification.AdditionalMetadataCase.GAVE_USDC -> FeedMessageMetadata.GaveUsdc

services/flipcash/src/main/kotlin/com/flipcash/services/internal/network/api/ActivityFeedApi.kt

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,13 @@ import com.codeinc.flipcash.gen.activity.v1.ActivityFeedGrpc
44
import com.codeinc.flipcash.gen.activity.v1.ActivityFeedService
55
import com.codeinc.flipcash.gen.activity.v1.Model
66
import com.flipcash.services.internal.annotations.FlipcashManagedChannel
7+
import com.flipcash.services.internal.network.extensions.asQueryOptions
78
import com.flipcash.services.internal.network.extensions.authenticate
89
import com.flipcash.services.models.ActivityFeedType
10+
import com.flipcash.services.models.QueryOptions
911
import com.getcode.ed25519.Ed25519.KeyPair
1012
import com.getcode.opencode.internal.network.core.GrpcApi
13+
import com.getcode.opencode.model.core.ID
1114
import io.grpc.ManagedChannel
1215
import kotlinx.coroutines.flow.Flow
1316
import javax.inject.Inject
@@ -41,4 +44,49 @@ class ActivityFeedApi @Inject constructor(
4144
return api::getLatestNotifications
4245
.callAsCancellableFlow(request)
4346
}
47+
48+
/**
49+
* Gets all notifications using a paging API.
50+
*
51+
* @param owner The owner of the activity feed
52+
* @param type The activity feed to fetch notifications from
53+
* @param queryOptions The paging options
54+
*/
55+
fun getNotificationsPage(
56+
owner: KeyPair,
57+
type: ActivityFeedType,
58+
queryOptions: QueryOptions,
59+
): Flow<ActivityFeedService.GetPagedNotificationsResponse> {
60+
val request = ActivityFeedService.GetPagedNotificationsRequest.newBuilder()
61+
.setType(Model.ActivityFeedType.forNumber(type.ordinal))
62+
.setQueryOptions(queryOptions.asQueryOptions())
63+
.apply { setAuth(authenticate(owner)) }
64+
.build()
65+
66+
return api::getPagedNotifications
67+
.callAsCancellableFlow(request)
68+
}
69+
70+
/**
71+
* Gets a batch of notifications by ID.
72+
*
73+
* @param owner The owner of the activity feed
74+
* @param ids The notification IDs
75+
*/
76+
fun getNotificationsByIds(
77+
owner: KeyPair,
78+
ids: List<ID>,
79+
): Flow<ActivityFeedService.GetBatchNotificationsResponse> {
80+
val request = ActivityFeedService.GetBatchNotificationsRequest.newBuilder()
81+
.apply {
82+
ids.forEachIndexed { index, id ->
83+
addIds(index, Model.NotificationId.parseFrom(id.toByteArray()))
84+
}
85+
}.apply {
86+
setAuth(authenticate(owner))
87+
}.build()
88+
89+
return api::getBatchNotifications
90+
.callAsCancellableFlow(request)
91+
}
4492
}

services/flipcash/src/main/kotlin/com/flipcash/services/internal/network/extensions/LocalToProtobuf.kt

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
package com.flipcash.services.internal.network.extensions
22

33
import com.codeinc.flipcash.gen.common.v1.Common
4+
import com.flipcash.services.models.PagingToken
5+
import com.flipcash.services.models.QueryOptions
46
import com.getcode.ed25519.Ed25519.KeyPair
57
import com.getcode.opencode.model.core.ID
68
import com.getcode.utils.toByteString
@@ -16,4 +18,21 @@ internal fun KeyPair.asPublicKey(): Common.PublicKey {
1618

1719
internal fun ID.asUserId(): Common.UserId {
1820
return Common.UserId.newBuilder().setValue(toByteString()).build()
21+
}
22+
23+
internal fun QueryOptions.asQueryOptions(): Common.QueryOptions {
24+
return Common.QueryOptions.newBuilder()
25+
.setPageSize(this@asQueryOptions.limit)
26+
.setOrder(
27+
if (this@asQueryOptions.descending) Common.QueryOptions.Order.DESC
28+
else Common.QueryOptions.Order.ASC
29+
).apply {
30+
this@asQueryOptions.token?.let {
31+
setPagingToken(it.toPagingToken())
32+
}
33+
}.build()
34+
}
35+
36+
internal fun PagingToken.toPagingToken(): Common.PagingToken {
37+
return Common.PagingToken.newBuilder().setValue(this.toByteString()).build()
1938
}

services/flipcash/src/main/kotlin/com/flipcash/services/internal/network/services/ActivityFeedService.kt

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,10 @@ import com.flipcash.services.internal.network.api.ActivityFeedApi
66
import com.flipcash.services.internal.network.managedApiRequest
77
import com.flipcash.services.models.ActivityFeedType
88
import com.flipcash.services.models.GetActivityFeedMessagesError
9+
import com.flipcash.services.models.QueryOptions
910
import com.getcode.ed25519.Ed25519.KeyPair
1011
import com.getcode.opencode.internal.network.core.NetworkOracle
12+
import com.getcode.opencode.model.core.ID
1113
import javax.inject.Inject
1214

1315
internal class ActivityFeedService @Inject constructor(
@@ -34,4 +36,46 @@ internal class ActivityFeedService @Inject constructor(
3436
}
3537
)
3638
}
39+
40+
suspend fun getNotificationsPage(
41+
owner: KeyPair,
42+
type: ActivityFeedType,
43+
queryOptions: QueryOptions,
44+
): Result<List<Model.Notification>> {
45+
return networkOracle.managedApiRequest(
46+
call = { api.getNotificationsPage(owner, type, queryOptions) },
47+
handleResponse = { response ->
48+
when (response.result) {
49+
ActivityFeedService.GetPagedNotificationsResponse.Result.OK -> Result.success(response.notificationsList)
50+
ActivityFeedService.GetPagedNotificationsResponse.Result.DENIED -> Result.failure(GetActivityFeedMessagesError.Denied())
51+
ActivityFeedService.GetPagedNotificationsResponse.Result.UNRECOGNIZED -> Result.failure(GetActivityFeedMessagesError.Unrecognized())
52+
else -> Result.failure(GetActivityFeedMessagesError.Other())
53+
}
54+
},
55+
onOtherError = { cause ->
56+
Result.failure(GetActivityFeedMessagesError.Other(cause = cause))
57+
}
58+
)
59+
}
60+
61+
suspend fun getNotificationsByIds(
62+
owner: KeyPair,
63+
ids: List<ID>
64+
): Result<List<Model.Notification>> {
65+
return networkOracle.managedApiRequest(
66+
call = { api.getNotificationsByIds(owner, ids) },
67+
handleResponse = { response ->
68+
when (response.result) {
69+
ActivityFeedService.GetBatchNotificationsResponse.Result.OK -> Result.success(response.notificationsList)
70+
ActivityFeedService.GetBatchNotificationsResponse.Result.DENIED -> Result.failure(GetActivityFeedMessagesError.Denied())
71+
ActivityFeedService.GetBatchNotificationsResponse.Result.NOT_FOUND -> Result.failure(GetActivityFeedMessagesError.NotFound())
72+
ActivityFeedService.GetBatchNotificationsResponse.Result.UNRECOGNIZED -> Result.failure(GetActivityFeedMessagesError.Unrecognized())
73+
else -> Result.failure(GetActivityFeedMessagesError.Other())
74+
}
75+
},
76+
onOtherError = { cause ->
77+
Result.failure(GetActivityFeedMessagesError.Other(cause = cause))
78+
}
79+
)
80+
}
3781
}

services/flipcash/src/main/kotlin/com/flipcash/services/internal/repositories/InternalActivityFeedRepository.kt

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,15 @@ import com.flipcash.services.internal.domain.ActivityFeedMessageMapper
44
import com.flipcash.services.internal.network.services.ActivityFeedService
55
import com.flipcash.services.models.ActivityFeedMessage
66
import com.flipcash.services.models.ActivityFeedType
7+
import com.flipcash.services.models.QueryOptions
78
import com.flipcash.services.repository.ActivityFeedRepository
89
import com.getcode.ed25519.Ed25519
10+
import com.getcode.opencode.model.core.ID
911
import com.getcode.utils.ErrorUtils
1012

1113
internal class InternalActivityFeedRepository(
1214
private val service: ActivityFeedService,
1315
private val mapper: ActivityFeedMessageMapper
14-
1516
) : ActivityFeedRepository {
1617
override suspend fun getLatestNotifications(
1718
owner: Ed25519.KeyPair,
@@ -20,4 +21,19 @@ internal class InternalActivityFeedRepository(
2021
): Result<List<ActivityFeedMessage>> = service.getLatestNotifications(owner, type, maxItems)
2122
.onFailure { ErrorUtils.handleError(it) }
2223
.map { items -> items.map { mapper.map(it) } }
24+
25+
override suspend fun getPagedNotifications(
26+
owner: Ed25519.KeyPair,
27+
type: ActivityFeedType,
28+
queryOptions: QueryOptions
29+
): Result<List<ActivityFeedMessage>> = service.getNotificationsPage(owner, type, queryOptions)
30+
.onFailure { ErrorUtils.handleError(it) }
31+
.map { items -> items.map { mapper.map(it) } }
32+
33+
override suspend fun getBatchedNotifications(
34+
owner: Ed25519.KeyPair,
35+
ids: List<ID>
36+
): Result<List<ActivityFeedMessage>> = service.getNotificationsByIds(owner, ids)
37+
.onFailure { ErrorUtils.handleError(it) }
38+
.map { items -> items.map { mapper.map(it) } }
2339
}

services/flipcash/src/main/kotlin/com/flipcash/services/models/ActivityFeedMessage.kt

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,16 +15,38 @@ import kotlinx.datetime.Instant
1515
* @param text The localized title text for the message
1616
* @param amount If a payment applies, the amount that was paid
1717
* @param timestamp The timestamp of this message
18+
* @param state The state of this notification
1819
* @param metadata Additional metadata for this message specific to the message
1920
*/
2021
data class ActivityFeedMessage(
2122
val id: ID,
2223
val text: String,
2324
val amount: LocalFiat?,
2425
val timestamp: Instant,
26+
val state: FeedMessageState,
2527
val metadata: FeedMessageMetadata?
2628
)
2729

30+
/**
31+
* Determines the mutability of a message, and whether client should attempt to refetch state.
32+
*/
33+
enum class FeedMessageState {
34+
/**
35+
* ¯\_(ツ)_/¯
36+
*/
37+
UNKNOWN,
38+
39+
/**
40+
* Message state will change based on some app action in the future
41+
*/
42+
PENDING,
43+
44+
/**
45+
* Message state will not change
46+
*/
47+
COMPLETED
48+
}
49+
2850
sealed interface FeedMessageMetadata {
2951
data object Unknown : FeedMessageMetadata
3052
data object WelcomeBonus : FeedMessageMetadata

services/flipcash/src/main/kotlin/com/flipcash/services/models/Errors.kt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,5 +65,6 @@ sealed class GetActivityFeedMessagesError(
6565
) : CodeServerError(message, cause) {
6666
class Denied : GetActivityFeedMessagesError()
6767
class Unrecognized : GetActivityFeedMessagesError()
68+
class NotFound: GetActivityFeedMessagesError()
6869
data class Other(override val cause: Throwable? = null) : GetActivityFeedMessagesError(cause = cause)
6970
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
package com.flipcash.services.models
2+
3+
typealias PagingToken = List<Byte>
4+
5+
data class QueryOptions(
6+
val limit: Int = 100,
7+
val token: PagingToken? = null,
8+
val descending: Boolean = true
9+
)

0 commit comments

Comments
 (0)