feat: Profile showcase backend (#4005)

* feat: profile showcase preferences basic impl

(squashed)

* feat: save preferences in batch for profile showcase

* chore: add validation for profile showcase settings request

and fix migration order
This commit is contained in:
Mikhail Rogachev 2023-10-24 14:43:18 +04:00 committed by GitHub
parent e83be20def
commit 1be356af93
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 771 additions and 334 deletions

View file

@ -0,0 +1,88 @@
package protocol
import "fmt"
type ProfileShowcasePreferences struct {
Communities []*ProfileShowcaseEntry `json:"communities"`
Accounts []*ProfileShowcaseEntry `json:"accounts"`
Collectibles []*ProfileShowcaseEntry `json:"collectibles"`
Assets []*ProfileShowcaseEntry `json:"assets"`
}
func (p *ProfileShowcasePreferences) Validate() error {
for _, community := range p.Communities {
if community.EntryType != ProfileShowcaseEntryTypeCommunity {
return fmt.Errorf("communities must contain only entriers of type ProfileShowcaseEntryTypeCommunity")
}
}
for _, community := range p.Accounts {
if community.EntryType != ProfileShowcaseEntryTypeAccount {
return fmt.Errorf("accounts must contain only entriers of type ProfileShowcaseEntryTypeAccount")
}
}
for _, community := range p.Collectibles {
if community.EntryType != ProfileShowcaseEntryTypeCollectible {
return fmt.Errorf("collectibles must contain only entriers of type ProfileShowcaseEntryTypeCollectible")
}
}
for _, community := range p.Assets {
if community.EntryType != ProfileShowcaseEntryTypeAsset {
return fmt.Errorf("assets must contain only entriers of type ProfileShowcaseEntryTypeAsset")
}
}
return nil
}
func (m *Messenger) SetProfileShowcasePreference(entry *ProfileShowcaseEntry) error {
return m.persistence.InsertOrUpdateProfileShowcasePreference(entry)
}
func (m *Messenger) SetProfileShowcasePreferences(preferences ProfileShowcasePreferences) error {
err := preferences.Validate()
if err != nil {
return err
}
allPreferences := []*ProfileShowcaseEntry{}
allPreferences = append(allPreferences, preferences.Communities...)
allPreferences = append(allPreferences, preferences.Accounts...)
allPreferences = append(allPreferences, preferences.Collectibles...)
allPreferences = append(allPreferences, preferences.Assets...)
return m.persistence.SaveProfileShowcasePreferences(allPreferences)
}
func (m *Messenger) GetProfileShowcasePreferences() (*ProfileShowcasePreferences, error) {
// NOTE: in the future default profile preferences should be filled in for each group according to special rules,
// that's why they should be grouped here
communities, err := m.persistence.GetProfileShowcasePreferencesByType(ProfileShowcaseEntryTypeCommunity)
if err != nil {
return nil, err
}
accounts, err := m.persistence.GetProfileShowcasePreferencesByType(ProfileShowcaseEntryTypeAccount)
if err != nil {
return nil, err
}
collectibles, err := m.persistence.GetProfileShowcasePreferencesByType(ProfileShowcaseEntryTypeCollectible)
if err != nil {
return nil, err
}
assets, err := m.persistence.GetProfileShowcasePreferencesByType(ProfileShowcaseEntryTypeAsset)
if err != nil {
return nil, err
}
return &ProfileShowcasePreferences{
Communities: communities,
Accounts: accounts,
Collectibles: collectibles,
Assets: assets,
}, nil
}

View file

@ -0,0 +1,78 @@
package protocol
import (
"testing"
"github.com/stretchr/testify/suite"
)
func TestMessengerProfileShowcaseSuite(t *testing.T) {
suite.Run(t, new(MessengerProfileShowcaseSuite))
}
type MessengerProfileShowcaseSuite struct {
MessengerBaseTestSuite
}
func (s *MessengerProfileShowcaseSuite) TestSetAndGetProfileShowcasePreferences() {
communityEntry1 := &ProfileShowcaseEntry{
ID: "0x01312357798976434",
EntryType: ProfileShowcaseEntryTypeCommunity,
ShowcaseVisibility: ProfileShowcaseVisibilityContacts,
Order: 10,
}
communityEntry2 := &ProfileShowcaseEntry{
ID: "0x01312357798976535",
EntryType: ProfileShowcaseEntryTypeCommunity,
ShowcaseVisibility: ProfileShowcaseVisibilityEveryone,
Order: 11,
}
accountEntry := &ProfileShowcaseEntry{
ID: "0cx34662234",
EntryType: ProfileShowcaseEntryTypeAccount,
ShowcaseVisibility: ProfileShowcaseVisibilityEveryone,
Order: 17,
}
collectibleEntry := &ProfileShowcaseEntry{
ID: "0x12378534257568678487683576",
EntryType: ProfileShowcaseEntryTypeCollectible,
ShowcaseVisibility: ProfileShowcaseVisibilityIDVerifiedContacts,
Order: 17,
}
assetEntry := &ProfileShowcaseEntry{
ID: "0x139ii4uu423",
EntryType: ProfileShowcaseEntryTypeAsset,
ShowcaseVisibility: ProfileShowcaseVisibilityNoOne,
Order: 17,
}
request := ProfileShowcasePreferences{
Communities: []*ProfileShowcaseEntry{communityEntry1, communityEntry2},
Accounts: []*ProfileShowcaseEntry{accountEntry},
Collectibles: []*ProfileShowcaseEntry{collectibleEntry},
Assets: []*ProfileShowcaseEntry{assetEntry},
}
err := s.m.SetProfileShowcasePreferences(request)
s.Require().NoError(err)
response, err := s.m.GetProfileShowcasePreferences()
s.Require().NoError(err)
s.Require().Len(response.Communities, 2)
s.Require().Equal(response.Communities[0], communityEntry1)
s.Require().Equal(response.Communities[1], communityEntry2)
s.Require().Len(response.Accounts, 1)
s.Require().Equal(response.Accounts[0], accountEntry)
s.Require().Len(response.Collectibles, 1)
s.Require().Equal(response.Collectibles[0], collectibleEntry)
s.Require().Len(response.Assets, 1)
s.Require().Equal(response.Assets[0], assetEntry)
}

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,13 @@
CREATE TABLE IF NOT EXISTS profile_showcase_preferences (
id TEXT PRIMARY KEY ON CONFLICT REPLACE,
entry_type INT NOT NULL DEFAULT 0,
visibility INT NOT NULL DEFAULT 0,
sort_order INT NOT NULL DEFAULT 0
);
CREATE TABLE IF NOT EXISTS profile_showcase_contacts (
contact_id TEXT PRIMARY KEY ON CONFLICT REPLACE,
entry_id TEXT NOT NULL,
entry_type INT NOT NULL DEFAULT 0,
entry_order INT NOT NULL DEFAULT 0
);

View file

@ -0,0 +1,122 @@
package protocol
import (
"context"
"database/sql"
)
type ProfileShowcaseEntryType int
const (
ProfileShowcaseEntryTypeCommunity ProfileShowcaseEntryType = iota
ProfileShowcaseEntryTypeAccount
ProfileShowcaseEntryTypeCollectible
ProfileShowcaseEntryTypeAsset
)
type ProfileShowcaseVisibility int
const (
ProfileShowcaseVisibilityNoOne ProfileShowcaseVisibility = iota
ProfileShowcaseVisibilityIDVerifiedContacts
ProfileShowcaseVisibilityContacts
ProfileShowcaseVisibilityEveryone
)
const insertOrUpdateProfilePreferencesQuery = "INSERT OR REPLACE INTO profile_showcase_preferences(id, entry_type, visibility, sort_order) VALUES (?, ?, ?, ?)"
const selectAllProfilePreferencesQuery = "SELECT id, entry_type, visibility, sort_order FROM profile_showcase_preferences"
const selectProfilePreferencesByTypeQuery = "SELECT id, entry_type, visibility, sort_order FROM profile_showcase_preferences WHERE entry_type = ?"
type ProfileShowcaseEntry struct {
ID string `json:"id"`
EntryType ProfileShowcaseEntryType `json:"entryType"`
ShowcaseVisibility ProfileShowcaseVisibility `json:"showcaseVisibility"`
Order int `json:"order"`
}
func (db sqlitePersistence) InsertOrUpdateProfileShowcasePreference(entry *ProfileShowcaseEntry) error {
_, err := db.db.Exec(insertOrUpdateProfilePreferencesQuery,
entry.ID,
entry.EntryType,
entry.ShowcaseVisibility,
entry.Order,
)
if err != nil {
return err
}
return nil
}
func (db sqlitePersistence) SaveProfileShowcasePreferences(entries []*ProfileShowcaseEntry) error {
tx, err := db.db.BeginTx(context.Background(), &sql.TxOptions{})
if err != nil {
return err
}
defer func() {
if err == nil {
err = tx.Commit()
return
}
// don't shadow original error
_ = tx.Rollback()
}()
for _, entry := range entries {
_, err = tx.Exec(insertOrUpdateProfilePreferencesQuery,
entry.ID,
entry.EntryType,
entry.ShowcaseVisibility,
entry.Order,
)
if err != nil {
return err
}
}
return nil
}
func (db sqlitePersistence) parseProfileShowcasePreferencesRows(rows *sql.Rows) ([]*ProfileShowcaseEntry, error) {
entries := []*ProfileShowcaseEntry{}
for rows.Next() {
entry := &ProfileShowcaseEntry{}
err := rows.Scan(
&entry.ID,
&entry.EntryType,
&entry.ShowcaseVisibility,
&entry.Order,
)
if err != nil {
return nil, err
}
entries = append(entries, entry)
}
return entries, nil
}
func (db sqlitePersistence) GetAllProfileShowcasePreferences() ([]*ProfileShowcaseEntry, error) {
rows, err := db.db.Query(selectAllProfilePreferencesQuery)
if err != nil {
return nil, err
}
defer rows.Close()
return db.parseProfileShowcasePreferencesRows(rows)
}
func (db sqlitePersistence) GetProfileShowcasePreferencesByType(entryType ProfileShowcaseEntryType) ([]*ProfileShowcaseEntry, error) {
rows, err := db.db.Query(selectProfilePreferencesByTypeQuery, entryType)
if err != nil {
return nil, err
}
defer rows.Close()
return db.parseProfileShowcasePreferencesRows(rows)
}

View file

@ -1546,6 +1546,16 @@ func (api *PublicAPI) CreateTokenGatedCommunity() (*protocol.MessengerResponse,
return api.service.messenger.CreateTokenGatedCommunity()
}
// Set profile showcase preference for current user
func (api *PublicAPI) SetProfileShowcasePreferences(preferences protocol.ProfileShowcasePreferences) error {
return api.service.messenger.SetProfileShowcasePreferences(preferences)
}
// Get all profile showcase preferences for current user
func (api *PublicAPI) GetProfileShowcasePreferences() (*protocol.ProfileShowcasePreferences, error) {
return api.service.messenger.GetProfileShowcasePreferences()
}
// -----
// HELPER
// -----