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/requests"
|
||||
"github.com/status-im/status-go/protocol/v1"
|
||||
"github.com/status-im/status-go/server"
|
||||
)
|
||||
|
||||
const signatureLength = 65
|
||||
|
@ -255,6 +256,162 @@ func (o *Community) MarshalPublicAPIJSON() ([]byte, error) {
|
|||
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) {
|
||||
if o.config.MemberIdentity == nil {
|
||||
return nil, errors.New("member identity not set")
|
||||
|
|
|
@ -4,11 +4,14 @@ import (
|
|||
"bytes"
|
||||
"context"
|
||||
"crypto/ecdsa"
|
||||
"crypto/tls"
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
"reflect"
|
||||
"strings"
|
||||
|
@ -4348,6 +4351,169 @@ func (s *MessengerCommunitiesSuite) sendImageToCommunity(sender *Messenger, chat
|
|||
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() {
|
||||
// GIVEN
|
||||
community, communityChat := s.createCommunity()
|
||||
|
|
|
@ -4843,6 +4843,22 @@ func (m *Messenger) CreateResponseWithACNotification(communityID string, acType
|
|||
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.
|
||||
// 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) {
|
||||
|
|
|
@ -14,6 +14,11 @@ import (
|
|||
"strconv"
|
||||
"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"
|
||||
|
||||
eth_common "github.com/ethereum/go-ethereum/common"
|
||||
|
@ -27,16 +32,18 @@ import (
|
|||
)
|
||||
|
||||
const (
|
||||
basePath = "/messages"
|
||||
imagesPath = basePath + "/images"
|
||||
audioPath = basePath + "/audio"
|
||||
ipfsPath = "/ipfs"
|
||||
discordAuthorsPath = "/discord/authors"
|
||||
discordAttachmentsPath = basePath + "/discord/attachments"
|
||||
LinkPreviewThumbnailPath = "/link-preview/thumbnail"
|
||||
LinkPreviewFaviconPath = "/link-preview/favicon"
|
||||
StatusLinkPreviewThumbnailPath = "/status-link-preview/thumbnail"
|
||||
communityTokenImagesPath = "/communityTokenImages"
|
||||
basePath = "/messages"
|
||||
imagesPath = basePath + "/images"
|
||||
audioPath = basePath + "/audio"
|
||||
ipfsPath = "/ipfs"
|
||||
discordAuthorsPath = "/discord/authors"
|
||||
discordAttachmentsPath = basePath + "/discord/attachments"
|
||||
LinkPreviewThumbnailPath = "/link-preview/thumbnail"
|
||||
LinkPreviewFaviconPath = "/link-preview/favicon"
|
||||
StatusLinkPreviewThumbnailPath = "/status-link-preview/thumbnail"
|
||||
communityTokenImagesPath = "/communityTokenImages"
|
||||
communityDescriptionImagesPath = "/communityDescriptionImages"
|
||||
communityDescriptionTokenImagesPath = "/communityDescriptionTokenImages"
|
||||
|
||||
walletBasePath = "/wallet"
|
||||
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 {
|
||||
if db == nil {
|
||||
return handleRequestDBMissing(logger)
|
||||
|
|
|
@ -42,22 +42,24 @@ func NewMediaServer(db *sql.DB, downloader *ipfs.Downloader, multiaccountsDB *mu
|
|||
walletDB: walletDB,
|
||||
}
|
||||
s.SetHandlers(HandlerPatternMap{
|
||||
accountImagesPath: handleAccountImages(s.multiaccountsDB, s.logger),
|
||||
accountInitialsPath: handleAccountInitials(s.multiaccountsDB, s.logger),
|
||||
audioPath: handleAudio(s.db, s.logger),
|
||||
contactImagesPath: handleContactImages(s.db, s.logger),
|
||||
discordAttachmentsPath: handleDiscordAttachment(s.db, s.logger),
|
||||
discordAuthorsPath: handleDiscordAuthorAvatar(s.db, s.logger),
|
||||
generateQRCode: handleQRCodeGeneration(s.multiaccountsDB, s.logger),
|
||||
imagesPath: handleImage(s.db, s.logger),
|
||||
ipfsPath: handleIPFS(s.downloader, s.logger),
|
||||
LinkPreviewThumbnailPath: handleLinkPreviewThumbnail(s.db, s.logger),
|
||||
LinkPreviewFaviconPath: handleLinkPreviewFavicon(s.db, s.logger),
|
||||
StatusLinkPreviewThumbnailPath: handleStatusLinkPreviewThumbnail(s.db, s.logger),
|
||||
communityTokenImagesPath: handleCommunityTokenImages(s.db, s.logger),
|
||||
walletCommunityImagesPath: handleWalletCommunityImages(s.walletDB, s.logger),
|
||||
walletCollectionImagesPath: handleWalletCollectionImages(s.walletDB, s.logger),
|
||||
walletCollectibleImagesPath: handleWalletCollectibleImages(s.walletDB, s.logger),
|
||||
accountImagesPath: handleAccountImages(s.multiaccountsDB, s.logger),
|
||||
accountInitialsPath: handleAccountInitials(s.multiaccountsDB, s.logger),
|
||||
audioPath: handleAudio(s.db, s.logger),
|
||||
contactImagesPath: handleContactImages(s.db, s.logger),
|
||||
discordAttachmentsPath: handleDiscordAttachment(s.db, s.logger),
|
||||
discordAuthorsPath: handleDiscordAuthorAvatar(s.db, s.logger),
|
||||
generateQRCode: handleQRCodeGeneration(s.multiaccountsDB, s.logger),
|
||||
imagesPath: handleImage(s.db, s.logger),
|
||||
ipfsPath: handleIPFS(s.downloader, s.logger),
|
||||
LinkPreviewThumbnailPath: handleLinkPreviewThumbnail(s.db, s.logger),
|
||||
LinkPreviewFaviconPath: handleLinkPreviewFavicon(s.db, s.logger),
|
||||
StatusLinkPreviewThumbnailPath: handleStatusLinkPreviewThumbnail(s.db, s.logger),
|
||||
communityTokenImagesPath: handleCommunityTokenImages(s.db, s.logger),
|
||||
communityDescriptionImagesPath: handleCommunityDescriptionImagesPath(s.db, s.logger),
|
||||
communityDescriptionTokenImagesPath: handleCommunityDescriptionTokenImagesPath(s.db, s.logger),
|
||||
walletCommunityImagesPath: handleWalletCommunityImages(s.walletDB, s.logger),
|
||||
walletCollectionImagesPath: handleWalletCollectionImages(s.walletDB, s.logger),
|
||||
walletCollectibleImagesPath: handleWalletCollectibleImages(s.walletDB, s.logger),
|
||||
})
|
||||
|
||||
return s, nil
|
||||
|
@ -168,6 +170,28 @@ func (s *MediaServer) MakeCommunityTokenImagesURL(communityID string, chainID ui
|
|||
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 {
|
||||
u := s.MakeBaseURL()
|
||||
u.Path = walletCommunityImagesPath
|
||||
|
|
|
@ -4,6 +4,7 @@ import (
|
|||
"context"
|
||||
"crypto/ecdsa"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"math/big"
|
||||
|
@ -394,10 +395,18 @@ func (api *PublicAPI) SetInstallationName(installationID string, name string) er
|
|||
}
|
||||
|
||||
// Communities returns a list of communities that are stored
|
||||
// Deprecated: Use SerializedCommunities instead
|
||||
func (api *PublicAPI) Communities(parent context.Context) ([]*communities.Community, error) {
|
||||
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
|
||||
func (api *PublicAPI) JoinedCommunities(parent context.Context) ([]*communities.Community, error) {
|
||||
return api.service.messenger.JoinedCommunities()
|
||||
|
|
Loading…
Reference in New Issue