feat(community)_: Move images from community data to MediaServer (#5336)
* feat(community)_: Move images from community data to MediaServer * test_: fix lint issue * test_: add more test statements * feat_: deprecate old API * test_: addressed review feedback from Icaro * fix_: addressed review feedback from Jonathan * chore_:wrap image url in an object
This commit is contained in:
parent
39dffd8b70
commit
e0673ad1ff
|
@ -27,6 +27,7 @@ import (
|
||||||
"github.com/status-im/status-go/protocol/protobuf"
|
"github.com/status-im/status-go/protocol/protobuf"
|
||||||
"github.com/status-im/status-go/protocol/requests"
|
"github.com/status-im/status-go/protocol/requests"
|
||||||
"github.com/status-im/status-go/protocol/v1"
|
"github.com/status-im/status-go/protocol/v1"
|
||||||
|
"github.com/status-im/status-go/server"
|
||||||
)
|
)
|
||||||
|
|
||||||
const signatureLength = 65
|
const signatureLength = 65
|
||||||
|
@ -255,6 +256,162 @@ func (o *Community) MarshalPublicAPIJSON() ([]byte, error) {
|
||||||
return json.Marshal(communityItem)
|
return json.Marshal(communityItem)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (o *Community) MarshalJSONWithMediaServer(mediaServer *server.MediaServer) ([]byte, error) {
|
||||||
|
if o.config.MemberIdentity == nil {
|
||||||
|
return nil, errors.New("member identity not set")
|
||||||
|
}
|
||||||
|
|
||||||
|
type Image struct {
|
||||||
|
Uri string `json:"uri"`
|
||||||
|
}
|
||||||
|
communityItem := struct {
|
||||||
|
ID types.HexBytes `json:"id"`
|
||||||
|
MemberRole protobuf.CommunityMember_Roles `json:"memberRole"`
|
||||||
|
IsControlNode bool `json:"isControlNode"`
|
||||||
|
Verified bool `json:"verified"`
|
||||||
|
Joined bool `json:"joined"`
|
||||||
|
JoinedAt int64 `json:"joinedAt"`
|
||||||
|
Spectated bool `json:"spectated"`
|
||||||
|
RequestedAccessAt int `json:"requestedAccessAt"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
Description string `json:"description"`
|
||||||
|
IntroMessage string `json:"introMessage"`
|
||||||
|
OutroMessage string `json:"outroMessage"`
|
||||||
|
Tags []CommunityTag `json:"tags"`
|
||||||
|
Chats map[string]CommunityChat `json:"chats"`
|
||||||
|
Categories map[string]CommunityCategory `json:"categories"`
|
||||||
|
Images map[string]Image `json:"images"`
|
||||||
|
Permissions *protobuf.CommunityPermissions `json:"permissions"`
|
||||||
|
Members map[string]*protobuf.CommunityMember `json:"members"`
|
||||||
|
CanRequestAccess bool `json:"canRequestAccess"`
|
||||||
|
CanManageUsers bool `json:"canManageUsers"` //TODO: we can remove this
|
||||||
|
CanDeleteMessageForEveryone bool `json:"canDeleteMessageForEveryone"` //TODO: we can remove this
|
||||||
|
CanJoin bool `json:"canJoin"`
|
||||||
|
Color string `json:"color"`
|
||||||
|
RequestedToJoinAt uint64 `json:"requestedToJoinAt,omitempty"`
|
||||||
|
IsMember bool `json:"isMember"`
|
||||||
|
Muted bool `json:"muted"`
|
||||||
|
MuteTill time.Time `json:"muteTill,omitempty"`
|
||||||
|
CommunityAdminSettings CommunityAdminSettings `json:"adminSettings"`
|
||||||
|
Encrypted bool `json:"encrypted"`
|
||||||
|
PendingAndBannedMembers map[string]CommunityMemberState `json:"pendingAndBannedMembers"`
|
||||||
|
TokenPermissions map[string]*CommunityTokenPermission `json:"tokenPermissions"`
|
||||||
|
CommunityTokensMetadata []*protobuf.CommunityTokenMetadata `json:"communityTokensMetadata"`
|
||||||
|
ActiveMembersCount uint64 `json:"activeMembersCount"`
|
||||||
|
PubsubTopic string `json:"pubsubTopic"`
|
||||||
|
PubsubTopicKey string `json:"pubsubTopicKey"`
|
||||||
|
Shard *shard.Shard `json:"shard"`
|
||||||
|
LastOpenedAt int64 `json:"lastOpenedAt"`
|
||||||
|
Clock uint64 `json:"clock"`
|
||||||
|
}{
|
||||||
|
ID: o.ID(),
|
||||||
|
Clock: o.Clock(),
|
||||||
|
MemberRole: o.MemberRole(o.MemberIdentity()),
|
||||||
|
IsControlNode: o.IsControlNode(),
|
||||||
|
Verified: o.config.Verified,
|
||||||
|
Chats: make(map[string]CommunityChat),
|
||||||
|
Categories: make(map[string]CommunityCategory),
|
||||||
|
Joined: o.config.Joined,
|
||||||
|
JoinedAt: o.config.JoinedAt,
|
||||||
|
Spectated: o.config.Spectated,
|
||||||
|
CanRequestAccess: o.CanRequestAccess(o.config.MemberIdentity),
|
||||||
|
CanJoin: o.canJoin(),
|
||||||
|
CanManageUsers: o.CanManageUsers(o.config.MemberIdentity),
|
||||||
|
CanDeleteMessageForEveryone: o.CanDeleteMessageForEveryone(o.config.MemberIdentity),
|
||||||
|
RequestedToJoinAt: o.RequestedToJoinAt(),
|
||||||
|
IsMember: o.isMember(),
|
||||||
|
Muted: o.config.Muted,
|
||||||
|
MuteTill: o.config.MuteTill,
|
||||||
|
Tags: o.Tags(),
|
||||||
|
Encrypted: o.Encrypted(),
|
||||||
|
PubsubTopic: o.PubsubTopic(),
|
||||||
|
PubsubTopicKey: o.PubsubTopicKey(),
|
||||||
|
Shard: o.Shard(),
|
||||||
|
LastOpenedAt: o.config.LastOpenedAt,
|
||||||
|
}
|
||||||
|
if o.config.CommunityDescription != nil {
|
||||||
|
for id, c := range o.config.CommunityDescription.Categories {
|
||||||
|
category := CommunityCategory{
|
||||||
|
ID: id,
|
||||||
|
Name: c.Name,
|
||||||
|
Position: int(c.Position),
|
||||||
|
}
|
||||||
|
communityItem.Encrypted = o.Encrypted()
|
||||||
|
communityItem.Categories[id] = category
|
||||||
|
}
|
||||||
|
for id, c := range o.config.CommunityDescription.Chats {
|
||||||
|
// NOTE: Here `CanPost` is only set for ChatMessage. But it can be different for reactions/pin/etc.
|
||||||
|
// Consider adding more properties to `CommunityChat` to reflect that.
|
||||||
|
canPost, err := o.CanPost(o.config.MemberIdentity, id, protobuf.ApplicationMetadataMessage_CHAT_MESSAGE)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
canPostReactions, err := o.CanPost(o.config.MemberIdentity, id, protobuf.ApplicationMetadataMessage_EMOJI_REACTION)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
canView := o.CanView(o.config.MemberIdentity, id)
|
||||||
|
|
||||||
|
chat := CommunityChat{
|
||||||
|
ID: id,
|
||||||
|
Name: c.Identity.DisplayName,
|
||||||
|
Emoji: c.Identity.Emoji,
|
||||||
|
Color: c.Identity.Color,
|
||||||
|
Description: c.Identity.Description,
|
||||||
|
Permissions: c.Permissions,
|
||||||
|
Members: c.Members,
|
||||||
|
CanPost: canPost,
|
||||||
|
CanView: canView,
|
||||||
|
CanPostReactions: canPostReactions,
|
||||||
|
ViewersCanPostReactions: c.ViewersCanPostReactions,
|
||||||
|
TokenGated: o.channelEncrypted(id),
|
||||||
|
CategoryID: c.CategoryId,
|
||||||
|
HideIfPermissionsNotMet: c.HideIfPermissionsNotMet,
|
||||||
|
Position: int(c.Position),
|
||||||
|
}
|
||||||
|
communityItem.Chats[id] = chat
|
||||||
|
}
|
||||||
|
communityItem.TokenPermissions = o.tokenPermissions()
|
||||||
|
communityItem.PendingAndBannedMembers = o.PendingAndBannedMembers()
|
||||||
|
communityItem.Members = o.config.CommunityDescription.Members
|
||||||
|
communityItem.Permissions = o.config.CommunityDescription.Permissions
|
||||||
|
communityItem.IntroMessage = o.config.CommunityDescription.IntroMessage
|
||||||
|
communityItem.OutroMessage = o.config.CommunityDescription.OutroMessage
|
||||||
|
|
||||||
|
// update token meta image to url rather than base64 image
|
||||||
|
var tokenMetadata []*protobuf.CommunityTokenMetadata
|
||||||
|
for _, m := range o.config.CommunityDescription.CommunityTokensMetadata {
|
||||||
|
copyM := proto.Clone(m).(*protobuf.CommunityTokenMetadata)
|
||||||
|
copyM.Image = mediaServer.MakeCommunityDescriptionTokenImageURL(o.IDString(), copyM.GetSymbol())
|
||||||
|
tokenMetadata = append(tokenMetadata, copyM)
|
||||||
|
}
|
||||||
|
communityItem.CommunityTokensMetadata = tokenMetadata
|
||||||
|
|
||||||
|
communityItem.ActiveMembersCount = o.config.CommunityDescription.ActiveMembersCount
|
||||||
|
|
||||||
|
if o.config.CommunityDescription.Identity != nil {
|
||||||
|
communityItem.Name = o.Name()
|
||||||
|
communityItem.Color = o.config.CommunityDescription.Identity.Color
|
||||||
|
communityItem.Description = o.config.CommunityDescription.Identity.Description
|
||||||
|
for t := range o.config.CommunityDescription.Identity.Images {
|
||||||
|
if communityItem.Images == nil {
|
||||||
|
communityItem.Images = make(map[string]Image)
|
||||||
|
}
|
||||||
|
communityItem.Images[t] = Image{Uri: mediaServer.MakeCommunityImageURL(o.IDString(), t)}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
communityItem.CommunityAdminSettings = CommunityAdminSettings{
|
||||||
|
PinMessageAllMembersEnabled: false,
|
||||||
|
}
|
||||||
|
|
||||||
|
if o.config.CommunityDescription.AdminSettings != nil {
|
||||||
|
communityItem.CommunityAdminSettings.PinMessageAllMembersEnabled = o.config.CommunityDescription.AdminSettings.PinMessageAllMembersEnabled
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return json.Marshal(communityItem)
|
||||||
|
}
|
||||||
|
|
||||||
func (o *Community) MarshalJSON() ([]byte, error) {
|
func (o *Community) MarshalJSON() ([]byte, error) {
|
||||||
if o.config.MemberIdentity == nil {
|
if o.config.MemberIdentity == nil {
|
||||||
return nil, errors.New("member identity not set")
|
return nil, errors.New("member identity not set")
|
||||||
|
|
|
@ -4,11 +4,14 @@ import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
"crypto/ecdsa"
|
"crypto/ecdsa"
|
||||||
|
"crypto/tls"
|
||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
|
"net/http"
|
||||||
|
"net/url"
|
||||||
"os"
|
"os"
|
||||||
"reflect"
|
"reflect"
|
||||||
"strings"
|
"strings"
|
||||||
|
@ -4348,6 +4351,169 @@ func (s *MessengerCommunitiesSuite) sendImageToCommunity(sender *Messenger, chat
|
||||||
return sentMessage
|
return sentMessage
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *MessengerCommunitiesSuite) TestSerializedCommunities() {
|
||||||
|
community, _ := s.createCommunity()
|
||||||
|
addMediaServer := func(messenger *Messenger) {
|
||||||
|
mediaServer, err := server.NewMediaServer(messenger.database, nil, nil, nil)
|
||||||
|
s.Require().NoError(err)
|
||||||
|
s.Require().NoError(mediaServer.Start())
|
||||||
|
messenger.httpServer = mediaServer
|
||||||
|
}
|
||||||
|
addMediaServer(s.owner)
|
||||||
|
|
||||||
|
// update community description
|
||||||
|
description := community.Description()
|
||||||
|
identImageName := "small"
|
||||||
|
identImagePayload := []byte("123")
|
||||||
|
description.Identity = &protobuf.ChatIdentity{
|
||||||
|
Images: map[string]*protobuf.IdentityImage{
|
||||||
|
identImageName: {
|
||||||
|
Payload: identImagePayload,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
// #nosec G101
|
||||||
|
tokenImageInBase64 := "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mP8/wcAAwAB/tdgBUgAAAAASUVORK5CYII="
|
||||||
|
description.CommunityTokensMetadata = []*protobuf.CommunityTokenMetadata{
|
||||||
|
{
|
||||||
|
Image: tokenImageInBase64,
|
||||||
|
Symbol: "STT",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
description.Clock = description.Clock + 1
|
||||||
|
community.Edit(description)
|
||||||
|
s.Require().NoError(s.owner.communitiesManager.SaveCommunity(community))
|
||||||
|
|
||||||
|
// check edit was successful
|
||||||
|
b, err := s.owner.communitiesManager.GetByID(community.ID())
|
||||||
|
s.Require().NoError(err)
|
||||||
|
s.Require().NotNil(b)
|
||||||
|
s.Len(b.Description().CommunityTokensMetadata, 1)
|
||||||
|
s.Equal(tokenImageInBase64, b.Description().CommunityTokensMetadata[0].Image)
|
||||||
|
s.Len(b.Description().Identity.Images, 1)
|
||||||
|
s.Equal(identImagePayload, b.Description().Identity.Images[identImageName].Payload)
|
||||||
|
|
||||||
|
c, err := s.owner.SerializedCommunities()
|
||||||
|
s.Require().NoError(err)
|
||||||
|
s.Require().Len(c, 1)
|
||||||
|
d, err := json.Marshal(c)
|
||||||
|
s.Require().NoError(err)
|
||||||
|
|
||||||
|
type Image struct {
|
||||||
|
Uri string `json:"uri"`
|
||||||
|
}
|
||||||
|
var communityData []struct {
|
||||||
|
Images map[string]Image `json:"images"`
|
||||||
|
CommunityTokensMetadata []struct {
|
||||||
|
Image string `json:"image"`
|
||||||
|
Symbol string `json:"symbol"`
|
||||||
|
} `json:"communityTokensMetadata"`
|
||||||
|
}
|
||||||
|
err = json.Unmarshal(d, &communityData)
|
||||||
|
s.Require().NoError(err)
|
||||||
|
// Check community description image
|
||||||
|
s.Require().NotEmpty(communityData[0].Images[identImageName])
|
||||||
|
image := communityData[0].Images[identImageName]
|
||||||
|
s.T().Log(fmt.Sprintf("Image URL (%s):", identImageName), image)
|
||||||
|
e, err := s.fetchImage(image.Uri)
|
||||||
|
s.Require().NoError(err)
|
||||||
|
s.Require().Equal(identImagePayload, e)
|
||||||
|
|
||||||
|
imageUrlWithoutCommunityID, err := s.removeUrlParam(image.Uri, "communityID")
|
||||||
|
s.Require().NoError(err)
|
||||||
|
e, err = s.fetchImage(imageUrlWithoutCommunityID)
|
||||||
|
s.Require().NoError(err)
|
||||||
|
s.Require().Len(e, 0)
|
||||||
|
|
||||||
|
imageUrlWithWrongCommunityID, err := s.updateUrlParam(image.Uri, "communityID", "0x0")
|
||||||
|
s.Require().NoError(err)
|
||||||
|
e, err = s.fetchImage(imageUrlWithWrongCommunityID)
|
||||||
|
s.Require().NoError(err)
|
||||||
|
s.Require().Len(e, 0)
|
||||||
|
|
||||||
|
// Check communityTokensMetadata image
|
||||||
|
s.Require().NotEmpty(communityData[0].CommunityTokensMetadata)
|
||||||
|
tokenImageUrl := communityData[0].CommunityTokensMetadata[0].Image
|
||||||
|
s.T().Log("Community Token Metadata Image:", tokenImageUrl)
|
||||||
|
s.T().Log("Community Token Metadata Symbol:", communityData[0].CommunityTokensMetadata[0].Symbol)
|
||||||
|
f, err := s.fetchImage(tokenImageUrl)
|
||||||
|
s.Require().NoError(err)
|
||||||
|
tokenImagePayload, err := images.GetPayloadFromURI(tokenImageInBase64)
|
||||||
|
s.Require().NoError(err)
|
||||||
|
s.Require().Equal(tokenImagePayload, f)
|
||||||
|
|
||||||
|
tokenImageUrlWithoutCommunityID, err := s.removeUrlParam(tokenImageUrl, "communityID")
|
||||||
|
s.Require().NoError(err)
|
||||||
|
f, err = s.fetchImage(tokenImageUrlWithoutCommunityID)
|
||||||
|
s.Require().NoError(err)
|
||||||
|
s.Require().Len(f, 0)
|
||||||
|
|
||||||
|
tokenImageUrlWithWrongCommunityID, err := s.updateUrlParam(tokenImageUrl, "communityID", "0x0")
|
||||||
|
s.Require().NoError(err)
|
||||||
|
f, err = s.fetchImage(tokenImageUrlWithWrongCommunityID)
|
||||||
|
s.Require().NoError(err)
|
||||||
|
s.Require().Len(f, 0)
|
||||||
|
|
||||||
|
tokenImageUrlWithoutSymbol, err := s.removeUrlParam(tokenImageUrl, "symbol")
|
||||||
|
s.Require().NoError(err)
|
||||||
|
f, err = s.fetchImage(tokenImageUrlWithoutSymbol)
|
||||||
|
s.Require().NoError(err)
|
||||||
|
s.Require().Len(f, 0)
|
||||||
|
|
||||||
|
tokenImageUrlWithWrongSymbol, err := s.updateUrlParam(tokenImageUrl, "symbol", "WRONG")
|
||||||
|
s.Require().NoError(err)
|
||||||
|
f, err = s.fetchImage(tokenImageUrlWithWrongSymbol)
|
||||||
|
s.Require().NoError(err)
|
||||||
|
s.Require().Len(f, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *MessengerCommunitiesSuite) updateUrlParam(rawURL string, name, val string) (string, error) {
|
||||||
|
parsedURL, err := url.Parse(rawURL)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
queryParams := parsedURL.Query()
|
||||||
|
queryParams.Set(name, val)
|
||||||
|
parsedURL.RawQuery = queryParams.Encode()
|
||||||
|
return parsedURL.String(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *MessengerCommunitiesSuite) removeUrlParam(rawURL, name string) (string, error) {
|
||||||
|
parsedURL, err := url.Parse(rawURL)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
queryParams := parsedURL.Query()
|
||||||
|
queryParams.Del(name)
|
||||||
|
parsedURL.RawQuery = queryParams.Encode()
|
||||||
|
return parsedURL.String(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *MessengerCommunitiesSuite) fetchImage(fullURL string) ([]byte, error) {
|
||||||
|
req, err := http.NewRequest("GET", fullURL, nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
client := &http.Client{
|
||||||
|
Transport: &http.Transport{
|
||||||
|
TLSClientConfig: &tls.Config{
|
||||||
|
MinVersion: tls.VersionTLS12,
|
||||||
|
InsecureSkipVerify: true, // nolint: gosec
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
resp, err := client.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
return ioutil.ReadAll(resp.Body)
|
||||||
|
}
|
||||||
|
|
||||||
func (s *MessengerCommunitiesSuite) TestMemberMessagesHasImageLink() {
|
func (s *MessengerCommunitiesSuite) TestMemberMessagesHasImageLink() {
|
||||||
// GIVEN
|
// GIVEN
|
||||||
community, communityChat := s.createCommunity()
|
community, communityChat := s.createCommunity()
|
||||||
|
|
|
@ -4843,6 +4843,22 @@ func (m *Messenger) CreateResponseWithACNotification(communityID string, acType
|
||||||
return response, nil
|
return response, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (m *Messenger) SerializedCommunities() ([]json.RawMessage, error) {
|
||||||
|
cs, err := m.Communities()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
res := make([]json.RawMessage, 0, len(cs))
|
||||||
|
for _, c := range cs {
|
||||||
|
b, err := c.MarshalJSONWithMediaServer(m.httpServer)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
res = append(res, b)
|
||||||
|
}
|
||||||
|
return res, nil
|
||||||
|
}
|
||||||
|
|
||||||
// SendMessageToControlNode sends a message to the control node of the community.
|
// SendMessageToControlNode sends a message to the control node of the community.
|
||||||
// use pointer to rawMessage to get the message ID and other updated properties.
|
// use pointer to rawMessage to get the message ID and other updated properties.
|
||||||
func (m *Messenger) SendMessageToControlNode(community *communities.Community, rawMessage *common.RawMessage) ([]byte, error) {
|
func (m *Messenger) SendMessageToControlNode(community *communities.Community, rawMessage *common.RawMessage) ([]byte, error) {
|
||||||
|
|
|
@ -14,6 +14,11 @@ import (
|
||||||
"strconv"
|
"strconv"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/golang/protobuf/proto"
|
||||||
|
|
||||||
|
"github.com/status-im/status-go/eth-node/types"
|
||||||
|
"github.com/status-im/status-go/protocol/protobuf"
|
||||||
|
|
||||||
"go.uber.org/zap"
|
"go.uber.org/zap"
|
||||||
|
|
||||||
eth_common "github.com/ethereum/go-ethereum/common"
|
eth_common "github.com/ethereum/go-ethereum/common"
|
||||||
|
@ -27,16 +32,18 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
basePath = "/messages"
|
basePath = "/messages"
|
||||||
imagesPath = basePath + "/images"
|
imagesPath = basePath + "/images"
|
||||||
audioPath = basePath + "/audio"
|
audioPath = basePath + "/audio"
|
||||||
ipfsPath = "/ipfs"
|
ipfsPath = "/ipfs"
|
||||||
discordAuthorsPath = "/discord/authors"
|
discordAuthorsPath = "/discord/authors"
|
||||||
discordAttachmentsPath = basePath + "/discord/attachments"
|
discordAttachmentsPath = basePath + "/discord/attachments"
|
||||||
LinkPreviewThumbnailPath = "/link-preview/thumbnail"
|
LinkPreviewThumbnailPath = "/link-preview/thumbnail"
|
||||||
LinkPreviewFaviconPath = "/link-preview/favicon"
|
LinkPreviewFaviconPath = "/link-preview/favicon"
|
||||||
StatusLinkPreviewThumbnailPath = "/status-link-preview/thumbnail"
|
StatusLinkPreviewThumbnailPath = "/status-link-preview/thumbnail"
|
||||||
communityTokenImagesPath = "/communityTokenImages"
|
communityTokenImagesPath = "/communityTokenImages"
|
||||||
|
communityDescriptionImagesPath = "/communityDescriptionImages"
|
||||||
|
communityDescriptionTokenImagesPath = "/communityDescriptionTokenImages"
|
||||||
|
|
||||||
walletBasePath = "/wallet"
|
walletBasePath = "/wallet"
|
||||||
walletCommunityImagesPath = walletBasePath + "/communityImages"
|
walletCommunityImagesPath = walletBasePath + "/communityImages"
|
||||||
|
@ -993,6 +1000,131 @@ func handleCommunityTokenImages(db *sql.DB, logger *zap.Logger) http.HandlerFunc
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func handleCommunityDescriptionImagesPath(db *sql.DB, logger *zap.Logger) http.HandlerFunc {
|
||||||
|
if db == nil {
|
||||||
|
return handleRequestDBMissing(logger)
|
||||||
|
}
|
||||||
|
|
||||||
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
params := r.URL.Query()
|
||||||
|
|
||||||
|
if len(params["communityID"]) == 0 {
|
||||||
|
logger.Error("[handleCommunityDescriptionImagesPath] no communityID")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
communityID := params["communityID"][0]
|
||||||
|
|
||||||
|
name := ""
|
||||||
|
if len(params["name"]) > 0 {
|
||||||
|
name = params["name"][0]
|
||||||
|
}
|
||||||
|
|
||||||
|
err, communityDescription := getCommunityDescription(db, communityID, logger)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if communityDescription.Identity == nil {
|
||||||
|
logger.Error("no identity in community description", zap.String("community id", communityID))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var imagePayload []byte
|
||||||
|
for t, i := range communityDescription.Identity.Images {
|
||||||
|
if t == name {
|
||||||
|
imagePayload = i.Payload
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if imagePayload == nil {
|
||||||
|
logger.Error("can't find community description image", zap.String("community id", communityID), zap.String("name", name))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
mime, err := images.GetProtobufImageMime(imagePayload)
|
||||||
|
if err != nil {
|
||||||
|
logger.Error("failed to get community image mime", zap.String("community id", communityID), zap.Error(err))
|
||||||
|
}
|
||||||
|
|
||||||
|
w.Header().Set("Content-Type", mime)
|
||||||
|
w.Header().Set("Cache-Control", "no-store")
|
||||||
|
_, err = w.Write(imagePayload)
|
||||||
|
if err != nil {
|
||||||
|
logger.Error("failed to write community image", zap.String("community id", communityID), zap.Error(err))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func handleCommunityDescriptionTokenImagesPath(db *sql.DB, logger *zap.Logger) http.HandlerFunc {
|
||||||
|
if db == nil {
|
||||||
|
return handleRequestDBMissing(logger)
|
||||||
|
}
|
||||||
|
|
||||||
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
params := r.URL.Query()
|
||||||
|
|
||||||
|
if len(params["communityID"]) == 0 {
|
||||||
|
logger.Error("[handleCommunityDescriptionTokenImagesPath] no communityID")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
communityID := params["communityID"][0]
|
||||||
|
|
||||||
|
if len(params["symbol"]) == 0 {
|
||||||
|
logger.Error("[handleCommunityDescriptionTokenImagesPath] no symbol")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
symbol := params["symbol"][0]
|
||||||
|
|
||||||
|
err, communityDescription := getCommunityDescription(db, communityID, logger)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var foundToken *protobuf.CommunityTokenMetadata
|
||||||
|
for _, m := range communityDescription.CommunityTokensMetadata {
|
||||||
|
if m.GetSymbol() == symbol {
|
||||||
|
foundToken = m
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if foundToken == nil {
|
||||||
|
logger.Error("can't find community description token image", zap.String("community id", communityID), zap.String("symbol", symbol))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
imagePayload, err := images.GetPayloadFromURI(foundToken.Image)
|
||||||
|
if err != nil {
|
||||||
|
logger.Error("failed to get community description token image payload", zap.Error(err))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
mime, err := images.GetProtobufImageMime(imagePayload)
|
||||||
|
if err != nil {
|
||||||
|
logger.Error("failed to get community description token image mime", zap.String("community id", communityID), zap.String("symbol", symbol), zap.Error(err))
|
||||||
|
}
|
||||||
|
|
||||||
|
w.Header().Set("Content-Type", mime)
|
||||||
|
w.Header().Set("Cache-Control", "no-store")
|
||||||
|
_, err = w.Write(imagePayload)
|
||||||
|
if err != nil {
|
||||||
|
logger.Error("failed to write community description token image", zap.String("community id", communityID), zap.String("symbol", symbol), zap.Error(err))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// getCommunityDescription returns the latest community description from the cache.
|
||||||
|
// NOTE: you should ensure preprocessDescription is called before this function.
|
||||||
|
func getCommunityDescription(db *sql.DB, communityID string, logger *zap.Logger) (error, *protobuf.CommunityDescription) {
|
||||||
|
var descriptionBytes []byte
|
||||||
|
err := db.QueryRow(`SELECT description FROM encrypted_community_description_cache WHERE community_id = ? ORDER BY clock DESC LIMIT 1`, types.Hex2Bytes(communityID)).Scan(&descriptionBytes)
|
||||||
|
if err != nil {
|
||||||
|
logger.Error("failed to find community description", zap.String("community id", communityID), zap.Error(err))
|
||||||
|
return err, nil
|
||||||
|
}
|
||||||
|
communityDescription := new(protobuf.CommunityDescription)
|
||||||
|
err = proto.Unmarshal(descriptionBytes, communityDescription)
|
||||||
|
if err != nil {
|
||||||
|
logger.Error("failed to unmarshal community description", zap.String("community id", communityID), zap.Error(err))
|
||||||
|
}
|
||||||
|
return err, communityDescription
|
||||||
|
}
|
||||||
|
|
||||||
func handleWalletCommunityImages(db *sql.DB, logger *zap.Logger) http.HandlerFunc {
|
func handleWalletCommunityImages(db *sql.DB, logger *zap.Logger) http.HandlerFunc {
|
||||||
if db == nil {
|
if db == nil {
|
||||||
return handleRequestDBMissing(logger)
|
return handleRequestDBMissing(logger)
|
||||||
|
|
|
@ -42,22 +42,24 @@ func NewMediaServer(db *sql.DB, downloader *ipfs.Downloader, multiaccountsDB *mu
|
||||||
walletDB: walletDB,
|
walletDB: walletDB,
|
||||||
}
|
}
|
||||||
s.SetHandlers(HandlerPatternMap{
|
s.SetHandlers(HandlerPatternMap{
|
||||||
accountImagesPath: handleAccountImages(s.multiaccountsDB, s.logger),
|
accountImagesPath: handleAccountImages(s.multiaccountsDB, s.logger),
|
||||||
accountInitialsPath: handleAccountInitials(s.multiaccountsDB, s.logger),
|
accountInitialsPath: handleAccountInitials(s.multiaccountsDB, s.logger),
|
||||||
audioPath: handleAudio(s.db, s.logger),
|
audioPath: handleAudio(s.db, s.logger),
|
||||||
contactImagesPath: handleContactImages(s.db, s.logger),
|
contactImagesPath: handleContactImages(s.db, s.logger),
|
||||||
discordAttachmentsPath: handleDiscordAttachment(s.db, s.logger),
|
discordAttachmentsPath: handleDiscordAttachment(s.db, s.logger),
|
||||||
discordAuthorsPath: handleDiscordAuthorAvatar(s.db, s.logger),
|
discordAuthorsPath: handleDiscordAuthorAvatar(s.db, s.logger),
|
||||||
generateQRCode: handleQRCodeGeneration(s.multiaccountsDB, s.logger),
|
generateQRCode: handleQRCodeGeneration(s.multiaccountsDB, s.logger),
|
||||||
imagesPath: handleImage(s.db, s.logger),
|
imagesPath: handleImage(s.db, s.logger),
|
||||||
ipfsPath: handleIPFS(s.downloader, s.logger),
|
ipfsPath: handleIPFS(s.downloader, s.logger),
|
||||||
LinkPreviewThumbnailPath: handleLinkPreviewThumbnail(s.db, s.logger),
|
LinkPreviewThumbnailPath: handleLinkPreviewThumbnail(s.db, s.logger),
|
||||||
LinkPreviewFaviconPath: handleLinkPreviewFavicon(s.db, s.logger),
|
LinkPreviewFaviconPath: handleLinkPreviewFavicon(s.db, s.logger),
|
||||||
StatusLinkPreviewThumbnailPath: handleStatusLinkPreviewThumbnail(s.db, s.logger),
|
StatusLinkPreviewThumbnailPath: handleStatusLinkPreviewThumbnail(s.db, s.logger),
|
||||||
communityTokenImagesPath: handleCommunityTokenImages(s.db, s.logger),
|
communityTokenImagesPath: handleCommunityTokenImages(s.db, s.logger),
|
||||||
walletCommunityImagesPath: handleWalletCommunityImages(s.walletDB, s.logger),
|
communityDescriptionImagesPath: handleCommunityDescriptionImagesPath(s.db, s.logger),
|
||||||
walletCollectionImagesPath: handleWalletCollectionImages(s.walletDB, s.logger),
|
communityDescriptionTokenImagesPath: handleCommunityDescriptionTokenImagesPath(s.db, s.logger),
|
||||||
walletCollectibleImagesPath: handleWalletCollectibleImages(s.walletDB, s.logger),
|
walletCommunityImagesPath: handleWalletCommunityImages(s.walletDB, s.logger),
|
||||||
|
walletCollectionImagesPath: handleWalletCollectionImages(s.walletDB, s.logger),
|
||||||
|
walletCollectibleImagesPath: handleWalletCollectibleImages(s.walletDB, s.logger),
|
||||||
})
|
})
|
||||||
|
|
||||||
return s, nil
|
return s, nil
|
||||||
|
@ -168,6 +170,28 @@ func (s *MediaServer) MakeCommunityTokenImagesURL(communityID string, chainID ui
|
||||||
return u.String()
|
return u.String()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *MediaServer) MakeCommunityImageURL(communityID, name string) string {
|
||||||
|
u := s.MakeBaseURL()
|
||||||
|
u.Path = communityDescriptionImagesPath
|
||||||
|
u.RawQuery = url.Values{
|
||||||
|
"communityID": {communityID},
|
||||||
|
"name": {name},
|
||||||
|
}.Encode()
|
||||||
|
|
||||||
|
return u.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *MediaServer) MakeCommunityDescriptionTokenImageURL(communityID, symbol string) string {
|
||||||
|
u := s.MakeBaseURL()
|
||||||
|
u.Path = communityDescriptionTokenImagesPath
|
||||||
|
u.RawQuery = url.Values{
|
||||||
|
"communityID": {communityID},
|
||||||
|
"symbol": {symbol},
|
||||||
|
}.Encode()
|
||||||
|
|
||||||
|
return u.String()
|
||||||
|
}
|
||||||
|
|
||||||
func (s *MediaServer) MakeWalletCommunityImagesURL(communityID string) string {
|
func (s *MediaServer) MakeWalletCommunityImagesURL(communityID string) string {
|
||||||
u := s.MakeBaseURL()
|
u := s.MakeBaseURL()
|
||||||
u.Path = walletCommunityImagesPath
|
u.Path = walletCommunityImagesPath
|
||||||
|
|
|
@ -4,6 +4,7 @@ import (
|
||||||
"context"
|
"context"
|
||||||
"crypto/ecdsa"
|
"crypto/ecdsa"
|
||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"math/big"
|
"math/big"
|
||||||
|
@ -394,10 +395,18 @@ func (api *PublicAPI) SetInstallationName(installationID string, name string) er
|
||||||
}
|
}
|
||||||
|
|
||||||
// Communities returns a list of communities that are stored
|
// Communities returns a list of communities that are stored
|
||||||
|
// Deprecated: Use SerializedCommunities instead
|
||||||
func (api *PublicAPI) Communities(parent context.Context) ([]*communities.Community, error) {
|
func (api *PublicAPI) Communities(parent context.Context) ([]*communities.Community, error) {
|
||||||
return api.service.messenger.Communities()
|
return api.service.messenger.Communities()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SerializedCommunities returns a list of serialized communities.
|
||||||
|
// The key difference from the Communities function is that it uses MediaServer
|
||||||
|
// to construct image URLs for all the images rather than using base64 encoding.
|
||||||
|
func (api *PublicAPI) SerializedCommunities(parent context.Context) ([]json.RawMessage, error) {
|
||||||
|
return api.service.messenger.SerializedCommunities()
|
||||||
|
}
|
||||||
|
|
||||||
// JoinedCommunities returns a list of communities that the user has joined
|
// JoinedCommunities returns a list of communities that the user has joined
|
||||||
func (api *PublicAPI) JoinedCommunities(parent context.Context) ([]*communities.Community, error) {
|
func (api *PublicAPI) JoinedCommunities(parent context.Context) ([]*communities.Community, error) {
|
||||||
return api.service.messenger.JoinedCommunities()
|
return api.service.messenger.JoinedCommunities()
|
||||||
|
|
Loading…
Reference in New Issue