diff --git a/protocol/communities/community.go b/protocol/communities/community.go index 0834f0b2a..1efb65419 100644 --- a/protocol/communities/community.go +++ b/protocol/communities/community.go @@ -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") diff --git a/protocol/communities_messenger_test.go b/protocol/communities_messenger_test.go index 21dc0203c..82142c22f 100644 --- a/protocol/communities_messenger_test.go +++ b/protocol/communities_messenger_test.go @@ -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() diff --git a/protocol/messenger_communities.go b/protocol/messenger_communities.go index 9a4818460..2f77b4f9d 100644 --- a/protocol/messenger_communities.go +++ b/protocol/messenger_communities.go @@ -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) { diff --git a/server/handlers.go b/server/handlers.go index bd9e0ba1e..e0fc9ce0d 100644 --- a/server/handlers.go +++ b/server/handlers.go @@ -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) diff --git a/server/server_media.go b/server/server_media.go index b30c7e441..0af8c7647 100644 --- a/server/server_media.go +++ b/server/server_media.go @@ -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 diff --git a/services/ext/api.go b/services/ext/api.go index 76f3d8bb9..a39e155f1 100644 --- a/services/ext/api.go +++ b/services/ext/api.go @@ -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()