Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
60 changes: 60 additions & 0 deletions definitions/flipcash/protos/src/main/proto/blob/v1/model.proto
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
syntax = "proto3";

package flipcash.blob.v1;

option go_package = "github.com/code-payments/flipcash2-protobuf-api/generated/go/blob/v1;blobpb";
option java_package = "com.codeinc.flipcash.gen.blob.v1";
option objc_class_prefix = "FPBBlobV1";

import "validate/validate.proto";

// Opaque, client-held handle to a stored blob. This is the durable identity for
// the bytes; the bytes it points at are immutable once the upload is finalized.
message BlobId {
bytes value = 1 [(validate.rules).bytes = {
min_len: 16
max_len: 16
}];
}

// Server-authoritative metadata describing a stored blob. Never set by clients.
// With the exception of download_url, every field is intrinsic to the stored
// bytes and immutable, derived once by the server.
message BlobMetadata {
// MIME type (e.g. "image/jpeg").
string mime_type = 1 [(validate.rules).string = {
min_len: 1
max_len: 255
}];

// Total size of the blob in bytes.
uint64 size_bytes = 2 [(validate.rules).uint64.gte = 1];

// Ephemeral, server-minted URL for fetching the blob bytes. Unlike the
// other fields it is NOT intrinsic to the blob: it is re-issued on every
// fetch, may expire (signed URL with a short TTL), and is authorized at
// mint time, not at fetch time. Clients MUST NOT persist or cache it across
// fetches; treat the BlobId as the durable handle and this as disposable.
string download_url = 3 [(validate.rules).string = {
uri: true
max_len: 2048
}];

// Kind-specific metadata the server derived from the bytes. Exactly one
// variant is set for a recognized media kind; left unset for opaque blobs.
// Only images are supported today; video/audio/etc. will be added as new
// variants.
oneof kind {
ImageMetadata image = 4;
}
}

// Intrinsic descriptors for a still image.
message ImageMetadata {
// Pixel dimensions, for reserving layout before the bytes arrive.
uint32 width = 1 [(validate.rules).uint32.gte = 1];
uint32 height = 2 [(validate.rules).uint32.gte = 1];

// Compact preview shown while the full image downloads (BlurHash string).
string blurhash = 3 [(validate.rules).string.max_len = 64];
}
63 changes: 31 additions & 32 deletions definitions/flipcash/protos/src/main/proto/messaging/v1/model.proto
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ option go_package = "github.com/code-payments/flipcash2-protobuf-api/generated/g
option java_package = "com.codeinc.flipcash.gen.messaging.v1";
option objc_class_prefix = "FPBMessagingV1";

import "blob/v1/model.proto";
import "common/v1/common.proto";
import "google/protobuf/timestamp.proto";
import "validate/validate.proto";
Expand Down Expand Up @@ -140,9 +141,12 @@ message ReplyContent {
}];
}

// Media content (images, video, etc.)
// Media content from blobs the user has already uploaded. The following media
// types are supported:
// - Images
message MediaContent {
// The media items attached to this message
// The media items attached to this message. A single item today; raising
// this cap later enables albums (each item self-describes its kind).
repeated MediaItem items = 1 [(validate.rules).repeated = {
min_items: 1
max_items: 1
Expand All @@ -152,43 +156,38 @@ message MediaContent {
TextContent caption = 2;
}

// One logical media item, carried as its set of renditions (quality/size variants).
message MediaItem {
// Client-provided reference to media already uploaded out-of-band
MediaId media_id = 1 [(validate.rules).message.required = true];

// Server-authoritative metadata, resolved from the upload record. It is
// omitted on SendMessage and populated on stored/returned messages
MediaMetadata metadata = 2;
}

message MediaId {
bytes value = 1 [(validate.rules).bytes = {
min_len: 16
max_len: 16
// The renditions of this media, each an independently-stored blob. On
// SendMessage the client supplies exactly one ORIGINAL rendition (its
// blob_id); the server fills metadata and appends any derived renditions
// (e.g. a downscaled DISPLAY and a THUMBNAIL).
repeated MediaItemRendition renditions = 1 [(validate.rules).repeated = {
min_items: 1
}];
}

// Server-authoritative metadata describing an uploaded media item. Never set by
// clients; the server derives every field from the uploaded bytes.
message MediaMetadata {
// MIME type (e.g. "image/jpeg", "video/mp4")
string mime_type = 1 [(validate.rules).string = {
min_len: 1
max_len: 255
// A single stored variant of a MediaItem
message MediaItemRendition {
// The intended use of this rendition within the item.
Role role = 1 [(validate.rules).enum = {
not_in: [0]
}];
enum Role {
UNKNOWN = 0;
ORIGINAL = 1; // full-quality source the client uploaded
DISPLAY = 2; // downscaled/compressed for inline display
THUMBNAIL = 3; // tiny grid preview
}

// Total size of the media in bytes.
uint64 size_bytes = 2 [(validate.rules).uint64.gte = 1];

// Pixel dimensions, for reserving layout before the bytes arrive.
uint32 width = 3 [(validate.rules).uint32.gte = 1];
uint32 height = 4 [(validate.rules).uint32.gte = 1];

// Compact preview shown while the full media downloads (BlurHash string).
string blurhash = 5 [(validate.rules).string.max_len = 64];
// Handle to the blob holding this rendition's bytes. Client-set on the
// ORIGINAL at send time; server-set for derived renditions.
blob.v1.BlobId blob_id = 2 [(validate.rules).message.required = true];

// Duration in milliseconds for audio/video; 0 for stills.
uint64 duration_ms = 6;
// Server-authoritative blob metadata (mime type, size, download URL, and
// the image dimensions/preview), resolved from the blob record. Omitted on
// SendMessage and populated on stored/returned messages.
blob.v1.BlobMetadata blob = 3;
}

// System message content
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -151,10 +151,29 @@ internal fun MessageContent.asContent(): MessagingModel.Content {

internal fun com.flipcash.services.models.chat.MediaItem.asMediaItem(): MessagingModel.MediaItem {
return MessagingModel.MediaItem.newBuilder()
.setMediaId(MessagingModel.MediaId.newBuilder().setValue(mediaId.bytes.toByteString()))
.addAllRenditions(renditions.map { it.asRendition() })
.build()
}

internal fun com.flipcash.services.models.chat.MediaItemRendition.asRendition(): MessagingModel.MediaItemRendition {
return MessagingModel.MediaItemRendition.newBuilder()
.setRole(role.asProtoRole())
.setBlobId(
com.codeinc.flipcash.gen.blob.v1.Model.BlobId.newBuilder()
.setValue(blobId.bytes.toByteString())
)
.build()
}

internal fun com.flipcash.services.models.chat.MediaItemRendition.Role.asProtoRole(): MessagingModel.MediaItemRendition.Role {
return when (this) {
com.flipcash.services.models.chat.MediaItemRendition.Role.ORIGINAL -> MessagingModel.MediaItemRendition.Role.ORIGINAL
com.flipcash.services.models.chat.MediaItemRendition.Role.DISPLAY -> MessagingModel.MediaItemRendition.Role.DISPLAY
com.flipcash.services.models.chat.MediaItemRendition.Role.THUMBNAIL -> MessagingModel.MediaItemRendition.Role.THUMBNAIL
com.flipcash.services.models.chat.MediaItemRendition.Role.UNKNOWN -> MessagingModel.MediaItemRendition.Role.UNKNOWN
}
}

internal fun com.flipcash.services.models.chat.Emoji.asEmoji(): MessagingModel.Emoji {
return MessagingModel.Emoji.newBuilder().setValue(value).build()
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,11 @@ import com.flipcash.services.models.chat.ChatType
import com.flipcash.services.models.chat.ChatUpdate
import com.flipcash.services.models.chat.Emoji
import com.flipcash.services.models.chat.EmojiReaction
import com.flipcash.services.models.chat.MediaId
import com.flipcash.services.models.chat.BlobId
import com.flipcash.services.models.chat.BlobMetadata
import com.flipcash.services.models.chat.ImageMetadata
import com.flipcash.services.models.chat.MediaItem
import com.flipcash.services.models.chat.MediaMetadata
import com.flipcash.services.models.chat.MediaItemRendition
import com.flipcash.services.models.chat.MessageContent
import com.flipcash.services.models.chat.MessagePointer
import com.flipcash.services.models.chat.MetadataUpdate
Expand Down Expand Up @@ -145,19 +147,41 @@ internal fun MessagingModel.Content.toMessageContent(): MessageContent {

internal fun MessagingModel.MediaItem.toMediaItem(): MediaItem {
return MediaItem(
mediaId = MediaId(mediaId.value.toByteArray()),
metadata = if (hasMetadata()) metadata.toMediaMetadata() else null,
renditions = renditionsList.map { it.toMediaItemRendition() },
)
}

internal fun MessagingModel.MediaMetadata.toMediaMetadata(): MediaMetadata {
return MediaMetadata(
internal fun MessagingModel.MediaItemRendition.toMediaItemRendition(): MediaItemRendition {
return MediaItemRendition(
role = role.toRole(),
blobId = BlobId(blobId.value.toByteArray()),
blob = if (hasBlob()) blob.toBlobMetadata() else null,
)
}

internal fun MessagingModel.MediaItemRendition.Role.toRole(): MediaItemRendition.Role {
return when (this) {
MessagingModel.MediaItemRendition.Role.ORIGINAL -> MediaItemRendition.Role.ORIGINAL
MessagingModel.MediaItemRendition.Role.DISPLAY -> MediaItemRendition.Role.DISPLAY
MessagingModel.MediaItemRendition.Role.THUMBNAIL -> MediaItemRendition.Role.THUMBNAIL
else -> MediaItemRendition.Role.UNKNOWN
}
}

internal fun com.codeinc.flipcash.gen.blob.v1.Model.BlobMetadata.toBlobMetadata(): BlobMetadata {
return BlobMetadata(
mimeType = mimeType,
sizeBytes = sizeBytes,
downloadUrl = downloadUrl,
image = if (hasImage()) image.toImageMetadata() else null,
)
}

internal fun com.codeinc.flipcash.gen.blob.v1.Model.ImageMetadata.toImageMetadata(): ImageMetadata {
return ImageMetadata(
width = width,
height = height,
blurhash = blurhash,
durationMs = durationMs,
)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,4 @@ import kotlinx.serialization.Serializable

@Serializable
@JvmInline
value class MediaId(val bytes: ByteArray)
value class BlobId(val bytes: ByteArray)
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package com.flipcash.services.models.chat

import kotlinx.serialization.Serializable

@Serializable
data class BlobMetadata(
val mimeType: String,
val sizeBytes: Long,
val downloadUrl: String,
val image: ImageMetadata?,
)
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,8 @@ package com.flipcash.services.models.chat
import kotlinx.serialization.Serializable

@Serializable
data class MediaMetadata(
val mimeType: String,
val sizeBytes: Long,
data class ImageMetadata(
val width: Int,
val height: Int,
val blurhash: String,
val durationMs: Long,
)
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,5 @@ import kotlinx.serialization.Serializable

@Serializable
data class MediaItem(
val mediaId: MediaId,
val metadata: MediaMetadata?,
val renditions: List<MediaItemRendition>,
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package com.flipcash.services.models.chat

import kotlinx.serialization.Serializable

@Serializable
data class MediaItemRendition(
val role: Role,
val blobId: BlobId,
val blob: BlobMetadata?,
) {
enum class Role {
UNKNOWN,
ORIGINAL,
DISPLAY,
THUMBNAIL,
}
}
Loading