-
Notifications
You must be signed in to change notification settings - Fork 46
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Support for media posts #36
Comments
Media post proposalCurrent statusCurrently (inside #40) the type Post struct {
PostID PostID `json:"id"`
ParentID PostID `json:"parent_id"`
Message string `json:"message"`
Created int64 `json:"created"` // Block height at which the post has been created
LastEdited int64 `json:"last_edited"` // Block height at which the post has been edited the last time
Owner sdk.AccAddress `json:"owner"`
} Media post implementationInstead of adding multiple fields into the above // PostMedia represents a media file associated to a post
type PostMedia struct {
Uri string `json:"uri"`
MimeType string `json:"mime_type"`
}
// Post is a struct of a Magpie post
type Post struct {
PostID PostID `json:"id"`
ParentID PostID `json:"parent_id"`
Message string `json:"message"`
Medias []PostMedia `json:"medias"` // List of medias associated to this post
Created int64 `json:"created"`
LastEdited int64 `json:"last_edited"`
Owner sdk.AccAddress `json:"owner"`
} In this way we can easily support multiple medias being added to a single post without any problem. Validity checksDuring the Post creation, the following checks should be implemented:
About the URIHaving such a generic field named
To solve these proplems, another field could be added: type PostMedia struct {
Provider string `json:"provider"`
Uri string `json:"uri"`
MimeType string `json:"mime_type"`
} In this way the user would have to select from a list of providers before inputting the
Also, new providers could later be added from the governance specifying the identifier and the regex to be used during the message validation. About the MIME typeI'm currently not sure whether we should check the content of the Pros
Cons
ConclusionsI'd personally go with an implementation such as // PostMedia represents a media file associated to a post
type PostMedia struct {
Provider string `json:"provider"`
Uri string `json:"uri"`
MimeType string `json:"mime_type"`
}
// Post is a struct of a Magpie post
type Post struct {
PostID PostID `json:"id"`
ParentID PostID `json:"parent_id"`
Message string `json:"message"`
Medias []PostMedia `json:"medias"` // List of medias associated to this post
Created int64 `json:"created"`
LastEdited int64 `json:"last_edited"`
Owner sdk.AccAddress `json:"owner"`
} Where each possible valid type Provider struct {
Id string `json:"id"`
RegEx string `json:"regex"`
}
type GenesisState struct {
Providers []Provider `json:"providers"`
MimeType []string `json:"mime_types"`
} Such lists can be used as reference to know what providers and MIME types are supported. |
After reviewing this implementation and testing some other ones with the Amino encoder, I think we can go another way too. Second Media Post implementation proposalInstead of adding a new field into the original // Post is a struct of a Magpie post
type Post struct {
PostID PostID `json:"id"`
ParentID PostID `json:"parent_id"`
Message string `json:"message"`
Medias []PostMedia `json:"medias"` // List of medias associated to this post
Created int64 `json:"created"`
LastEdited int64 `json:"last_edited"`
Owner sdk.AccAddress `json:"owner"`
} What we can do is define a new type, called type MediaPost struct {
Post
Medias []PostMedia `json:"medias"`
} The // PostMedia represents a media file associated to a post
type PostMedia struct {
Provider string `json:"provider"`
Uri string `json:"uri"`
MimeType string `json:"mime_type"`
} Pros and consPros
Cons
|
I'm up for the 2nd proposal. Building custom marshal/unmarshal methods is not a big deal. It makes the code cleaner and no media file is added to normal post. This is how the We cannot and not necessary to verify the mime types on-chain. It depends on how the applications work and specify it as the uploading and rendering parts won't be done on-chain. The URI should be a valid URI which would have direct access to the file. The chain doesn't have to understand the protocol of the URI. For example, if the application uploaded a file to IPFS and get a hash, the application might want to store |
Should we then use the generic Web 2 URI RegEx to verify if it's a valid URI?
This only makes sure the URI starts with |
And IPFS if they don't use web gateway? |
🤔 This is going to be hard then. We have two options then:
I'd personally go for the first one, as it might be better for mobile clients that most of the time do not have other access to media resources other than the standard Web2. |
Ok just keep everything with |
Let me recap if I understood everything properly:
Everything right? |
Yes, correct. Please note that that RegEx might not be enough/correct. We should search online for other options and create a proper test suite to test it. |
Cool. I'll look deeper for that. |
Do you see any useful thing inside this: https://mathiasbynens.be/demo/url-regex? I've also discovered that using IntelliJ you can check regEx directly from the IDE (but i'm not so sure about how it works). Do I have also to implement a new type of message? Or just the type for now? |
Probably the
I don't know how it works, I've never used it. I usually refer to regex101 to test regex before using them.
Yes, a new message must be implemented to allow users to create such post types. |
I'm already implemented it with yours. But it might be useful to test some of them. I will give a try to
All right! 👍 |
I was thinking about how to properly save 1. Reuse the current methodReuse func (k Keeper) SaveMedia(ctx sdk.Context, mediaPost types.MediaPost) {
store := ctx.KVStore(k.StoreKey)
//Save the post inside mediaPost
k.SavePost(ctx, mediaPost.Post)
//Save the medias associated with mediaPost.Post ID
store.Set(
[]byte(types.MediaPostStorePrefix+mediaPost.Post.PostID.String()),
k.Cdc.MustMarshalBinaryBare(mediaPost.Medias)
)
} PROS
CONS
2. Create a new methodDon't reuse and save the entire func (k Keeper) SaveMedia(ctx sdk.Context, mediaPost types.MediaPost) {
store := ctx.KVStore(k.StoreKey)
store.Set([]byte(types.MediaPostStorePrefix+mediaPost.Post.PostID.String()), k.Cdc.MustMarshalBinaryBare(&mediaPost))
// Set the last post id only if the current post has a greater one than the last one stored
if mediaPost.Post.PostID > k.GetLastPostID(ctx) {
store.Set([]byte(types.LastPostIDStoreKey), k.Cdc.MustMarshalBinaryBare(&mediaPost.Post.PostID))
}
// Save the comments to the parent post, if it is valid
if mediaPost.Post.ParentID.Valid() {
parentCommentsKey := []byte(types.PostCommentsStorePrefix + mediaPost.Post.ParentID.String())
var commentsIDs types.PostIDs
k.Cdc.MustUnmarshalBinaryBare(store.Get(parentCommentsKey), &commentsIDs)
if editedIDs, appended := commentsIDs.AppendIfMissing(mediaPost.Post.PostID); appended {
store.Set(parentCommentsKey, k.Cdc.MustMarshalBinaryBare(&editedIDs))
}
}
} PROS
CONS
I'd personally go for the First one, as it seems to me a more reasonable approach. |
@bragaz I personally think there is a more elegant and clean way of handling this, much like it's being done inside Tendermint's Implementation through interfacesWhat we could do is define the following interface: // Represents the interface that each post should implement
type PostI interface {
Validate() error // Tells if the post is valid or not
GetID() PostID // Returns the ID of the post
GetParentID() PostID // Returns the ParentID of the post
Equals(PostI) bool // Tells if this post has the same contents of the one given
} Then what we would have to do is implementing such interface in both type Post struct { ... }
func NewPost (...) Post { ... }
// GetID implements PostI
func (p Post) GetID() PostID {
return p.PostID
}
// GetParentID implements PostI
func (p Post) GetParentID() PostID {
return p.ParentID
}
// Validate implements validator
func (p Post) Validate() error { ... }
func (p Post) Equals(other PostI) bool {
// Cast and delegate
if otherPost, ok := other.(Post); ok {
return checkPostsEqual(p, otherPost)
} else {
return false
}
}
func checkPostsEqual(first, second Post) bool {
equalsOptionalData := len(first.OptionalData) == len(second.OptionalData)
if equalsOptionalData {
for key := range first.OptionalData {
equalsOptionalData = equalsOptionalData && first.OptionalData[key] == second.OptionalData[key]
}
}
return first.PostID.Equals(second.PostID) &&
first.ParentID.Equals(second.ParentID) &&
first.Message == second.Message &&
first.Created.Equal(second.Created) &&
first.LastEdited.Equal(second.LastEdited) &&
first.AllowsComments == second.AllowsComments &&
first.Subspace == second.Subspace &&
equalsOptionalData &&
first.Creator.Equals(second.Creator)
} and type MediaPost struct { }
func NewMediaPost ( ... ) MediaPost { ... }
// GetID implements PostI
func (mp MediaPost) GetID() PostID {
return mp.PostID
}
// GetParentID implements PostI
func (mp MediaPost) GetParentID() PostID {
return mp.ParentID
}
func (mp MediaPost) Validate() error { ... }
func (mp MediaPost) Equals(other PostI) bool {
// Cast and delegate
if otherMp, ok := other.(MediaPost); ok {
return checkMediaPostEquals(mp, otherMp)
} else {
return false
}
}
func checkMediaPostEquals(first, second MediaPost) bool {
if !first.Post.Equals(second.Post) {
return false
}
if len(first.Medias) != len(second.Medias) {
return false
}
for index, media := range first.Medias {
if media != second.Medias[index] {
return false
}
}
return true
} Codec changesDue to how Amino works, we could simply register the interface and the concrete types inside the func RegisterCodec(cdc *codec.Codec) {
// ...
cdc.RegisterInterface((*PostI)(nil), nil)
cdc.RegisterConcrete(Post{}, "text", nil)
cdc.RegisterConcrete(MediaPost{}, "media", nil)
} Keeper changesWith this trick, we can use just the func (k Keeper) SavePost(ctx sdk.Context, post types.PostI) {
store := ctx.KVStore(k.StoreKey)
// Save the post
store.Set([]byte(types.PostStorePrefix+post.GetID().String()), k.Cdc.MustMarshalBinaryBare(&post))
// Set the last post id only if the current post has a greater one than the last one stored
if id := post.GetID(); id > k.GetLastPostID(ctx) {
store.Set([]byte(types.LastPostIDStoreKey), k.Cdc.MustMarshalBinaryBare(&id))
}
// Save the comments to the parent post, if it is valid
if post.GetParentID().Valid() {
parentCommentsKey := []byte(types.PostCommentsStorePrefix + post.GetParentID().String())
var commentsIDs types.PostIDs
k.Cdc.MustUnmarshalBinaryBare(store.Get(parentCommentsKey), &commentsIDs)
if editedIDs, appended := commentsIDs.AppendIfMissing(post.GetID()); appended {
store.Set(parentCommentsKey, k.Cdc.MustMarshalBinaryBare(&editedIDs))
}
}
} Reading of postsPlease note that the func (k Keeper) GetPost(ctx sdk.Context, id types.PostID) (post types.PostI, found bool) {
store := ctx.KVStore(k.StoreKey)
key := k.getPostStoreKey(id)
if !store.Has(key) {
return types.Post{}, false
}
k.Cdc.MustUnmarshalBinaryBare(store.Get(key), &post)
return post, true
}
|
The whole solution seems reasonable and better than the others to me, using interfaces is the way.
I agree more with this naming because it's more natural and easier to understand than something like |
Propose adding a
Media
andMediaType
field inPost
module for storing a media file in a post. Similar to an image of a Twitter tweet or an image post of Instagram.This field is optional.
If it exists, validate if the
Media
value is a valid url.MediaType
should be an option from a list of mime types.The text was updated successfully, but these errors were encountered: