From 4182481d313e52d5d71ef7445a218a95216b5743 Mon Sep 17 00:00:00 2001 From: Benny Date: Tue, 17 Oct 2023 13:29:14 -0400 Subject: [PATCH 1/6] Contract L1 Chain (#1227) * l1 contract * l1 queries * unused field * typo sql * fix conflict statement * l1 chain with chain for conflict --- db/gen/coredb/batch.go | 16 ++++--- db/gen/coredb/contract_gallery.sql.go | 28 +++++++----- db/gen/coredb/models_gen.go | 1 + db/gen/coredb/query.sql.go | 47 +++++++++++--------- db/gen/coredb/search.sql.go | 3 +- db/migrations/core/000112_contract_l1.up.sql | 28 ++++++++++++ db/queries/core/contract_gallery.sql | 10 +++-- db/queries/core/query.sql | 2 +- service/multichain/multichain.go | 12 ++--- service/persist/contract_gallery.go | 1 + service/persist/postgres/contract_gallery.go | 16 +++---- 11 files changed, 108 insertions(+), 56 deletions(-) create mode 100644 db/migrations/core/000112_contract_l1.up.sql diff --git a/db/gen/coredb/batch.go b/db/gen/coredb/batch.go index 4bf1263c7..432ff6313 100644 --- a/db/gen/coredb/batch.go +++ b/db/gen/coredb/batch.go @@ -743,7 +743,7 @@ func (b *GetAdmiresByActorIDBatchBatchResults) Close() error { } const getChildContractsByParentIDBatchPaginate = `-- name: GetChildContractsByParentIDBatchPaginate :batchmany -select c.id, c.deleted, c.version, c.created_at, c.last_updated, c.name, c.symbol, c.address, c.creator_address, c.chain, c.profile_banner_url, c.profile_image_url, c.badge_url, c.description, c.owner_address, c.is_provider_marked_spam, c.parent_id, c.override_creator_user_id +select c.id, c.deleted, c.version, c.created_at, c.last_updated, c.name, c.symbol, c.address, c.creator_address, c.chain, c.profile_banner_url, c.profile_image_url, c.badge_url, c.description, c.owner_address, c.is_provider_marked_spam, c.parent_id, c.override_creator_user_id, c.l1_chain from contracts c where c.parent_id = $1 and c.deleted = false @@ -825,6 +825,7 @@ func (b *GetChildContractsByParentIDBatchPaginateBatchResults) Query(f func(int, &i.IsProviderMarkedSpam, &i.ParentID, &i.OverrideCreatorUserID, + &i.L1Chain, ); err != nil { return err } @@ -1035,7 +1036,7 @@ func (b *GetCommentByCommentIDBatchBatchResults) Close() error { } const getContractByChainAddressBatch = `-- name: GetContractByChainAddressBatch :batchone -select id, deleted, version, created_at, last_updated, name, symbol, address, creator_address, chain, profile_banner_url, profile_image_url, badge_url, description, owner_address, is_provider_marked_spam, parent_id, override_creator_user_id FROM contracts WHERE address = $1 AND chain = $2 AND deleted = false +select id, deleted, version, created_at, last_updated, name, symbol, address, creator_address, chain, profile_banner_url, profile_image_url, badge_url, description, owner_address, is_provider_marked_spam, parent_id, override_creator_user_id, l1_chain FROM contracts WHERE address = $1 AND chain = $2 AND deleted = false ` type GetContractByChainAddressBatchBatchResults struct { @@ -1092,6 +1093,7 @@ func (b *GetContractByChainAddressBatchBatchResults) QueryRow(f func(int, Contra &i.IsProviderMarkedSpam, &i.ParentID, &i.OverrideCreatorUserID, + &i.L1Chain, ) if f != nil { f(t, i, err) @@ -1127,7 +1129,7 @@ displayed as ( and galleries.last_updated > last_refreshed.last_updated and collections.last_updated > last_refreshed.last_updated ) -select contracts.id, contracts.deleted, contracts.version, contracts.created_at, contracts.last_updated, contracts.name, contracts.symbol, contracts.address, contracts.creator_address, contracts.chain, contracts.profile_banner_url, contracts.profile_image_url, contracts.badge_url, contracts.description, contracts.owner_address, contracts.is_provider_marked_spam, contracts.parent_id, contracts.override_creator_user_id from contracts, displayed +select contracts.id, contracts.deleted, contracts.version, contracts.created_at, contracts.last_updated, contracts.name, contracts.symbol, contracts.address, contracts.creator_address, contracts.chain, contracts.profile_banner_url, contracts.profile_image_url, contracts.badge_url, contracts.description, contracts.owner_address, contracts.is_provider_marked_spam, contracts.parent_id, contracts.override_creator_user_id, contracts.l1_chain from contracts, displayed where contracts.id = displayed.contract_id and contracts.deleted = false ` @@ -1186,6 +1188,7 @@ func (b *GetContractsDisplayedByUserIDBatchBatchResults) Query(f func(int, []Con &i.IsProviderMarkedSpam, &i.ParentID, &i.OverrideCreatorUserID, + &i.L1Chain, ); err != nil { return err } @@ -1205,7 +1208,7 @@ func (b *GetContractsDisplayedByUserIDBatchBatchResults) Close() error { } const getCreatedContractsBatchPaginate = `-- name: GetCreatedContractsBatchPaginate :batchmany -select contracts.id, contracts.deleted, contracts.version, contracts.created_at, contracts.last_updated, contracts.name, contracts.symbol, contracts.address, contracts.creator_address, contracts.chain, contracts.profile_banner_url, contracts.profile_image_url, contracts.badge_url, contracts.description, contracts.owner_address, contracts.is_provider_marked_spam, contracts.parent_id, contracts.override_creator_user_id +select contracts.id, contracts.deleted, contracts.version, contracts.created_at, contracts.last_updated, contracts.name, contracts.symbol, contracts.address, contracts.creator_address, contracts.chain, contracts.profile_banner_url, contracts.profile_image_url, contracts.badge_url, contracts.description, contracts.owner_address, contracts.is_provider_marked_spam, contracts.parent_id, contracts.override_creator_user_id, contracts.l1_chain from contracts join contract_creators on contracts.id = contract_creators.contract_id and contract_creators.creator_user_id = $1 where ($2::bool or contracts.chain = any(string_to_array($3, ',')::int[])) @@ -1291,6 +1294,7 @@ func (b *GetCreatedContractsBatchPaginateBatchResults) Query(f func(int, []Contr &i.IsProviderMarkedSpam, &i.ParentID, &i.OverrideCreatorUserID, + &i.L1Chain, ); err != nil { return err } @@ -2453,7 +2457,7 @@ func (b *GetProfileImageByIDBatchResults) Close() error { } const getSharedContractsBatchPaginate = `-- name: GetSharedContractsBatchPaginate :batchmany -select contracts.id, contracts.deleted, contracts.version, contracts.created_at, contracts.last_updated, contracts.name, contracts.symbol, contracts.address, contracts.creator_address, contracts.chain, contracts.profile_banner_url, contracts.profile_image_url, contracts.badge_url, contracts.description, contracts.owner_address, contracts.is_provider_marked_spam, contracts.parent_id, contracts.override_creator_user_id, a.displayed as displayed_by_user_a, b.displayed as displayed_by_user_b, a.owned_count +select contracts.id, contracts.deleted, contracts.version, contracts.created_at, contracts.last_updated, contracts.name, contracts.symbol, contracts.address, contracts.creator_address, contracts.chain, contracts.profile_banner_url, contracts.profile_image_url, contracts.badge_url, contracts.description, contracts.owner_address, contracts.is_provider_marked_spam, contracts.parent_id, contracts.override_creator_user_id, contracts.l1_chain, a.displayed as displayed_by_user_a, b.displayed as displayed_by_user_b, a.owned_count from owned_contracts a, owned_contracts b, contracts left join marketplace_contracts on contracts.id = marketplace_contracts.contract_id where a.user_id = $1 @@ -2531,6 +2535,7 @@ type GetSharedContractsBatchPaginateRow struct { IsProviderMarkedSpam bool `json:"is_provider_marked_spam"` ParentID persist.DBID `json:"parent_id"` OverrideCreatorUserID persist.DBID `json:"override_creator_user_id"` + L1Chain persist.L1Chain `json:"l1_chain"` DisplayedByUserA bool `json:"displayed_by_user_a"` DisplayedByUserB bool `json:"displayed_by_user_b"` OwnedCount int64 `json:"owned_count"` @@ -2596,6 +2601,7 @@ func (b *GetSharedContractsBatchPaginateBatchResults) Query(f func(int, []GetSha &i.IsProviderMarkedSpam, &i.ParentID, &i.OverrideCreatorUserID, + &i.L1Chain, &i.DisplayedByUserA, &i.DisplayedByUserB, &i.OwnedCount, diff --git a/db/gen/coredb/contract_gallery.sql.go b/db/gen/coredb/contract_gallery.sql.go index b81e96abc..9dfa8f5e9 100644 --- a/db/gen/coredb/contract_gallery.sql.go +++ b/db/gen/coredb/contract_gallery.sql.go @@ -10,7 +10,7 @@ import ( ) const upsertChildContracts = `-- name: UpsertChildContracts :many -insert into contracts(id, deleted, version, created_at, name, address, creator_address, owner_address, chain, description, parent_id) ( +insert into contracts(id, deleted, version, created_at, name, address, creator_address, owner_address, chain, l1_chain, description, parent_id) ( select unnest($1::varchar[]) as id , false , 0 @@ -20,17 +20,18 @@ insert into contracts(id, deleted, version, created_at, name, address, creator_a , unnest($4::varchar[]) , unnest($5::varchar[]) , unnest($6::int[]) - , unnest($7::varchar[]) + , unnest($7::int[]) , unnest($8::varchar[]) + , unnest($9::varchar[]) ) -on conflict (chain, parent_id, address) where parent_id is not null +on conflict (l1_chain, chain, parent_id, address) where parent_id is not null do update set deleted = excluded.deleted , name = excluded.name , creator_address = excluded.creator_address , owner_address = excluded.owner_address , description = excluded.description , last_updated = now() -returning id, deleted, version, created_at, last_updated, name, symbol, address, creator_address, chain, profile_banner_url, profile_image_url, badge_url, description, owner_address, is_provider_marked_spam, parent_id, override_creator_user_id +returning id, deleted, version, created_at, last_updated, name, symbol, address, creator_address, chain, profile_banner_url, profile_image_url, badge_url, description, owner_address, is_provider_marked_spam, parent_id, override_creator_user_id, l1_chain ` type UpsertChildContractsParams struct { @@ -40,6 +41,7 @@ type UpsertChildContractsParams struct { CreatorAddress []string `json:"creator_address"` OwnerAddress []string `json:"owner_address"` Chain []int32 `json:"chain"` + L1Chain []int32 `json:"l1_chain"` Description []string `json:"description"` ParentIds []string `json:"parent_ids"` } @@ -52,6 +54,7 @@ func (q *Queries) UpsertChildContracts(ctx context.Context, arg UpsertChildContr arg.CreatorAddress, arg.OwnerAddress, arg.Chain, + arg.L1Chain, arg.Description, arg.ParentIds, ) @@ -81,6 +84,7 @@ func (q *Queries) UpsertChildContracts(ctx context.Context, arg UpsertChildContr &i.IsProviderMarkedSpam, &i.ParentID, &i.OverrideCreatorUserID, + &i.L1Chain, ); err != nil { return nil, err } @@ -93,7 +97,7 @@ func (q *Queries) UpsertChildContracts(ctx context.Context, arg UpsertChildContr } const upsertParentContracts = `-- name: UpsertParentContracts :many -insert into contracts(id, deleted, version, created_at, address, symbol, name, owner_address, chain, description, profile_image_url, is_provider_marked_spam) ( +insert into contracts(id, deleted, version, created_at, address, symbol, name, owner_address, chain, l1_chain, description, profile_image_url, is_provider_marked_spam) ( select unnest($1::varchar[]) , false , unnest($2::int[]) @@ -103,17 +107,18 @@ insert into contracts(id, deleted, version, created_at, address, symbol, name, o , unnest($5::varchar[]) , unnest($6::varchar[]) , unnest($7::int[]) - , unnest($8::varchar[]) + , unnest($8::int[]) , unnest($9::varchar[]) - , unnest($10::bool[]) + , unnest($10::varchar[]) + , unnest($11::bool[]) ) -on conflict (chain, address) where parent_id is null +on conflict (l1_chain, chain, address) where parent_id is null do update set symbol = coalesce(nullif(excluded.symbol, ''), nullif(contracts.symbol, '')) , version = excluded.version , name = coalesce(nullif(excluded.name, ''), nullif(contracts.name, '')) , owner_address = case - when nullif(contracts.owner_address, '') is null or ($11::bool and nullif (excluded.owner_address, '') is not null) + when nullif(contracts.owner_address, '') is null or ($12::bool and nullif (excluded.owner_address, '') is not null) then excluded.owner_address else contracts.owner_address @@ -122,7 +127,7 @@ do update set symbol = coalesce(nullif(excluded.symbol, ''), nullif(contracts.sy , profile_image_url = coalesce(nullif(excluded.profile_image_url, ''), nullif(contracts.profile_image_url, '')) , deleted = excluded.deleted , last_updated = now() -returning id, deleted, version, created_at, last_updated, name, symbol, address, creator_address, chain, profile_banner_url, profile_image_url, badge_url, description, owner_address, is_provider_marked_spam, parent_id, override_creator_user_id +returning id, deleted, version, created_at, last_updated, name, symbol, address, creator_address, chain, profile_banner_url, profile_image_url, badge_url, description, owner_address, is_provider_marked_spam, parent_id, override_creator_user_id, l1_chain ` type UpsertParentContractsParams struct { @@ -133,6 +138,7 @@ type UpsertParentContractsParams struct { Name []string `json:"name"` OwnerAddress []string `json:"owner_address"` Chain []int32 `json:"chain"` + L1Chain []int32 `json:"l1_chain"` Description []string `json:"description"` ProfileImageUrl []string `json:"profile_image_url"` ProviderMarkedSpam []bool `json:"provider_marked_spam"` @@ -148,6 +154,7 @@ func (q *Queries) UpsertParentContracts(ctx context.Context, arg UpsertParentCon arg.Name, arg.OwnerAddress, arg.Chain, + arg.L1Chain, arg.Description, arg.ProfileImageUrl, arg.ProviderMarkedSpam, @@ -179,6 +186,7 @@ func (q *Queries) UpsertParentContracts(ctx context.Context, arg UpsertParentCon &i.IsProviderMarkedSpam, &i.ParentID, &i.OverrideCreatorUserID, + &i.L1Chain, ); err != nil { return nil, err } diff --git a/db/gen/coredb/models_gen.go b/db/gen/coredb/models_gen.go index cd3ad9f5f..ab9b232bf 100644 --- a/db/gen/coredb/models_gen.go +++ b/db/gen/coredb/models_gen.go @@ -81,6 +81,7 @@ type Contract struct { IsProviderMarkedSpam bool `json:"is_provider_marked_spam"` ParentID persist.DBID `json:"parent_id"` OverrideCreatorUserID persist.DBID `json:"override_creator_user_id"` + L1Chain persist.L1Chain `json:"l1_chain"` } type ContractCreator struct { diff --git a/db/gen/coredb/query.sql.go b/db/gen/coredb/query.sql.go index 764128d68..d30e35f2d 100644 --- a/db/gen/coredb/query.sql.go +++ b/db/gen/coredb/query.sql.go @@ -1456,7 +1456,7 @@ func (q *Queries) GetAllTimeTrendingUserIDs(ctx context.Context, limit int32) ([ const getAllTokensWithContractsByIDs = `-- name: GetAllTokensWithContractsByIDs :many select tokens.id, tokens.deleted, tokens.version, tokens.created_at, tokens.last_updated, tokens.name, tokens.description, tokens.collectors_note, tokens.token_uri, tokens.token_type, tokens.token_id, tokens.quantity, tokens.ownership_history, tokens.external_url, tokens.block_number, tokens.owner_user_id, tokens.owned_by_wallets, tokens.chain, tokens.contract, tokens.is_user_marked_spam, tokens.is_provider_marked_spam, tokens.last_synced, tokens.fallback_media, tokens.token_media_id, tokens.is_creator_token, tokens.is_holder_token, tokens.displayable, - contracts.id, contracts.deleted, contracts.version, contracts.created_at, contracts.last_updated, contracts.name, contracts.symbol, contracts.address, contracts.creator_address, contracts.chain, contracts.profile_banner_url, contracts.profile_image_url, contracts.badge_url, contracts.description, contracts.owner_address, contracts.is_provider_marked_spam, contracts.parent_id, contracts.override_creator_user_id, + contracts.id, contracts.deleted, contracts.version, contracts.created_at, contracts.last_updated, contracts.name, contracts.symbol, contracts.address, contracts.creator_address, contracts.chain, contracts.profile_banner_url, contracts.profile_image_url, contracts.badge_url, contracts.description, contracts.owner_address, contracts.is_provider_marked_spam, contracts.parent_id, contracts.override_creator_user_id, contracts.l1_chain, ( select wallets.address from wallets @@ -1523,6 +1523,7 @@ type GetAllTokensWithContractsByIDsRow struct { IsProviderMarkedSpam_2 bool `json:"is_provider_marked_spam_2"` ParentID persist.DBID `json:"parent_id"` OverrideCreatorUserID persist.DBID `json:"override_creator_user_id"` + L1Chain persist.L1Chain `json:"l1_chain"` WalletAddress persist.Address `json:"wallet_address"` } @@ -1581,6 +1582,7 @@ func (q *Queries) GetAllTokensWithContractsByIDs(ctx context.Context, arg GetAll &i.IsProviderMarkedSpam_2, &i.ParentID, &i.OverrideCreatorUserID, + &i.L1Chain, &i.WalletAddress, ); err != nil { return nil, err @@ -1763,7 +1765,7 @@ func (q *Queries) GetCommentsByCommentIDs(ctx context.Context, commentIds persis } const getContractByChainAddress = `-- name: GetContractByChainAddress :one -select id, deleted, version, created_at, last_updated, name, symbol, address, creator_address, chain, profile_banner_url, profile_image_url, badge_url, description, owner_address, is_provider_marked_spam, parent_id, override_creator_user_id FROM contracts WHERE address = $1 AND chain = $2 AND deleted = false +select id, deleted, version, created_at, last_updated, name, symbol, address, creator_address, chain, profile_banner_url, profile_image_url, badge_url, description, owner_address, is_provider_marked_spam, parent_id, override_creator_user_id, l1_chain FROM contracts WHERE address = $1 AND chain = $2 AND deleted = false ` type GetContractByChainAddressParams struct { @@ -1793,12 +1795,13 @@ func (q *Queries) GetContractByChainAddress(ctx context.Context, arg GetContract &i.IsProviderMarkedSpam, &i.ParentID, &i.OverrideCreatorUserID, + &i.L1Chain, ) return i, err } const getContractByID = `-- name: GetContractByID :one -select id, deleted, version, created_at, last_updated, name, symbol, address, creator_address, chain, profile_banner_url, profile_image_url, badge_url, description, owner_address, is_provider_marked_spam, parent_id, override_creator_user_id FROM contracts WHERE id = $1 AND deleted = false +select id, deleted, version, created_at, last_updated, name, symbol, address, creator_address, chain, profile_banner_url, profile_image_url, badge_url, description, owner_address, is_provider_marked_spam, parent_id, override_creator_user_id, l1_chain FROM contracts WHERE id = $1 AND deleted = false ` func (q *Queries) GetContractByID(ctx context.Context, id persist.DBID) (Contract, error) { @@ -1823,6 +1826,7 @@ func (q *Queries) GetContractByID(ctx context.Context, id persist.DBID) (Contrac &i.IsProviderMarkedSpam, &i.ParentID, &i.OverrideCreatorUserID, + &i.L1Chain, ) return i, err } @@ -1859,7 +1863,7 @@ func (q *Queries) GetContractCreatorsByIds(ctx context.Context, contractIds []st } const getContractsByIDs = `-- name: GetContractsByIDs :many -SELECT id, deleted, version, created_at, last_updated, name, symbol, address, creator_address, chain, profile_banner_url, profile_image_url, badge_url, description, owner_address, is_provider_marked_spam, parent_id, override_creator_user_id from contracts WHERE id = ANY($1) AND deleted = false +SELECT id, deleted, version, created_at, last_updated, name, symbol, address, creator_address, chain, profile_banner_url, profile_image_url, badge_url, description, owner_address, is_provider_marked_spam, parent_id, override_creator_user_id, l1_chain from contracts WHERE id = ANY($1) AND deleted = false ` func (q *Queries) GetContractsByIDs(ctx context.Context, contractIds persist.DBIDList) ([]Contract, error) { @@ -1890,6 +1894,7 @@ func (q *Queries) GetContractsByIDs(ctx context.Context, contractIds persist.DBI &i.IsProviderMarkedSpam, &i.ParentID, &i.OverrideCreatorUserID, + &i.L1Chain, ); err != nil { return nil, err } @@ -1902,7 +1907,7 @@ func (q *Queries) GetContractsByIDs(ctx context.Context, contractIds persist.DBI } const getContractsByTokenIDs = `-- name: GetContractsByTokenIDs :many -select contracts.id, contracts.deleted, contracts.version, contracts.created_at, contracts.last_updated, contracts.name, contracts.symbol, contracts.address, contracts.creator_address, contracts.chain, contracts.profile_banner_url, contracts.profile_image_url, contracts.badge_url, contracts.description, contracts.owner_address, contracts.is_provider_marked_spam, contracts.parent_id, contracts.override_creator_user_id from contracts join tokens on contracts.id = tokens.contract where tokens.id = any($1) and contracts.deleted = false +select contracts.id, contracts.deleted, contracts.version, contracts.created_at, contracts.last_updated, contracts.name, contracts.symbol, contracts.address, contracts.creator_address, contracts.chain, contracts.profile_banner_url, contracts.profile_image_url, contracts.badge_url, contracts.description, contracts.owner_address, contracts.is_provider_marked_spam, contracts.parent_id, contracts.override_creator_user_id, contracts.l1_chain from contracts join tokens on contracts.id = tokens.contract where tokens.id = any($1) and contracts.deleted = false ` func (q *Queries) GetContractsByTokenIDs(ctx context.Context, tokenIds persist.DBIDList) ([]Contract, error) { @@ -1933,6 +1938,7 @@ func (q *Queries) GetContractsByTokenIDs(ctx context.Context, tokenIds persist.D &i.IsProviderMarkedSpam, &i.ParentID, &i.OverrideCreatorUserID, + &i.L1Chain, ); err != nil { return nil, err } @@ -1945,19 +1951,19 @@ func (q *Queries) GetContractsByTokenIDs(ctx context.Context, tokenIds persist.D } const getCreatedContractsByUserID = `-- name: GetCreatedContractsByUserID :many -select c.id, c.deleted, c.version, c.created_at, c.last_updated, c.name, c.symbol, c.address, c.creator_address, c.chain, c.profile_banner_url, c.profile_image_url, c.badge_url, c.description, c.owner_address, c.is_provider_marked_spam, c.parent_id, c.override_creator_user_id, +select c.id, c.deleted, c.version, c.created_at, c.last_updated, c.name, c.symbol, c.address, c.creator_address, c.chain, c.profile_banner_url, c.profile_image_url, c.badge_url, c.description, c.owner_address, c.is_provider_marked_spam, c.parent_id, c.override_creator_user_id, c.l1_chain, w.id as wallet_id, false as is_override_creator from users u, contracts c, wallets w where u.id = $1 and c.chain = any($2::int[]) and w.id = any(u.wallets) and coalesce(nullif(c.owner_address, ''), nullif(c.creator_address, '')) = w.address - and w.chain = any($3::int[]) + and w.l1_chain = c.l1_chain and u.deleted = false and c.deleted = false and w.deleted = false and c.override_creator_user_id is null - and (not $4::bool or not exists( + and (not $3::bool or not exists( select 1 from tokens t where t.owner_user_id = $1 and t.contract = c.id @@ -1968,14 +1974,14 @@ where u.id = $1 union all -select c.id, c.deleted, c.version, c.created_at, c.last_updated, c.name, c.symbol, c.address, c.creator_address, c.chain, c.profile_banner_url, c.profile_image_url, c.badge_url, c.description, c.owner_address, c.is_provider_marked_spam, c.parent_id, c.override_creator_user_id, +select c.id, c.deleted, c.version, c.created_at, c.last_updated, c.name, c.symbol, c.address, c.creator_address, c.chain, c.profile_banner_url, c.profile_image_url, c.badge_url, c.description, c.owner_address, c.is_provider_marked_spam, c.parent_id, c.override_creator_user_id, c.l1_chain, null as wallet_id, true as is_override_creator from contracts c where c.override_creator_user_id = $1 and c.chain = any($2::int[]) and c.deleted = false - and (not $4::bool or not exists( + and (not $3::bool or not exists( select 1 from tokens t where t.owner_user_id = $1 and t.contract = c.id @@ -1988,7 +1994,6 @@ where c.override_creator_user_id = $1 type GetCreatedContractsByUserIDParams struct { UserID persist.DBID `json:"user_id"` Chains []int32 `json:"chains"` - L1Chains []int32 `json:"l1_chains"` NewContractsOnly bool `json:"new_contracts_only"` } @@ -1999,12 +2004,7 @@ type GetCreatedContractsByUserIDRow struct { } func (q *Queries) GetCreatedContractsByUserID(ctx context.Context, arg GetCreatedContractsByUserIDParams) ([]GetCreatedContractsByUserIDRow, error) { - rows, err := q.db.Query(ctx, getCreatedContractsByUserID, - arg.UserID, - arg.Chains, - arg.L1Chains, - arg.NewContractsOnly, - ) + rows, err := q.db.Query(ctx, getCreatedContractsByUserID, arg.UserID, arg.Chains, arg.NewContractsOnly) if err != nil { return nil, err } @@ -2031,6 +2031,7 @@ func (q *Queries) GetCreatedContractsByUserID(ctx context.Context, arg GetCreate &i.Contract.IsProviderMarkedSpam, &i.Contract.ParentID, &i.Contract.OverrideCreatorUserID, + &i.Contract.L1Chain, &i.WalletID, &i.IsOverrideCreator, ); err != nil { @@ -2309,7 +2310,7 @@ func (q *Queries) GetEventsInWindow(ctx context.Context, arg GetEventsInWindowPa const getFallbackTokenByUserTokenIdentifiers = `-- name: GetFallbackTokenByUserTokenIdentifiers :one with contract as ( - select id, deleted, version, created_at, last_updated, name, symbol, address, creator_address, chain, profile_banner_url, profile_image_url, badge_url, description, owner_address, is_provider_marked_spam, parent_id, override_creator_user_id from contracts where contracts.chain = $3 and contracts.address = $4 and not contracts.deleted + select id, deleted, version, created_at, last_updated, name, symbol, address, creator_address, chain, profile_banner_url, profile_image_url, badge_url, description, owner_address, is_provider_marked_spam, parent_id, override_creator_user_id, l1_chain from contracts where contracts.chain = $3 and contracts.address = $4 and not contracts.deleted ) select tokens.id, tokens.deleted, tokens.version, tokens.created_at, tokens.last_updated, tokens.name, tokens.description, tokens.collectors_note, tokens.token_uri, tokens.token_type, tokens.token_id, tokens.quantity, tokens.ownership_history, tokens.external_url, tokens.block_number, tokens.owner_user_id, tokens.owned_by_wallets, tokens.chain, tokens.contract, tokens.is_user_marked_spam, tokens.is_provider_marked_spam, tokens.last_synced, tokens.fallback_media, tokens.token_media_id, tokens.is_creator_token, tokens.is_holder_token, tokens.displayable from tokens, contract @@ -2715,7 +2716,7 @@ func (q *Queries) GetLastFeedEventForUser(ctx context.Context, arg GetLastFeedEv const getMediaByUserTokenIdentifiers = `-- name: GetMediaByUserTokenIdentifiers :one with contract as ( - select id, deleted, version, created_at, last_updated, name, symbol, address, creator_address, chain, profile_banner_url, profile_image_url, badge_url, description, owner_address, is_provider_marked_spam, parent_id, override_creator_user_id from contracts where contracts.chain = $1 and contracts.address = $2 and not contracts.deleted + select id, deleted, version, created_at, last_updated, name, symbol, address, creator_address, chain, profile_banner_url, profile_image_url, badge_url, description, owner_address, is_provider_marked_spam, parent_id, override_creator_user_id, l1_chain from contracts where contracts.chain = $1 and contracts.address = $2 and not contracts.deleted ), matching_media as ( select token_medias.id, token_medias.created_at, token_medias.last_updated, token_medias.version, token_medias.contract_id, token_medias.token_id, token_medias.chain, token_medias.active, token_medias.metadata, token_medias.media, token_medias.name, token_medias.description, token_medias.processing_job_id, token_medias.deleted @@ -2830,7 +2831,7 @@ func (q *Queries) GetMerchDiscountCodeByTokenID(ctx context.Context, tokenHex pe const getMissingThumbnailTokensByIDRange = `-- name: GetMissingThumbnailTokensByIDRange :many SELECT tokens.id, tokens.deleted, tokens.version, tokens.created_at, tokens.last_updated, tokens.name, tokens.description, tokens.collectors_note, tokens.token_uri, tokens.token_type, tokens.token_id, tokens.quantity, tokens.ownership_history, tokens.external_url, tokens.block_number, tokens.owner_user_id, tokens.owned_by_wallets, tokens.chain, tokens.contract, tokens.is_user_marked_spam, tokens.is_provider_marked_spam, tokens.last_synced, tokens.fallback_media, tokens.token_media_id, tokens.is_creator_token, tokens.is_holder_token, tokens.displayable, - contracts.id, contracts.deleted, contracts.version, contracts.created_at, contracts.last_updated, contracts.name, contracts.symbol, contracts.address, contracts.creator_address, contracts.chain, contracts.profile_banner_url, contracts.profile_image_url, contracts.badge_url, contracts.description, contracts.owner_address, contracts.is_provider_marked_spam, contracts.parent_id, contracts.override_creator_user_id, + contracts.id, contracts.deleted, contracts.version, contracts.created_at, contracts.last_updated, contracts.name, contracts.symbol, contracts.address, contracts.creator_address, contracts.chain, contracts.profile_banner_url, contracts.profile_image_url, contracts.badge_url, contracts.description, contracts.owner_address, contracts.is_provider_marked_spam, contracts.parent_id, contracts.override_creator_user_id, contracts.l1_chain, ( SELECT wallets.address FROM wallets @@ -2895,6 +2896,7 @@ type GetMissingThumbnailTokensByIDRangeRow struct { IsProviderMarkedSpam_2 bool `json:"is_provider_marked_spam_2"` ParentID persist.DBID `json:"parent_id"` OverrideCreatorUserID persist.DBID `json:"override_creator_user_id"` + L1Chain persist.L1Chain `json:"l1_chain"` WalletAddress persist.Address `json:"wallet_address"` } @@ -2953,6 +2955,7 @@ func (q *Queries) GetMissingThumbnailTokensByIDRange(ctx context.Context, arg Ge &i.IsProviderMarkedSpam_2, &i.ParentID, &i.OverrideCreatorUserID, + &i.L1Chain, &i.WalletAddress, ); err != nil { return nil, err @@ -3392,7 +3395,7 @@ func (q *Queries) GetReprocessJobRangeByID(ctx context.Context, id int) (Reproce const getSVGTokensWithContractsByIDs = `-- name: GetSVGTokensWithContractsByIDs :many SELECT tokens.id, tokens.deleted, tokens.version, tokens.created_at, tokens.last_updated, tokens.name, tokens.description, tokens.collectors_note, tokens.token_uri, tokens.token_type, tokens.token_id, tokens.quantity, tokens.ownership_history, tokens.external_url, tokens.block_number, tokens.owner_user_id, tokens.owned_by_wallets, tokens.chain, tokens.contract, tokens.is_user_marked_spam, tokens.is_provider_marked_spam, tokens.last_synced, tokens.fallback_media, tokens.token_media_id, tokens.is_creator_token, tokens.is_holder_token, tokens.displayable, - contracts.id, contracts.deleted, contracts.version, contracts.created_at, contracts.last_updated, contracts.name, contracts.symbol, contracts.address, contracts.creator_address, contracts.chain, contracts.profile_banner_url, contracts.profile_image_url, contracts.badge_url, contracts.description, contracts.owner_address, contracts.is_provider_marked_spam, contracts.parent_id, contracts.override_creator_user_id, + contracts.id, contracts.deleted, contracts.version, contracts.created_at, contracts.last_updated, contracts.name, contracts.symbol, contracts.address, contracts.creator_address, contracts.chain, contracts.profile_banner_url, contracts.profile_image_url, contracts.badge_url, contracts.description, contracts.owner_address, contracts.is_provider_marked_spam, contracts.parent_id, contracts.override_creator_user_id, contracts.l1_chain, ( SELECT wallets.address FROM wallets @@ -3460,6 +3463,7 @@ type GetSVGTokensWithContractsByIDsRow struct { IsProviderMarkedSpam_2 bool `json:"is_provider_marked_spam_2"` ParentID persist.DBID `json:"parent_id"` OverrideCreatorUserID persist.DBID `json:"override_creator_user_id"` + L1Chain persist.L1Chain `json:"l1_chain"` WalletAddress persist.Address `json:"wallet_address"` } @@ -3518,6 +3522,7 @@ func (q *Queries) GetSVGTokensWithContractsByIDs(ctx context.Context, arg GetSVG &i.IsProviderMarkedSpam_2, &i.ParentID, &i.OverrideCreatorUserID, + &i.L1Chain, &i.WalletAddress, ); err != nil { return nil, err diff --git a/db/gen/coredb/search.sql.go b/db/gen/coredb/search.sql.go index 56c1d43f9..38958de3d 100644 --- a/db/gen/coredb/search.sql.go +++ b/db/gen/coredb/search.sql.go @@ -20,7 +20,7 @@ poap_weight as ( -- to offset the fact that we're going to multiply all addresses by 1000000000. select $5::float4 / 1000000000 as weight ) -select contracts.id, contracts.deleted, contracts.version, contracts.created_at, contracts.last_updated, contracts.name, contracts.symbol, contracts.address, contracts.creator_address, contracts.chain, contracts.profile_banner_url, contracts.profile_image_url, contracts.badge_url, contracts.description, contracts.owner_address, contracts.is_provider_marked_spam, contracts.parent_id, contracts.override_creator_user_id from contracts left join contract_relevance on contract_relevance.id = contracts.id, +select contracts.id, contracts.deleted, contracts.version, contracts.created_at, contracts.last_updated, contracts.name, contracts.symbol, contracts.address, contracts.creator_address, contracts.chain, contracts.profile_banner_url, contracts.profile_image_url, contracts.badge_url, contracts.description, contracts.owner_address, contracts.is_provider_marked_spam, contracts.parent_id, contracts.override_creator_user_id, contracts.l1_chain from contracts left join contract_relevance on contract_relevance.id = contracts.id, to_tsquery('simple', websearch_to_tsquery('simple', $1)::text || ':*') simple_partial_query, websearch_to_tsquery('simple', $1) simple_full_query, websearch_to_tsquery('english', $1) english_full_query, @@ -84,6 +84,7 @@ func (q *Queries) SearchContracts(ctx context.Context, arg SearchContractsParams &i.IsProviderMarkedSpam, &i.ParentID, &i.OverrideCreatorUserID, + &i.L1Chain, ); err != nil { return nil, err } diff --git a/db/migrations/core/000112_contract_l1.up.sql b/db/migrations/core/000112_contract_l1.up.sql new file mode 100644 index 000000000..6169313ce --- /dev/null +++ b/db/migrations/core/000112_contract_l1.up.sql @@ -0,0 +1,28 @@ +alter table contracts add column l1_chain int; +update contracts set l1_chain = 0; +alter table contracts alter column l1_chain set not null; +update contracts set l1_chain = 4 where chain = 4; +create index contracts_l1_chain_idx on contracts (address,chain,l1_chain) where deleted = false; +create unique index contracts_l1_chain_unique_idx on contracts (l1_chain,chain,address) where parent_id is null; +create unique index contracts_l1_chain_parent_unique_idx on contracts (l1_chain,chain,parent_id,address) where parent_id is not null; + +drop view if exists contract_creators; + +create view contract_creators as + select c.id as contract_id, + u.id as creator_user_id, + c.chain as chain, + coalesce(nullif(c.owner_address, ''), nullif(c.creator_address, '')) as creator_address + from contracts c + left join wallets w on + w.deleted = false and + w.l1_chain = c.l1_chain and + coalesce(nullif(c.owner_address, ''), nullif(c.creator_address, '')) = w.address + left join users u on + u.deleted = false and + ( + (c.override_creator_user_id is not null and c.override_creator_user_id = u.id) + or + (c.override_creator_user_id is null and w.address is not null and array[w.id] <@ u.wallets) + ) + where c.deleted = false; \ No newline at end of file diff --git a/db/queries/core/contract_gallery.sql b/db/queries/core/contract_gallery.sql index 2a4b5c519..74787010c 100644 --- a/db/queries/core/contract_gallery.sql +++ b/db/queries/core/contract_gallery.sql @@ -1,5 +1,5 @@ -- name: UpsertParentContracts :many -insert into contracts(id, deleted, version, created_at, address, symbol, name, owner_address, chain, description, profile_image_url, is_provider_marked_spam) ( +insert into contracts(id, deleted, version, created_at, address, symbol, name, owner_address, chain, l1_chain, description, profile_image_url, is_provider_marked_spam) ( select unnest(@ids::varchar[]) , false , unnest(@version::int[]) @@ -9,11 +9,12 @@ insert into contracts(id, deleted, version, created_at, address, symbol, name, o , unnest(@name::varchar[]) , unnest(@owner_address::varchar[]) , unnest(@chain::int[]) + , unnest(@l1_chain::int[]) , unnest(@description::varchar[]) , unnest(@profile_image_url::varchar[]) , unnest(@provider_marked_spam::bool[]) ) -on conflict (chain, address) where parent_id is null +on conflict (l1_chain, chain, address) where parent_id is null do update set symbol = coalesce(nullif(excluded.symbol, ''), nullif(contracts.symbol, '')) , version = excluded.version , name = coalesce(nullif(excluded.name, ''), nullif(contracts.name, '')) @@ -31,7 +32,7 @@ do update set symbol = coalesce(nullif(excluded.symbol, ''), nullif(contracts.sy returning *; -- name: UpsertChildContracts :many -insert into contracts(id, deleted, version, created_at, name, address, creator_address, owner_address, chain, description, parent_id) ( +insert into contracts(id, deleted, version, created_at, name, address, creator_address, owner_address, chain, l1_chain, description, parent_id) ( select unnest(@id::varchar[]) as id , false , 0 @@ -41,10 +42,11 @@ insert into contracts(id, deleted, version, created_at, name, address, creator_a , unnest(@creator_address::varchar[]) , unnest(@owner_address::varchar[]) , unnest(@chain::int[]) + , unnest(@l1_chain::int[]) , unnest(@description::varchar[]) , unnest(@parent_ids::varchar[]) ) -on conflict (chain, parent_id, address) where parent_id is not null +on conflict (l1_chain, chain, parent_id, address) where parent_id is not null do update set deleted = excluded.deleted , name = excluded.name , creator_address = excluded.creator_address diff --git a/db/queries/core/query.sql b/db/queries/core/query.sql index ad3427369..cbbd1bc0c 100644 --- a/db/queries/core/query.sql +++ b/db/queries/core/query.sql @@ -1589,7 +1589,7 @@ from users u, contracts c, wallets w where u.id = @user_id and c.chain = any(@chains::int[]) and w.id = any(u.wallets) and coalesce(nullif(c.owner_address, ''), nullif(c.creator_address, '')) = w.address - and w.chain = any(@l1_chains::int[]) + and w.l1_chain = c.l1_chain and u.deleted = false and c.deleted = false and w.deleted = false diff --git a/service/multichain/multichain.go b/service/multichain/multichain.go index 6723a25ee..c1242d75c 100644 --- a/service/multichain/multichain.go +++ b/service/multichain/multichain.go @@ -701,11 +701,8 @@ func (p *Provider) SyncCreatedTokensForNewContracts(ctx context.Context, userID chainInts := util.MapWithoutError(chains, func(c persist.Chain) int32 { return int32(c) }) rows, err := p.Queries.GetCreatedContractsByUserID(ctx, db.GetCreatedContractsByUserIDParams{ - UserID: userID, - Chains: chainInts, - L1Chains: util.MapWithoutError(chainInts, func(c int32) int32 { - return int32(persist.Chain(c).L1Chain()) - }), + UserID: userID, + Chains: chainInts, NewContractsOnly: true, }) @@ -936,6 +933,7 @@ func (p *Provider) SyncTokensCreatedOnSharedContracts(ctx context.Context, userI params.CreatorAddress = append(params.CreatorAddress, child.CreatorAddress.String()) params.OwnerAddress = append(params.OwnerAddress, child.OwnerAddress.String()) params.Chain = append(params.Chain, int32(result.Chain)) + params.L1Chain = append(params.L1Chain, int32(result.Chain.L1Chain())) params.Description = append(params.Description, child.Description) params.ParentIds = append(params.ParentIds, contractToDBID[persist.NewContractIdentifiers(edge.Parent.Address, result.Chain)].String()) } @@ -1463,8 +1461,9 @@ func (p *Provider) RefreshTokenDescriptorsByTokenIdentifiers(ctx context.Context return persist.ErrTokenNotFoundByTokenIdentifiers{Token: ti} } - contractID, err := p.Repos.ContractRepository.UpsertByAddress(ctx, ti.ContractAddress, ti.Chain, persist.ContractGallery{ + contractID, err := p.Repos.ContractRepository.UpsertByAddress(ctx, ti.ContractAddress, persist.ContractGallery{ Chain: ti.Chain, + L1Chain: ti.Chain.L1Chain(), Address: persist.Address(ti.Chain.NormalizeAddress(ti.ContractAddress)), Symbol: persist.NullString(finalContractDescriptors.Symbol), Name: persist.NullString(finalContractDescriptors.Name), @@ -2139,6 +2138,7 @@ func contractsToNewDedupedContracts(contracts []chainContracts, existingContract for address, meta := range contractMetadatas { res = append(res, persist.ContractGallery{ Chain: address.Chain(), + L1Chain: address.Chain().L1Chain(), Address: address.Address(), Symbol: persist.NullString(meta.Symbol), Name: persist.NullString(meta.Name), diff --git a/service/persist/contract_gallery.go b/service/persist/contract_gallery.go index 0f6aa5d1c..56a273214 100644 --- a/service/persist/contract_gallery.go +++ b/service/persist/contract_gallery.go @@ -15,6 +15,7 @@ type ContractGallery struct { LastUpdated time.Time `json:"last_updated"` Chain Chain `json:"chain"` + L1Chain L1Chain `json:"l1_chain"` Address Address `json:"address"` Symbol NullString `json:"symbol"` Name NullString `json:"name"` diff --git a/service/persist/postgres/contract_gallery.go b/service/persist/postgres/contract_gallery.go index a74a577f7..1e99d5c82 100644 --- a/service/persist/postgres/contract_gallery.go +++ b/service/persist/postgres/contract_gallery.go @@ -45,16 +45,15 @@ func NewContractGalleryRepository(db *sql.DB, queries *db.Queries) *ContractGall checkNoErr(err) upsertByAddressStmt, err := db.PrepareContext(ctx, ` - insert into contracts (id,version,address,symbol,name,owner_address,chain,description,profile_image_url) values ($1,$2,$3,$4,$5,$6,$7,$8,$9) - on conflict (address,chain) where parent_id is null do update set + insert into contracts (id,version,address,symbol,name,owner_address,chain,l1_chain,description,profile_image_url) values ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10) + on conflict (address,l1_chain) where parent_id is null do update set version = $2, address = $3, symbol = coalesce(nullif(contracts.symbol, ''), nullif($4, '')), name = coalesce(nullif(contracts.name, ''), nullif($5, '')), - description = coalesce(nullif(contracts.description, ''), nullif($8, '')), - profile_image_url = coalesce(nullif(contracts.profile_image_url, ''), nullif($9, '')), - owner_address = case when nullif(contracts.owner_address, '') is null then $6 else contracts.owner_address end, - chain = $7 + description = coalesce(nullif(contracts.description, ''), nullif($9, '')), + profile_image_url = coalesce(nullif(contracts.profile_image_url, ''), nullif($10, '')), + owner_address = case when nullif(contracts.owner_address, '') is null then $6 else contracts.owner_address end returning id; `) checkNoErr(err) @@ -154,8 +153,8 @@ func (c *ContractGalleryRepository) GetByTokenIDs(pCtx context.Context, pDBIDs p } // UpsertByAddress upserts the contract with the given address -func (c *ContractGalleryRepository) UpsertByAddress(pCtx context.Context, pAddress persist.Address, pChain persist.Chain, pContract persist.ContractGallery) (contractID persist.DBID, err error) { - err = c.upsertByAddressStmt.QueryRowContext(pCtx, persist.GenerateID(), pContract.Version, pContract.Address, pContract.Symbol, pContract.Name, pContract.OwnerAddress, pContract.Chain, pContract.Description, pContract.ProfileImageURL).Scan(&contractID) +func (c *ContractGalleryRepository) UpsertByAddress(pCtx context.Context, pAddress persist.Address, pContract persist.ContractGallery) (contractID persist.DBID, err error) { + err = c.upsertByAddressStmt.QueryRowContext(pCtx, persist.GenerateID(), pContract.Version, pContract.Address, pContract.Symbol, pContract.Name, pContract.OwnerAddress, pContract.Chain, pContract.L1Chain, pContract.Description, pContract.ProfileImageURL).Scan(&contractID) if err != nil { return "", err } @@ -183,6 +182,7 @@ func (c *ContractGalleryRepository) BulkUpsert(pCtx context.Context, pContracts params.Name = append(params.Name, c.Name.String()) params.OwnerAddress = append(params.OwnerAddress, c.OwnerAddress.String()) params.Chain = append(params.Chain, int32(c.Chain)) + params.L1Chain = append(params.L1Chain, int32(c.Chain.L1Chain())) params.Description = append(params.Description, c.Description.String()) params.ProfileImageUrl = append(params.ProfileImageUrl, c.ProfileImageURL.String()) params.ProviderMarkedSpam = append(params.ProviderMarkedSpam, c.IsProviderMarkedSpam) From 77eeaed1dcbc39d03362ebd4301923296c59200f Mon Sep 17 00:00:00 2001 From: Benny Date: Wed, 18 Oct 2023 20:18:31 -0400 Subject: [PATCH 2/6] Benny/zora creator support minor fixes (#1234) * typos, balance versus regular * will break --- service/multichain/zora/zora.go | 191 +++++++++++++++++++++----------- 1 file changed, 126 insertions(+), 65 deletions(-) diff --git a/service/multichain/zora/zora.go b/service/multichain/zora/zora.go index 9e1b22d59..2e555df81 100644 --- a/service/multichain/zora/zora.go +++ b/service/multichain/zora/zora.go @@ -127,15 +127,20 @@ func (d *Provider) GetBlockchainInfo() multichain.BlockchainInfo { } } -type getTokensResponse struct { +type getBalanceTokensResponse struct { Tokens []zoraBalanceToken `json:"results"` HasNextPage bool `json:"has_next_page"` } +type getTokensResponse struct { + Tokens []zoraToken `json:"results"` + HasNextPage bool `json:"has_next_page"` +} + // GetTokensByWalletAddress retrieves tokens for a wallet address on the zora Blockchain func (d *Provider) GetTokensByWalletAddress(ctx context.Context, addr persist.Address) ([]multichain.ChainAgnosticToken, []multichain.ChainAgnosticContract, error) { url := fmt.Sprintf("%s/user/%s/tokens?chain_names=ZORA-MAINNET&sort_direction=DESC", zoraRESTURL, addr.String()) - return d.getTokens(ctx, url, nil) + return d.getTokens(ctx, url, nil, true) } func (d *Provider) GetTokensIncrementallyByWalletAddress(ctx context.Context, addr persist.Address) (<-chan multichain.ChainAgnosticTokensAndContracts, <-chan error) { @@ -143,7 +148,7 @@ func (d *Provider) GetTokensIncrementallyByWalletAddress(ctx context.Context, ad errChan := make(chan error) url := fmt.Sprintf("%s/user/%s/tokens?chain_names=ZORA-MAINNET&sort_direction=DESC", zoraRESTURL, addr.String()) go func() { - _, _, err := d.getTokens(ctx, url, rec) + _, _, err := d.getTokens(ctx, url, rec, true) if err != nil { errChan <- err return @@ -153,12 +158,12 @@ func (d *Provider) GetTokensIncrementallyByWalletAddress(ctx context.Context, ad } func (d *Provider) GetTokenByTokenIdentifiersAndOwner(ctx context.Context, ti multichain.ChainAgnosticIdentifiers, owner persist.Address) (multichain.ChainAgnosticToken, multichain.ChainAgnosticContract, error) { - url := fmt.Sprintf("%s/contract/ZORA_MAINNET/%s/%s", zoraRESTURL, ti.ContractAddress.String(), ti.TokenID.Base10String()) + url := fmt.Sprintf("%s/contract/ZORA-MAINNET/%s/%s", zoraRESTURL, ti.ContractAddress.String(), ti.TokenID.Base10String()) return d.getToken(ctx, owner, url) } func (d *Provider) GetTokenMetadataByTokenIdentifiers(ctx context.Context, ti multichain.ChainAgnosticIdentifiers) (persist.TokenMetadata, error) { - url := fmt.Sprintf("%s/contract/ZORA_MAINNET/%s/%s", zoraRESTURL, ti.ContractAddress.String(), ti.TokenID.Base10String()) + url := fmt.Sprintf("%s/contract/ZORA-MAINNET/%s/%s", zoraRESTURL, ti.ContractAddress.String(), ti.TokenID.Base10String()) token, _, err := d.getToken(ctx, "", url) if err != nil { return nil, err @@ -169,13 +174,14 @@ func (d *Provider) GetTokenMetadataByTokenIdentifiers(ctx context.Context, ti mu // GetTokensByContractAddress retrieves tokens for a contract address on the zora Blockchain func (d *Provider) GetTokensByContractAddress(ctx context.Context, contractAddress persist.Address, limit, offset int) ([]multichain.ChainAgnosticToken, multichain.ChainAgnosticContract, error) { - url := fmt.Sprintf("%s/tokens/ZORA_MAINNET/%s?offset=%d&limit=%d&sort_key=CREATED&sort_direction=DESC", zoraRESTURL, contractAddress.String(), offset, limit) - tokens, contracts, err := d.getTokens(ctx, url, nil) + url := fmt.Sprintf("%s/tokens/ZORA-MAINNET/%s?&sort_key=CREATED&sort_direction=DESC", zoraRESTURL, contractAddress.String()) + tokens, contracts, err := d.getTokens(ctx, url, nil, false) if err != nil { return nil, multichain.ChainAgnosticContract{}, err } if len(contracts) != 1 { - return nil, multichain.ChainAgnosticContract{}, fmt.Errorf("invalid number of contracts returned from zora: %d", len(contracts)) + logger.For(ctx).Warnf("invalid number of contracts returned from zora: %d", len(contracts)) + return nil, multichain.ChainAgnosticContract{}, nil } return tokens, contracts[0], nil @@ -206,7 +212,7 @@ func (d *Provider) GetTokensByContractAddressAndOwner(ctx context.Context, owner // GetContractByAddress retrieves an zora contract by address func (d *Provider) GetContractByAddress(ctx context.Context, addr persist.Address) (multichain.ChainAgnosticContract, error) { - url := fmt.Sprintf("%s/contract/ZORA_MAINNET/%s", zoraRESTURL, addr.String()) + url := fmt.Sprintf("%s/contract/ZORA-MAINNET/%s", zoraRESTURL, addr.String()) _, contract, err := d.getToken(ctx, "", url) if err != nil { return multichain.ChainAgnosticContract{}, err @@ -253,7 +259,7 @@ func (d *Provider) GetContractsByOwnerAddress(ctx context.Context, addr persist. return result, nil } -func (d *Provider) getTokens(ctx context.Context, url string, rec chan<- multichain.ChainAgnosticTokensAndContracts) ([]multichain.ChainAgnosticToken, []multichain.ChainAgnosticContract, error) { +func (d *Provider) getTokens(ctx context.Context, url string, rec chan<- multichain.ChainAgnosticTokensAndContracts, balance bool) ([]multichain.ChainAgnosticToken, []multichain.ChainAgnosticContract, error) { offset := 0 limit := 50 allTokens := []multichain.ChainAgnosticToken{} @@ -271,15 +277,36 @@ func (d *Provider) getTokens(ctx context.Context, url string, rec chan<- multich } defer resp.Body.Close() - var result getTokensResponse - err = json.NewDecoder(resp.Body).Decode(&result) - if err != nil { - return nil, nil, err - } + var tokens []multichain.ChainAgnosticToken + var contracts []multichain.ChainAgnosticContract + var willBreak bool + if balance { + var result getBalanceTokensResponse + err = json.NewDecoder(resp.Body).Decode(&result) + if err != nil { + return nil, nil, err + } - tokens, contracts, err := d.tokensToChainAgnostic(ctx, result.Tokens) - if err != nil { - return nil, nil, err + logger.For(ctx).Infof("zora raw tokens retrieved: %d", len(result.Tokens)) + + tokens, contracts = d.balanceTokensToChainAgnostic(ctx, result.Tokens) + if len(result.Tokens) < limit || !result.HasNextPage { + willBreak = true + } + + } else { + var result getTokensResponse + err = json.NewDecoder(resp.Body).Decode(&result) + if err != nil { + return nil, nil, err + } + + logger.For(ctx).Infof("zora raw tokens retrieved: %d", len(result.Tokens)) + + tokens, contracts = d.tokensToChainAgnostic(ctx, result.Tokens) + if len(result.Tokens) < limit || !result.HasNextPage { + willBreak = true + } } allTokens = append(allTokens, tokens...) @@ -291,10 +318,10 @@ func (d *Provider) getTokens(ctx context.Context, url string, rec chan<- multich Contracts: contracts, } } - - if len(result.Tokens) < limit || !result.HasNextPage { + if willBreak { break } + } logger.For(ctx).Infof("zora tokens retrieved: %d", len(allTokens)) @@ -320,13 +347,8 @@ func (d *Provider) getToken(ctx context.Context, ownerAddress persist.Address, u return multichain.ChainAgnosticToken{}, multichain.ChainAgnosticContract{}, err } - tokens, contracts, err := d.tokensToChainAgnostic(ctx, []zoraBalanceToken{{ - Balance: 1, - Token: result, - }}) - if err != nil { - return multichain.ChainAgnosticToken{}, multichain.ChainAgnosticContract{}, err - } + tokens, contracts := d.tokensToChainAgnostic(ctx, []zoraToken{result}) + if len(tokens) == 0 || len(contracts) == 0 { return multichain.ChainAgnosticToken{}, multichain.ChainAgnosticContract{}, fmt.Errorf("invalid number of tokens or contracts returned from zora: %d %d", len(tokens), len(contracts)) } @@ -343,68 +365,107 @@ func (d *Provider) getToken(ctx context.Context, ownerAddress persist.Address, u return tokens[0], contracts[0], nil } -func (d *Provider) tokensToChainAgnostic(ctx context.Context, tokens []zoraBalanceToken) ([]multichain.ChainAgnosticToken, []multichain.ChainAgnosticContract, error) { - result := make([]multichain.ChainAgnosticToken, len(tokens)) +func (d *Provider) balanceTokensToChainAgnostic(ctx context.Context, tokens []zoraBalanceToken) ([]multichain.ChainAgnosticToken, []multichain.ChainAgnosticContract) { + result := make([]multichain.ChainAgnosticToken, 0, len(tokens)) contracts := map[string]multichain.ChainAgnosticContract{} - for i, token := range tokens { - var tokenType persist.TokenType - switch token.Token.TokenStandard { - case "ERC721": - tokenType = persist.TokenTypeERC721 - case "ERC1155": - tokenType = persist.TokenTypeERC1155 - default: - panic(fmt.Sprintf("unknown token standard %s %+v", token.Token.TokenStandard, token)) + for _, token := range tokens { + converted, err := d.tokenToAgnostic(ctx, token.Token) + if err != nil { + logger.For(ctx).Errorf("error converting zora token %+v: %s", token, err.Error()) + continue } - metadataName, _ := token.Token.Metadata["name"].(string) - metadataDescription, _ := token.Token.Metadata["description"].(string) - balanceAsBig := new(big.Int).SetInt64(int64(token.Balance)) balanceAsHex := balanceAsBig.Text(16) if balanceAsHex == "" { balanceAsHex = "1" } + converted.Quantity = persist.HexString(balanceAsHex) - result[i] = multichain.ChainAgnosticToken{ - Descriptors: multichain.ChainAgnosticTokenDescriptors{ - Name: metadataName, - Description: metadataDescription, - }, - TokenType: tokenType, - TokenMetadata: token.Token.Metadata, - TokenID: persist.TokenID(token.Token.TokenID.toBase16String()), - Quantity: persist.HexString(balanceAsHex), - OwnerAddress: persist.Address(strings.ToLower(token.Token.Owner)), - ContractAddress: persist.Address(strings.ToLower(token.Token.CollectionAddress)), - - FallbackMedia: persist.FallbackMedia{ - ImageURL: persist.NullString(token.Token.Media.ImagePreview.EncodedPreview), - }, - } + result = append(result, converted) if _, ok := contracts[token.Token.CollectionAddress]; ok { continue } - contracts[token.Token.CollectionAddress] = d.contractToChainAgnostic(ctx, token) + contracts[token.Token.CollectionAddress] = d.contractToChainAgnostic(ctx, token.Token) } contractResults := util.MapValues(contracts) - return result, contractResults, nil + logger.For(ctx).Infof("zora tokens converted: %d (%d)", len(result), len(contractResults)) + + return result, contractResults + +} + +func (d *Provider) tokensToChainAgnostic(ctx context.Context, tokens []zoraToken) ([]multichain.ChainAgnosticToken, []multichain.ChainAgnosticContract) { + result := make([]multichain.ChainAgnosticToken, 0, len(tokens)) + contracts := map[string]multichain.ChainAgnosticContract{} + for _, token := range tokens { + converted, err := d.tokenToAgnostic(ctx, token) + if err != nil { + logger.For(ctx).Errorf("error converting zora token %+v: %s", token, err.Error()) + continue + } + result = append(result, converted) + + if _, ok := contracts[token.CollectionAddress]; ok { + continue + } + contracts[token.CollectionAddress] = d.contractToChainAgnostic(ctx, token) + + } + + contractResults := util.MapValues(contracts) + + logger.For(ctx).Infof("zora tokens converted: %d (%d)", len(result), len(contractResults)) + + return result, contractResults + +} + +func (*Provider) tokenToAgnostic(ctx context.Context, token zoraToken) (multichain.ChainAgnosticToken, error) { + var tokenType persist.TokenType + switch token.TokenStandard { + case "ERC721": + tokenType = persist.TokenTypeERC721 + case "ERC1155": + tokenType = persist.TokenTypeERC1155 + default: + return multichain.ChainAgnosticToken{}, fmt.Errorf("unknown token standard %s", token.TokenStandard) + } + metadataName, _ := token.Metadata["name"].(string) + metadataDescription, _ := token.Metadata["description"].(string) + + return multichain.ChainAgnosticToken{ + Descriptors: multichain.ChainAgnosticTokenDescriptors{ + Name: metadataName, + Description: metadataDescription, + }, + TokenType: tokenType, + TokenMetadata: token.Metadata, + TokenID: persist.TokenID(token.TokenID.toBase16String()), + Quantity: persist.HexString("1"), + OwnerAddress: persist.Address(strings.ToLower(token.Owner)), + ContractAddress: persist.Address(strings.ToLower(token.CollectionAddress)), + + FallbackMedia: persist.FallbackMedia{ + ImageURL: persist.NullString(token.Media.ImagePreview.EncodedPreview), + }, + }, nil } -func (d *Provider) contractToChainAgnostic(ctx context.Context, token zoraBalanceToken) multichain.ChainAgnosticContract { +func (d *Provider) contractToChainAgnostic(ctx context.Context, token zoraToken) multichain.ChainAgnosticContract { return multichain.ChainAgnosticContract{ Descriptors: multichain.ChainAgnosticContractDescriptors{ - Symbol: token.Token.Mintable.Collection.Symbol, - Name: token.Token.Mintable.Collection.Name, - Description: token.Token.Mintable.Collection.Description, - CreatorAddress: persist.Address(strings.ToLower(token.Token.Mintable.CreatorAddress)), + Symbol: token.Mintable.Collection.Symbol, + Name: token.Mintable.Collection.Name, + Description: token.Mintable.Collection.Description, + CreatorAddress: persist.Address(strings.ToLower(token.Mintable.CreatorAddress)), }, - Address: persist.Address(strings.ToLower(token.Token.CollectionAddress)), + Address: persist.Address(strings.ToLower(token.CollectionAddress)), } } From 17f229c577207c33bc6c7f33fb2df3ce848f422f Mon Sep 17 00:00:00 2001 From: Benny Date: Wed, 18 Oct 2023 20:23:54 -0400 Subject: [PATCH 3/6] Benny/email lock and templates (#1232) * distrubuted lock and new notification template data * share logic * message body * ack always --- emails/emails.go | 5 +- emails/send.go | 143 ++--------- emails/t__unit_test.go | 9 +- service/notifications/notifications.go | 334 +++++++++++++++++++------ 4 files changed, 283 insertions(+), 208 deletions(-) diff --git a/emails/emails.go b/emails/emails.go index fa820edee..d9a65d644 100644 --- a/emails/emails.go +++ b/emails/emails.go @@ -15,6 +15,7 @@ import ( "github.com/mikeydub/go-gallery/service/auth" "github.com/mikeydub/go-gallery/service/logger" "github.com/mikeydub/go-gallery/service/persist/postgres" + "github.com/mikeydub/go-gallery/service/redis" sentryutil "github.com/mikeydub/go-gallery/service/sentry" "github.com/mikeydub/go-gallery/service/tracing" "github.com/mikeydub/go-gallery/util" @@ -71,7 +72,9 @@ func coreInitServer() *gin.Engine { } } - go autoSendNotificationEmails(queries, sendgridClient, pub) + lock := redis.NewLockClient(redis.NewCache(redis.EmailThrottleCache)) + + go autoSendNotificationEmails(queries, sendgridClient, pub, lock) return handlersInitServer(router, loaders, queries, sendgridClient) } diff --git a/emails/send.go b/emails/send.go index 686793eea..53224f96e 100644 --- a/emails/send.go +++ b/emails/send.go @@ -4,11 +4,14 @@ import ( "context" "encoding/json" "fmt" - "github.com/mikeydub/go-gallery/service/auth" "net/http" "sync/atomic" "time" + "github.com/bsm/redislock" + "github.com/mikeydub/go-gallery/service/auth" + "github.com/mikeydub/go-gallery/service/notifications" + "cloud.google.com/go/pubsub" "github.com/gin-gonic/gin" "github.com/mikeydub/go-gallery/db/gen/coredb" @@ -103,17 +106,10 @@ func sendVerificationEmail(dataloaders *dataloader.Loaders, queries *coredb.Quer } } -type notificationEmailDynamicTemplateData struct { - Actor string `json:"actor"` - Action string `json:"action"` - CollectionName string `json:"collectionName"` - CollectionID persist.DBID `json:"collectionId"` - PreviewText string `json:"previewText"` -} type notificationsEmailDynamicTemplateData struct { - Notifications []notificationEmailDynamicTemplateData `json:"notifications"` - Username string `json:"username"` - UnsubscribeToken string `json:"unsubscribeToken"` + Notifications []notifications.UserFacingNotificationData `json:"notifications"` + Username string `json:"username"` + UnsubscribeToken string `json:"unsubscribeToken"` } func adminSendNotificationEmail(queries *coredb.Queries, s *sendgrid.Client) gin.HandlerFunc { @@ -141,18 +137,25 @@ func adminSendNotificationEmail(queries *coredb.Queries, s *sendgrid.Client) gin } } -func autoSendNotificationEmails(queries *coredb.Queries, s *sendgrid.Client, psub *pubsub.Client) error { +func autoSendNotificationEmails(queries *coredb.Queries, s *sendgrid.Client, psub *pubsub.Client, r *redislock.Client) error { ctx := context.Background() sub := psub.Subscription(env.GetString("PUBSUB_NOTIFICATIONS_EMAILS_SUBSCRIPTION")) return sub.Receive(ctx, func(ctx context.Context, msg *pubsub.Message) { - err := sendNotificationEmailsToAllUsers(ctx, queries, s, env.GetString("ENV") == "production") + l, err := r.Obtain(ctx, "send-notification-emails", time.Minute*5, nil) + if err != nil { + msg.Ack() + return + } + defer l.Release(ctx) + err = sendNotificationEmailsToAllUsers(ctx, queries, s, env.GetString("ENV") == "production") if err != nil { logger.For(ctx).Errorf("error sending notification emails: %s", err) msg.Nack() return } msg.Ack() + }) } @@ -198,17 +201,18 @@ func sendNotificationEmailToUser(c context.Context, u coredb.PiiUserView, emailR } data := notificationsEmailDynamicTemplateData{ - Notifications: make([]notificationEmailDynamicTemplateData, 0, resultLimit), + Notifications: make([]notifications.UserFacingNotificationData, 0, resultLimit), Username: u.Username.String, UnsubscribeToken: j, } - notifTemplates := make(chan notificationEmailDynamicTemplateData) + notifTemplates := make(chan notifications.UserFacingNotificationData) errChan := make(chan error) for _, n := range notifs { notif := n go func() { - notifTemplate, err := notifToTemplateData(c, queries, notif) + // notifTemplate, err := notifToTemplateData(c, queries, notif) + notifTemplate, err := notifications.NotificationToUserFacingData(c, queries, notif) if err != nil { errChan <- err return @@ -270,113 +274,6 @@ outer: return &rest.Response{StatusCode: 200, Body: "not sending real emails", Headers: map[string][]string{}}, nil } -func notifToTemplateData(ctx context.Context, queries *coredb.Queries, n coredb.Notification) (notificationEmailDynamicTemplateData, error) { - - switch n.Action { - case persist.ActionAdmiredFeedEvent: - feedEvent, err := queries.GetFeedEventByID(ctx, n.FeedEventID) - if err != nil { - return notificationEmailDynamicTemplateData{}, fmt.Errorf("failed to get feed event for admire %s: %w", n.FeedEventID, err) - } - collection, _ := queries.GetCollectionById(ctx, feedEvent.Data.CollectionID) - data := notificationEmailDynamicTemplateData{} - if collection.ID != "" && collection.Name.String != "" { - data.CollectionID = collection.ID - data.CollectionName = collection.Name.String - data.Action = "admired your additions to" - } else { - data.Action = "admired your gallery update" - } - if len(n.Data.AdmirerIDs) > 1 { - data.Actor = fmt.Sprintf("%d collectors", len(n.Data.AdmirerIDs)) - } else { - actorUser, err := queries.GetUserById(ctx, n.Data.AdmirerIDs[0]) - if err != nil { - return notificationEmailDynamicTemplateData{}, err - } - data.Actor = actorUser.Username.String - } - return data, nil - case persist.ActionUserFollowedUsers: - if len(n.Data.FollowerIDs) > 1 { - return notificationEmailDynamicTemplateData{ - Actor: fmt.Sprintf("%d users", len(n.Data.FollowerIDs)), - Action: "followed you", - }, nil - } - if len(n.Data.FollowerIDs) == 1 { - userActor, err := queries.GetUserById(ctx, n.Data.FollowerIDs[0]) - if err != nil { - return notificationEmailDynamicTemplateData{}, fmt.Errorf("failed to get user for follower %s: %w", n.Data.FollowerIDs[0], err) - } - action := "followed you" - if n.Data.FollowedBack { - action = "followed you back" - } - return notificationEmailDynamicTemplateData{ - Actor: userActor.Username.String, - Action: action, - }, nil - } - return notificationEmailDynamicTemplateData{}, fmt.Errorf("no follower ids") - case persist.ActionCommentedOnFeedEvent: - comment, err := queries.GetCommentByCommentID(ctx, n.CommentID) - if err != nil { - return notificationEmailDynamicTemplateData{}, fmt.Errorf("failed to get comment for comment %s: %w", n.CommentID, err) - } - userActor, err := queries.GetUserById(ctx, comment.ActorID) - if err != nil { - return notificationEmailDynamicTemplateData{}, fmt.Errorf("failed to get user for comment actor %s: %w", comment.ActorID, err) - } - feedEvent, err := queries.GetFeedEventByID(ctx, n.FeedEventID) - if err != nil { - return notificationEmailDynamicTemplateData{}, fmt.Errorf("failed to get feed event for comment %s: %w", n.FeedEventID, err) - } - collection, _ := queries.GetCollectionById(ctx, feedEvent.Data.CollectionID) - if collection.ID != "" { - return notificationEmailDynamicTemplateData{ - Actor: userActor.Username.String, - Action: "commented on your additions to", - CollectionName: collection.Name.String, - CollectionID: collection.ID, - PreviewText: util.TruncateWithEllipsis(comment.Comment, 20), - }, nil - } - return notificationEmailDynamicTemplateData{ - Actor: userActor.Username.String, - Action: "commented on your gallery update", - PreviewText: util.TruncateWithEllipsis(comment.Comment, 20), - }, nil - case persist.ActionViewedGallery: - if len(n.Data.AuthedViewerIDs)+len(n.Data.UnauthedViewerIDs) > 1 { - return notificationEmailDynamicTemplateData{ - Actor: fmt.Sprintf("%d collectors", len(n.Data.AuthedViewerIDs)+len(n.Data.UnauthedViewerIDs)), - Action: "viewed your gallery", - }, nil - } - if len(n.Data.AuthedViewerIDs) == 1 { - userActor, err := queries.GetUserById(ctx, n.Data.AuthedViewerIDs[0]) - if err != nil { - return notificationEmailDynamicTemplateData{}, fmt.Errorf("failed to get user for viewer %s: %w", n.Data.AuthedViewerIDs[0], err) - } - return notificationEmailDynamicTemplateData{ - Actor: userActor.Username.String, - Action: "viewed your gallery", - }, nil - } - if len(n.Data.UnauthedViewerIDs) == 1 { - return notificationEmailDynamicTemplateData{ - Actor: "Someone", - Action: "viewed your gallery", - }, nil - } - - return notificationEmailDynamicTemplateData{}, fmt.Errorf("no viewer ids") - default: - return notificationEmailDynamicTemplateData{}, fmt.Errorf("unknown action %s", n.Action) - } -} - func runForUsersWithNotificationsOnForEmailType(ctx context.Context, emailType persist.EmailType, queries *coredb.Queries, fn func(u coredb.PiiUserView) error) error { errGroup := new(errgroup.Group) var lastID persist.DBID diff --git a/emails/t__unit_test.go b/emails/t__unit_test.go index 8c1cdc352..73f58e8b7 100644 --- a/emails/t__unit_test.go +++ b/emails/t__unit_test.go @@ -6,6 +6,7 @@ import ( "time" "github.com/mikeydub/go-gallery/db/gen/coredb" + "github.com/mikeydub/go-gallery/service/notifications" ) func TestNotificationTemplating_Success(t *testing.T) { @@ -17,20 +18,20 @@ func TestNotificationTemplating_Success(t *testing.T) { q := coredb.New(pgx) t.Run("creates a template for admire notifications", func(t *testing.T) { - data, err := notifToTemplateData(ctx, q, admireNotif) + data, err := notifications.NotificationToUserFacingData(ctx, q, admireNotif) a.NoError(err) a.Equal(testUser2.Username.String, data.Actor) a.Equal(data.CollectionID, testGallery.Collections[0]) }) t.Run("creates a template for follow notifications", func(t *testing.T) { - data, err := notifToTemplateData(ctx, q, followNotif) + data, err := notifications.NotificationToUserFacingData(ctx, q, followNotif) a.NoError(err) a.Equal(testUser2.Username.String, data.Actor) }) t.Run("creates a template for comment notifications", func(t *testing.T) { - data, err := notifToTemplateData(ctx, q, commentNotif) + data, err := notifications.NotificationToUserFacingData(ctx, q, commentNotif) a.NoError(err) a.Equal(testUser2.Username.String, data.Actor) a.Equal(data.CollectionID, testGallery.Collections[0]) @@ -38,7 +39,7 @@ func TestNotificationTemplating_Success(t *testing.T) { }) t.Run("creates a template for view notifications", func(t *testing.T) { - data, err := notifToTemplateData(ctx, q, viewNotif) + data, err := notifications.NotificationToUserFacingData(ctx, q, viewNotif) a.NoError(err) a.Equal(testUser2.Username.String, data.Actor) }) diff --git a/service/notifications/notifications.go b/service/notifications/notifications.go index 276dab813..9c4331c93 100644 --- a/service/notifications/notifications.go +++ b/service/notifications/notifications.go @@ -18,6 +18,7 @@ import ( "github.com/bsm/redislock" "github.com/gin-gonic/gin" "github.com/googleapis/gax-go/v2/apierror" + "github.com/mikeydub/go-gallery/db/gen/coredb" db "github.com/mikeydub/go-gallery/db/gen/coredb" "github.com/mikeydub/go-gallery/env" "github.com/mikeydub/go-gallery/service/logger" @@ -527,6 +528,12 @@ func createPushMessage(ctx context.Context, notif db.Notification, queries *db.Q }, } + userFacing, err := NotificationToUserFacingData(ctx, queries, notif) + if err != nil { + return task.PushNotificationMessage{}, err + } + + message.Body = userFacing.String() if notif.Action == persist.ActionAdmiredFeedEvent || notif.Action == persist.ActionAdmiredPost { admirer, err := queries.GetUserById(ctx, notif.Data.AdmirerIDs[0]) if err != nil { @@ -537,16 +544,6 @@ func createPushMessage(ctx context.Context, notif db.Notification, queries *db.Q return task.PushNotificationMessage{}, err } - if !admirer.Username.Valid { - return task.PushNotificationMessage{}, fmt.Errorf("user with ID=%s has no username", admirer.ID) - } - - ac := "activity" - if notif.Action == persist.ActionAdmiredPost { - ac = "post" - } - - message.Body = fmt.Sprintf("%s admired your %s", admirer.Username.String, ac) return message, nil } @@ -565,16 +562,6 @@ func createPushMessage(ctx context.Context, notif db.Notification, queries *db.Q return task.PushNotificationMessage{}, err } - if !commenter.Username.Valid { - return task.PushNotificationMessage{}, fmt.Errorf("user with ID=%s has no username", commenter.ID) - } - - ac := "activity" - if notif.Action == persist.ActionCommentedOnPost { - ac = "post" - } - - message.Body = fmt.Sprintf("%s commented on your %s", commenter.Username.String, ac) return message, nil } @@ -588,47 +575,14 @@ func createPushMessage(ctx context.Context, notif db.Notification, queries *db.Q return task.PushNotificationMessage{}, err } - if !follower.Username.Valid { - return task.PushNotificationMessage{}, fmt.Errorf("user with ID=%s has no username", follower.ID) - } - - var body string - if notif.Data.FollowedBack { - body = fmt.Sprintf("%s followed you back", follower.Username.String) - } else { - body = fmt.Sprintf("%s followed you", follower.Username.String) - } - - message.Body = body return message, nil } if notif.Action == persist.ActionNewTokensReceived { - tm, err := queries.GetTokenMediaByTokenId(ctx, notif.Data.NewTokenID) - if err != nil { - return task.PushNotificationMessage{}, err - } - name := tm.Name - if name == "" { - to, err := queries.GetTokenById(ctx, notif.Data.NewTokenID) - if err != nil { - return task.PushNotificationMessage{}, err - } - name = to.Name.String - } - - name = util.TruncateWithEllipsis(name, 20) - if err := limiter.tryTokens(ctx, notif.OwnerID, notif.Data.NewTokenID); err != nil { return task.PushNotificationMessage{}, err } - amount := notif.Data.NewTokenQuantity - i := amount.BigInt().Uint64() - if i > 1 { - message.Body = fmt.Sprintf("You received %d new %s tokens", i, name) - } else { - message.Body = fmt.Sprintf("You received a new %s token", name) - } + return message, nil } @@ -648,11 +602,6 @@ func createPushMessage(ctx context.Context, notif db.Notification, queries *db.Q return task.PushNotificationMessage{}, err } - if !commenter.Username.Valid { - return task.PushNotificationMessage{}, fmt.Errorf("user with ID=%s has no username", commenter.ID) - } - - message.Body = fmt.Sprintf("%s replied to your comment", commenter.Username.String) return message, nil } @@ -660,35 +609,27 @@ func createPushMessage(ctx context.Context, notif db.Notification, queries *db.Q if notif.Action == persist.ActionMentionUser || notif.Action == persist.ActionMentionCommunity { var actor db.User - var mentionedIn string - var preview string + switch { case notif.CommentID != "": - mentionedIn = "comment" - comment, err := queries.GetCommentByCommentID(ctx, notif.CommentID) if err != nil { return task.PushNotificationMessage{}, err } - preview = util.TruncateWithEllipsis(comment.Comment, 20) - actor, err = queries.GetUserById(ctx, comment.ActorID) if err != nil { return task.PushNotificationMessage{}, err } case notif.PostID != "": - mentionedIn = "post" post, err := queries.GetPostByID(ctx, notif.PostID) if err != nil { return task.PushNotificationMessage{}, err } - preview = util.TruncateWithEllipsis(post.Caption.String, 20) - actor, err = queries.GetUserById(ctx, post.ActorID) if err != nil { return task.PushNotificationMessage{}, err @@ -697,33 +638,266 @@ func createPushMessage(ctx context.Context, notif db.Notification, queries *db.Q default: return task.PushNotificationMessage{}, fmt.Errorf("no comment or post id provided for mention notification") } - if mentionedIn == "" || preview == "" { - return task.PushNotificationMessage{}, fmt.Errorf("no push data found for mention notification") - } if err := limiter.tryMention(ctx, actor.ID, notif.OwnerID, notif.FeedEventID); err != nil { return task.PushNotificationMessage{}, err } + return message, nil + + } + + return task.PushNotificationMessage{}, fmt.Errorf("unsupported notification action: %s", notif.Action) +} + +type UserFacingNotificationData struct { + Actor string `json:"actor"` + Action string `json:"action"` + CollectionName string `json:"collectionName"` + CollectionID persist.DBID `json:"collectionId"` + PreviewText string `json:"previewText"` +} + +func (u UserFacingNotificationData) String() string { + cur := fmt.Sprintf("%s %s", u.Actor, u.Action) + if u.CollectionName != "" { + cur = fmt.Sprintf("%s %s", cur, u.CollectionName) + } + if u.PreviewText != "" { + cur = fmt.Sprintf("%s: %s", cur, u.PreviewText) + } + return cur +} + +func NotificationToUserFacingData(ctx context.Context, queries *coredb.Queries, n coredb.Notification) (UserFacingNotificationData, error) { + + switch n.Action { + case persist.ActionAdmiredFeedEvent, persist.ActionAdmiredPost: + feedEvent, err := queries.GetFeedEventByID(ctx, n.FeedEventID) + if err != nil { + return UserFacingNotificationData{}, fmt.Errorf("failed to get feed event for admire %s: %w", n.FeedEventID, err) + } + collection, _ := queries.GetCollectionById(ctx, feedEvent.Data.CollectionID) + data := UserFacingNotificationData{} + if n.Action == persist.ActionAdmiredFeedEvent && collection.ID != "" && collection.Name.String != "" { + data.CollectionID = collection.ID + data.CollectionName = collection.Name.String + data.Action = "admired your additions to" + } else if n.Action == persist.ActionAdmiredFeedEvent { + data.Action = "admired your gallery update" + } else { + data.Action = "admired your post" + } + if len(n.Data.AdmirerIDs) > 1 { + data.Actor = fmt.Sprintf("%d collectors", len(n.Data.AdmirerIDs)) + } else { + actorUser, err := queries.GetUserById(ctx, n.Data.AdmirerIDs[0]) + if err != nil { + return UserFacingNotificationData{}, err + } + data.Actor = actorUser.Username.String + } + return data, nil + case persist.ActionUserFollowedUsers: + if len(n.Data.FollowerIDs) > 1 { + return UserFacingNotificationData{ + Actor: fmt.Sprintf("%d users", len(n.Data.FollowerIDs)), + Action: "followed you", + }, nil + } + if len(n.Data.FollowerIDs) == 1 { + userActor, err := queries.GetUserById(ctx, n.Data.FollowerIDs[0]) + if err != nil { + return UserFacingNotificationData{}, fmt.Errorf("failed to get user for follower %s: %w", n.Data.FollowerIDs[0], err) + } + action := "followed you" + if n.Data.FollowedBack { + action = "followed you back" + } + return UserFacingNotificationData{ + Actor: userActor.Username.String, + Action: action, + }, nil + } + return UserFacingNotificationData{}, fmt.Errorf("no follower ids") + case persist.ActionCommentedOnFeedEvent, persist.ActionCommentedOnPost: + comment, err := queries.GetCommentByCommentID(ctx, n.CommentID) + if err != nil { + return UserFacingNotificationData{}, fmt.Errorf("failed to get comment for comment %s: %w", n.CommentID, err) + } + userActor, err := queries.GetUserById(ctx, comment.ActorID) + if err != nil { + return UserFacingNotificationData{}, fmt.Errorf("failed to get user for comment actor %s: %w", comment.ActorID, err) + } + feedEvent, err := queries.GetFeedEventByID(ctx, n.FeedEventID) + if err != nil { + return UserFacingNotificationData{}, fmt.Errorf("failed to get feed event for comment %s: %w", n.FeedEventID, err) + } + action := "commented on your post" + if n.Action == persist.ActionCommentedOnFeedEvent && feedEvent.Data.CollectionID != "" { + collection, err := queries.GetCollectionById(ctx, feedEvent.Data.CollectionID) + if err != nil { + return UserFacingNotificationData{}, fmt.Errorf("failed to get collection for comment %s: %w", feedEvent.Data.CollectionID, err) + } + + return UserFacingNotificationData{ + Actor: userActor.Username.String, + Action: "commented on your additions to", + CollectionName: collection.Name.String, + CollectionID: collection.ID, + PreviewText: util.TruncateWithEllipsis(comment.Comment, 20), + }, nil + + } else if n.Action == persist.ActionCommentedOnFeedEvent { + action = "commented on your gallery update" + } + + return UserFacingNotificationData{ + Actor: userActor.Username.String, + Action: action, + PreviewText: util.TruncateWithEllipsis(comment.Comment, 20), + }, nil + case persist.ActionViewedGallery: + if len(n.Data.AuthedViewerIDs)+len(n.Data.UnauthedViewerIDs) > 1 { + return UserFacingNotificationData{ + Actor: fmt.Sprintf("%d collectors", len(n.Data.AuthedViewerIDs)+len(n.Data.UnauthedViewerIDs)), + Action: "viewed your gallery", + }, nil + } + if len(n.Data.AuthedViewerIDs) == 1 { + userActor, err := queries.GetUserById(ctx, n.Data.AuthedViewerIDs[0]) + if err != nil { + return UserFacingNotificationData{}, fmt.Errorf("failed to get user for viewer %s: %w", n.Data.AuthedViewerIDs[0], err) + } + return UserFacingNotificationData{ + Actor: userActor.Username.String, + Action: "viewed your gallery", + }, nil + } + if len(n.Data.UnauthedViewerIDs) == 1 { + return UserFacingNotificationData{ + Actor: "Someone", + Action: "viewed your gallery", + }, nil + } + + return UserFacingNotificationData{}, fmt.Errorf("no viewer ids") + + case persist.ActionMentionUser, persist.ActionMentionCommunity: + + data := UserFacingNotificationData{} + var actor db.User + var mentionedIn string + var preview string + switch { + case n.CommentID != "": + + mentionedIn = "comment" + + comment, err := queries.GetCommentByCommentID(ctx, n.CommentID) + if err != nil { + return UserFacingNotificationData{}, err + } + + preview = util.TruncateWithEllipsis(comment.Comment, 20) + + actor, err = queries.GetUserById(ctx, comment.ActorID) + if err != nil { + return UserFacingNotificationData{}, err + } + + case n.PostID != "": + mentionedIn = "post" + + post, err := queries.GetPostByID(ctx, n.PostID) + if err != nil { + return UserFacingNotificationData{}, err + } + + preview = util.TruncateWithEllipsis(post.Caption.String, 20) + + actor, err = queries.GetUserById(ctx, post.ActorID) + if err != nil { + return UserFacingNotificationData{}, err + } + + default: + return UserFacingNotificationData{}, fmt.Errorf("no comment or post id provided for mention notification") + } + if mentionedIn == "" || preview == "" { + return UserFacingNotificationData{}, fmt.Errorf("no push data found for mention notification") + } + if !actor.Username.Valid { - return task.PushNotificationMessage{}, fmt.Errorf("user with ID=%s has no username", actor.ID) + return UserFacingNotificationData{}, fmt.Errorf("user with ID=%s has no username", actor.ID) } - if notif.Action == persist.ActionMentionCommunity { - contract, err := queries.GetContractByID(ctx, notif.ContractID) + data.Actor = actor.Username.String + data.PreviewText = preview + + if n.Action == persist.ActionMentionCommunity { + contract, err := queries.GetContractByID(ctx, n.ContractID) if err != nil { - return task.PushNotificationMessage{}, err + return UserFacingNotificationData{}, err } - message.Body = fmt.Sprintf("%s mentioned @%s in a %s: %s", actor.Username.String, contract.Name.String, mentionedIn, preview) + data.Action = fmt.Sprintf("mentioned your community @%s in a comment", contract.Name.String) } else { - message.Body = fmt.Sprintf("%s mentioned you in a %s: %s", actor.Username.String, mentionedIn, preview) + data.Action = "mentioned you in a comment" } - return message, nil + return data, nil + case persist.ActionReplyToComment: - } + comment, err := queries.GetCommentByCommentID(ctx, n.CommentID) + if err != nil { + return UserFacingNotificationData{}, err + } - return task.PushNotificationMessage{}, fmt.Errorf("unsupported notification action: %s", notif.Action) + commenter, err := queries.GetUserById(ctx, comment.ActorID) + if err != nil { + return UserFacingNotificationData{}, err + } + + if !commenter.Username.Valid { + return UserFacingNotificationData{}, fmt.Errorf("user with ID=%s has no username", commenter.ID) + } + + return UserFacingNotificationData{ + Actor: commenter.Username.String, + Action: "replied to your comment", + PreviewText: util.TruncateWithEllipsis(comment.Comment, 20), + }, nil + case persist.ActionNewTokensReceived: + data := UserFacingNotificationData{} + tm, err := queries.GetTokenMediaByTokenId(ctx, n.Data.NewTokenID) + if err != nil { + return UserFacingNotificationData{}, err + } + name := tm.Name + if name == "" { + to, err := queries.GetTokenById(ctx, n.Data.NewTokenID) + if err != nil { + return UserFacingNotificationData{}, err + } + name = to.Name.String + } + + name = util.TruncateWithEllipsis(name, 20) + + amount := n.Data.NewTokenQuantity + i := amount.BigInt().Uint64() + if i > 1 { + data.Actor = "you" + data.Action = fmt.Sprintf("received %d new %s tokens", i, name) + } else { + data.Actor = "you" + data.Action = fmt.Sprintf("received a new %s token", name) + } + + return data, nil + default: + return UserFacingNotificationData{}, fmt.Errorf("unknown action %s", n.Action) + } } func actionSupportsPushNotifications(action persist.Action) bool { From 708b9dbdcc510e6e3ef0a89162a3461dd9951452 Mon Sep 17 00:00:00 2001 From: Benny Date: Wed, 18 Oct 2023 20:24:03 -0400 Subject: [PATCH 4/6] someone posted your work notification (#1225) --- db/gen/coredb/query.sql.go | 48 ++++++++++++++++++++++ db/queries/core/query.sql | 3 ++ event/event.go | 4 ++ publicapi/feed.go | 55 +++++++++++++++++++++++++- service/notifications/notifications.go | 38 ++++++++++++++++++ service/persist/event.go | 2 + service/persist/notification.go | 3 ++ 7 files changed, 151 insertions(+), 2 deletions(-) diff --git a/db/gen/coredb/query.sql.go b/db/gen/coredb/query.sql.go index d30e35f2d..e0031851f 100644 --- a/db/gen/coredb/query.sql.go +++ b/db/gen/coredb/query.sql.go @@ -1214,6 +1214,54 @@ func (q *Queries) CreateUserEvent(ctx context.Context, arg CreateUserEventParams return i, err } +const createUserPostedYourWorkNotification = `-- name: CreateUserPostedYourWorkNotification :one +INSERT INTO notifications (id, owner_id, action, data, event_ids, post_id, contract_id) VALUES ($1, $2, $3, $4, $5, $7, $6) RETURNING id, deleted, owner_id, version, last_updated, created_at, action, data, event_ids, feed_event_id, comment_id, gallery_id, seen, amount, post_id, token_id, contract_id, mention_id +` + +type CreateUserPostedYourWorkNotificationParams struct { + ID persist.DBID `json:"id"` + OwnerID persist.DBID `json:"owner_id"` + Action persist.Action `json:"action"` + Data persist.NotificationData `json:"data"` + EventIds persist.DBIDList `json:"event_ids"` + ContractID persist.DBID `json:"contract_id"` + Post sql.NullString `json:"post"` +} + +func (q *Queries) CreateUserPostedYourWorkNotification(ctx context.Context, arg CreateUserPostedYourWorkNotificationParams) (Notification, error) { + row := q.db.QueryRow(ctx, createUserPostedYourWorkNotification, + arg.ID, + arg.OwnerID, + arg.Action, + arg.Data, + arg.EventIds, + arg.ContractID, + arg.Post, + ) + var i Notification + err := row.Scan( + &i.ID, + &i.Deleted, + &i.OwnerID, + &i.Version, + &i.LastUpdated, + &i.CreatedAt, + &i.Action, + &i.Data, + &i.EventIds, + &i.FeedEventID, + &i.CommentID, + &i.GalleryID, + &i.Seen, + &i.Amount, + &i.PostID, + &i.TokenID, + &i.ContractID, + &i.MentionID, + ) + return i, err +} + const createViewGalleryNotification = `-- name: CreateViewGalleryNotification :one INSERT INTO notifications (id, owner_id, action, data, event_ids, gallery_id) VALUES ($1, $2, $3, $4, $5, $6) RETURNING id, deleted, owner_id, version, last_updated, created_at, action, data, event_ids, feed_event_id, comment_id, gallery_id, seen, amount, post_id, token_id, contract_id, mention_id ` diff --git a/db/queries/core/query.sql b/db/queries/core/query.sql index cbbd1bc0c..4c7439e70 100644 --- a/db/queries/core/query.sql +++ b/db/queries/core/query.sql @@ -720,6 +720,9 @@ INSERT INTO notifications (id, owner_id, action, data, event_ids, feed_event_id, -- name: CreateContractNotification :one INSERT INTO notifications (id, owner_id, action, data, event_ids, feed_event_id, post_id, comment_id, contract_id, mention_id) VALUES ($1, $2, $3, $4, $5, sqlc.narg('feed_event'), sqlc.narg('post'), sqlc.narg('comment'), $6, $7) RETURNING *; +-- name: CreateUserPostedYourWorkNotification :one +INSERT INTO notifications (id, owner_id, action, data, event_ids, post_id, contract_id) VALUES ($1, $2, $3, $4, $5, sqlc.narg('post'), $6) RETURNING *; + -- name: CreateSimpleNotification :one INSERT INTO notifications (id, owner_id, action, data, event_ids) VALUES ($1, $2, $3, $4, $5) RETURNING *; diff --git a/event/event.go b/event/event.go index 05ed3e52d..95cc2bee0 100644 --- a/event/event.go +++ b/event/event.go @@ -66,6 +66,7 @@ func AddTo(ctx *gin.Context, disableDataloaderCaching bool, notif *notifications sender.addDelayedHandler(notifications, persist.ActionMentionUser, notificationHandler) sender.addDelayedHandler(notifications, persist.ActionMentionCommunity, notificationHandler) sender.addDelayedHandler(notifications, persist.ActionNewTokensReceived, notificationHandler) + sender.addDelayedHandler(notifications, persist.ActionUserPostedYourWork, notificationHandler) sender.feed = feed sender.notifications = notifications @@ -493,6 +494,8 @@ func (h notificationHandler) createNotificationDataForEvent(event db.Event) (dat data.NewTokenQuantity = event.Data.NewTokenQuantity case persist.ActionReplyToComment: data.OriginalCommentID = event.SubjectID + case persist.ActionUserPostedYourWork: + data.YourContractID = event.Data.YourContractID default: logger.For(nil).Debugf("no notification data for event: %s", event.Action) } @@ -555,6 +558,7 @@ func (h notificationHandler) findOwnerForNotificationFromEvent(ctx context.Conte return "", nil } return u.CreatorUserID, nil + } return "", fmt.Errorf("no owner found for event: %s", event.Action) diff --git a/publicapi/feed.go b/publicapi/feed.go index 794c6d09a..a453d7216 100644 --- a/publicapi/feed.go +++ b/publicapi/feed.go @@ -142,8 +142,8 @@ func (api FeedAPI) PostTokens(ctx context.Context, tokenIDs []persist.DBID, ment return "", err } - contractIDs, _ := util.Map(contracts, func(c db.Contract) (persist.DBID, error) { - return c.ID, nil + contractIDs := util.MapWithoutError(contracts, func(c db.Contract) persist.DBID { + return c.ID }) tx, err := api.repos.BeginTx(ctx) @@ -207,6 +207,25 @@ func (api FeedAPI) PostTokens(ctx context.Context, tokenIDs []persist.DBID, ment return "", err } + creators, _ := api.loaders.ContractCreatorByContractID.LoadAll(contractIDs) + for _, creator := range creators { + if creator.CreatorUserID == "" { + continue + } + err = event.Dispatch(ctx, db.Event{ + ActorID: persist.DBIDToNullStr(actorID), + Action: persist.ActionUserPostedYourWork, + ResourceTypeID: persist.ResourceTypeContract, + UserID: creator.CreatorUserID, + SubjectID: creator.ContractID, + PostID: postID, + ContractID: creator.ContractID, + }) + if err != nil { + logger.For(ctx).Errorf("error dispatching event: %v", err) + } + } + err = event.Dispatch(ctx, db.Event{ ActorID: persist.DBIDToNullStr(actorID), Action: persist.ActionUserPosted, @@ -273,6 +292,22 @@ func (api FeedAPI) ReferralPostToken(ctx context.Context, t persist.TokenIdentif return postID, err } + creator, _ := api.loaders.ContractCreatorByContractID.Load(contract.ID) + if creator.CreatorUserID != "" { + err = event.Dispatch(ctx, db.Event{ + ActorID: persist.DBIDToNullStr(userID), + Action: persist.ActionUserPostedYourWork, + ResourceTypeID: persist.ResourceTypeContract, + UserID: creator.CreatorUserID, + SubjectID: creator.ContractID, + PostID: postID, + ContractID: creator.ContractID, + }) + if err != nil { + logger.For(ctx).Errorf("error dispatching event: %v", err) + } + } + err = event.Dispatch(ctx, db.Event{ ActorID: persist.DBIDToNullStr(user.ID), Action: persist.ActionUserPosted, @@ -311,6 +346,22 @@ func (api FeedAPI) ReferralPostToken(ctx context.Context, t persist.TokenIdentif return postID, err } + creator, _ := api.loaders.ContractCreatorByContractID.Load(synced.Contract) + if creator.CreatorUserID != "" { + err = event.Dispatch(ctx, db.Event{ + ActorID: persist.DBIDToNullStr(userID), + Action: persist.ActionUserPostedYourWork, + ResourceTypeID: persist.ResourceTypeContract, + UserID: creator.CreatorUserID, + SubjectID: creator.ContractID, + PostID: postID, + ContractID: creator.ContractID, + }) + if err != nil { + logger.For(ctx).Errorf("error dispatching event: %v", err) + } + } + err = event.Dispatch(ctx, db.Event{ ActorID: persist.DBIDToNullStr(user.ID), Action: persist.ActionUserPosted, diff --git a/service/notifications/notifications.go b/service/notifications/notifications.go index 9c4331c93..e0b43c7fe 100644 --- a/service/notifications/notifications.go +++ b/service/notifications/notifications.go @@ -164,6 +164,7 @@ func New(queries *db.Queries, pub *pubsub.Client, taskClient *cloudtasks.Client, notifDispatcher.AddHandler(persist.ActionReplyToComment, def) notifDispatcher.AddHandler(persist.ActionMentionUser, def) notifDispatcher.AddHandler(persist.ActionMentionCommunity, def) + notifDispatcher.AddHandler(persist.ActionUserPostedYourWork, def) // notification actions that are grouped by token id notifDispatcher.AddHandler(persist.ActionNewTokensReceived, tokenIDGrouped) @@ -647,6 +648,31 @@ func createPushMessage(ctx context.Context, notif db.Notification, queries *db.Q } + if notif.Action == persist.ActionUserPostedYourWork { + + post, err := queries.GetPostByID(ctx, notif.PostID) + if err != nil { + return task.PushNotificationMessage{}, err + } + actor, err := queries.GetUserById(ctx, post.ActorID) + if err != nil { + return task.PushNotificationMessage{}, err + } + + if err := limiter.tryMention(ctx, actor.ID, notif.OwnerID, notif.FeedEventID); err != nil { + return task.PushNotificationMessage{}, err + } + + if !actor.Username.Valid { + return task.PushNotificationMessage{}, fmt.Errorf("user with ID=%s has no username", actor.ID) + } + contract, err := queries.GetContractByID(ctx, notif.ContractID) + if err != nil { + return task.PushNotificationMessage{}, err + } + message.Body = fmt.Sprintf("%s posted your work: %s", actor.Username.String, contract.Name.String) + } + return task.PushNotificationMessage{}, fmt.Errorf("unsupported notification action: %s", notif.Action) } @@ -914,6 +940,8 @@ func actionSupportsPushNotifications(action persist.Action) bool { return true case persist.ActionAdmiredPost: return true + case persist.ActionUserPostedYourWork: + return true default: return false } @@ -1146,6 +1174,16 @@ func addNotification(ctx context.Context, notif db.Notification, queries *db.Que MentionID: notif.MentionID, ContractID: notif.ContractID, }) + case persist.ActionUserPostedYourWork: + return queries.CreateUserPostedYourWorkNotification(ctx, db.CreateUserPostedYourWorkNotificationParams{ + ID: id, + OwnerID: notif.OwnerID, + Action: notif.Action, + Data: notif.Data, + EventIds: notif.EventIds, + Post: util.ToNullString(notif.PostID.String(), true), + ContractID: notif.ContractID, + }) default: return db.Notification{}, fmt.Errorf("unknown notification action: %s", notif.Action) } diff --git a/service/persist/event.go b/service/persist/event.go index 93ad22553..62dcb3c4d 100644 --- a/service/persist/event.go +++ b/service/persist/event.go @@ -21,6 +21,7 @@ const ( ActionUserCreated Action = "UserCreated" ActionUserFollowedUsers Action = "UserFollowedUsers" ActionUserPosted Action = "UserPosted" + ActionUserPostedYourWork Action = "UserPostedYourWork" ActionCollectorsNoteAddedToToken Action = "CollectorsNoteAddedToToken" ActionCollectionCreated Action = "CollectionCreated" ActionCollectorsNoteAddedToCollection Action = "CollectorsNoteAddedToCollection" @@ -58,6 +59,7 @@ type EventData struct { GalleryNewTokenIDs map[DBID]DBIDList `json:"gallery_new_token_ids"` GalleryNewCollections DBIDList `json:"gallery_new_collections"` GalleryNewTokenCollectorsNotes map[DBID]string `json:"gallery_new_token_collectors_notes"` + YourContractID DBID `json:"your_contract_id"` } type FeedEventData struct { diff --git a/service/persist/notification.go b/service/persist/notification.go index f74583eb4..4f0691613 100644 --- a/service/persist/notification.go +++ b/service/persist/notification.go @@ -14,6 +14,7 @@ type NotificationData struct { NewTokenID DBID `json:"new_token_id,omitempty"` NewTokenQuantity HexString `json:"new_token_quantity,omitempty"` OriginalCommentID DBID `json:"original_comment_id,omitempty"` + YourContractID DBID `json:"your_contract_id,omitempty"` } func (n NotificationData) Validate() NotificationData { @@ -25,6 +26,7 @@ func (n NotificationData) Validate() NotificationData { result.NewTokenID = n.NewTokenID result.NewTokenQuantity = n.NewTokenQuantity result.OriginalCommentID = n.OriginalCommentID + result.YourContractID = n.YourContractID return result } @@ -40,6 +42,7 @@ func (n NotificationData) Concat(other NotificationData) NotificationData { result.NewTokenQuantity = other.NewTokenQuantity.Add(n.NewTokenQuantity) result.NewTokenID = DBID(util.FirstNonEmptyString(other.NewTokenID.String(), n.NewTokenID.String())) result.OriginalCommentID = DBID(util.FirstNonEmptyString(other.OriginalCommentID.String(), n.OriginalCommentID.String())) + result.YourContractID = DBID(util.FirstNonEmptyString(other.YourContractID.String(), n.YourContractID.String())) return result.Validate() } From e7e93389292d6e0d7251d82614e660c5a1fb289d Mon Sep 17 00:00:00 2001 From: Benny Date: Mon, 23 Oct 2023 10:02:04 -0700 Subject: [PATCH 5/6] check for weird prefix in token ID (#1235) --- Makefile | 2 +- service/multichain/zora/zora.go | 13 +++++++++++-- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/Makefile b/Makefile index 52a22b896..b39b31d1d 100644 --- a/Makefile +++ b/Makefile @@ -272,7 +272,7 @@ $(DEPLOY)-%-userpref-upload : DOCKER_FILE := $(DOCKER_DIR)/userpre $(PROMOTE)-%-backend : SERVICE := default $(PROMOTE)-%-indexer : SERVICE := indexer $(PROMOTE)-%-indexer-server : SERVICE := indexer-api -$(PROMOTE)-%-emails : SERVICE := emails +$(PROMOTE)-%-emails : SERVICE := emails-v2 $(PROMOTE)-%-tokenprocessing : SERVICE := tokenprocessing $(PROMOTE)-%-tokenprocessing : SERVICE := tokenprocessing-v3 $(PROMOTE)-%-autosocial : SERVICE := autosocial diff --git a/service/multichain/zora/zora.go b/service/multichain/zora/zora.go index 2e555df81..91d1e2d05 100644 --- a/service/multichain/zora/zora.go +++ b/service/multichain/zora/zora.go @@ -29,9 +29,18 @@ type Provider struct { type tokenID string func (t tokenID) toBase16String() string { - big, ok := new(big.Int).SetString(string(t), 10) + copy := string(t) + if strings.Contains("-", copy) { + // take what is after the dash + parts := strings.Split(copy, "-") + if len(parts) != 2 { + panic(fmt.Sprintf("invalid token id %s", t)) + } + copy = parts[1] + } + big, ok := new(big.Int).SetString(copy, 10) if !ok { - panic("invalid token ID") + panic(fmt.Sprintf("invalid token id %s", t)) } return big.Text(16) } From c355c6dd82a38c179f5bd0b185f20bc156e5b6a1 Mon Sep 17 00:00:00 2001 From: Benny Date: Mon, 23 Oct 2023 12:00:43 -0700 Subject: [PATCH 6/6] fix local key/task queue, logs (#1236) --- autosocial/handler.go | 1 + docker-compose.yml | 75 +++++++++++-------- publicapi/user.go | 1 - secrets/local/local/app-local-autosocial.yaml | 8 +- secrets/local/local/app-local-backend.yaml | 6 +- service/farcaster/neynar.go | 9 +++ service/socialauth/socialauth.go | 7 +- 7 files changed, 66 insertions(+), 41 deletions(-) diff --git a/autosocial/handler.go b/autosocial/handler.go index d9c5bc513..51d8d6911 100644 --- a/autosocial/handler.go +++ b/autosocial/handler.go @@ -115,6 +115,7 @@ func addFarcasterProfileToUser(ctx context.Context, n *farcaster.NeynarAPI, addr } u, err := n.UserByAddress(ctx, a.Address()) if err != nil { + logrus.Error(err) continue } if u.Fid.String() == "" { diff --git a/docker-compose.yml b/docker-compose.yml index ba7707530..b8d2d073e 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,13 +1,13 @@ -version: "3.9" +version: '3.9' services: redis: - image: "redis:6" + image: 'redis:6' ports: - - "6379:6379" + - '6379:6379' postgres: build: - context: "docker/postgres" - dockerfile: "DOCKERFILE" + context: 'docker/postgres' + dockerfile: 'DOCKERFILE' args: - PGHOST=${PGHOST} - PGPORT=${PGPORT} @@ -16,18 +16,18 @@ services: - PGDATABASE=${PGDATABASE} - PGTESTUSER=${PGTESTUSER} ports: - - "5432:5432" + - '5432:5432' environment: - POSTGRES_HOST_AUTH_METHOD=trust - POSTGRES_USER=postgres - POSTGRES_DB=postgres volumes: - - "${PWD}/docker/postgres/postgres.conf:/etc/postgresql/postgresql.conf" - command: ["-c", "config-file=/etc/postgresql/postgresql.conf"] + - '${PWD}/docker/postgres/postgres.conf:/etc/postgresql/postgresql.conf' + command: ['-c', 'config-file=/etc/postgresql/postgresql.conf'] postgres_indexer: build: - context: "." - dockerfile: "docker/postgres_indexer/DOCKERFILE" + context: '.' + dockerfile: 'docker/postgres_indexer/DOCKERFILE' args: - PGHOST=${PGHOST} - PGPORT=${PGPORT} @@ -36,7 +36,7 @@ services: - PGDATABASE=${PGDATABASE} - PGTESTUSER=${PGTESTUSER} ports: - - "5433:5432" + - '5433:5432' environment: - POSTGRES_HOST_AUTH_METHOD=trust - POSTGRES_USER=postgres @@ -44,31 +44,44 @@ services: task-emulator: image: ghcr.io/aertje/cloud-tasks-emulator:latest ports: - - "8123:8123" + - '8123:8123' expose: - - "8123" - command: [ - "-host", "0.0.0.0", - "-port", "8123", - "-queue", "projects/gallery-local/locations/here/queues/feedbot", - "-queue", "projects/gallery-local/locations/here/queues/feed-event", - "-queue", "projects/gallery-local/locations/here/queues/token-processing", - "-queue", "projects/gallery-local/locations/here/queues/indexer-refreshes", - "-queue", "projects/gallery-local/locations/here/queues/wallet-validate", - "-queue", "projects/gallery-local/locations/here/queues/push-notifications", - "-queue", "projects/gallery-local/locations/here/queues/email", - ] + - '8123' + command: + [ + '-host', + '0.0.0.0', + '-port', + '8123', + '-queue', + 'projects/gallery-local/locations/here/queues/feedbot', + '-queue', + 'projects/gallery-local/locations/here/queues/feed-event', + '-queue', + 'projects/gallery-local/locations/here/queues/token-processing', + '-queue', + 'projects/gallery-local/locations/here/queues/indexer-refreshes', + '-queue', + 'projects/gallery-local/locations/here/queues/wallet-validate', + '-queue', + 'projects/gallery-local/locations/here/queues/push-notifications', + '-queue', + 'projects/gallery-local/locations/here/queues/email', + '-queue', + 'projects/gallery-local/locations/here/queues/autosocial', + ] pubsub-emulator: image: gcr.io/google.com/cloudsdktool/google-cloud-cli:emulators expose: - - "8085" + - '8085' ports: - - "8085:8085" - command: [ - "/bin/bash", - "-c", - "gcloud beta emulators pubsub start --host-port=0.0.0.0:8085 --project=gallery-local" - ] + - '8085:8085' + command: + [ + '/bin/bash', + '-c', + 'gcloud beta emulators pubsub start --host-port=0.0.0.0:8085 --project=gallery-local', + ] # Uncomment if you want to run tokenprocessing locally as a container # tokenprocessing: diff --git a/publicapi/user.go b/publicapi/user.go index 007b8a916..1508d24ec 100644 --- a/publicapi/user.go +++ b/publicapi/user.go @@ -585,7 +585,6 @@ func (api UserAPI) CreateUser(ctx context.Context, authenticator auth.Authentica }) if err != nil { logger.For(ctx).Errorf("failed to dispatch event: %s", err) - return userID, galleryID, err } err = task.CreateTaskForAutosocialProcessUsers(ctx, task.AutosocialProcessUsersMessage{ diff --git a/secrets/local/local/app-local-autosocial.yaml b/secrets/local/local/app-local-autosocial.yaml index f32de3da9..0b109845b 100644 --- a/secrets/local/local/app-local-autosocial.yaml +++ b/secrets/local/local/app-local-autosocial.yaml @@ -1,4 +1,6 @@ -NEYNAR_API_KEY: ENC[AES256_GCM,data:lOgGUa7gpiGp/yU8IqzRnaZDpAO4ePG+2/3hRHwLPb7ghZFe,iv:diiA6IRDT9Jah88i7NYuSffeHLJJHomwekm6wt/dRQQ=,tag:/96dvAyKKe6XtgnQdWSLmQ==,type:str] +AUTOSOCIAL_QUEUE: ENC[AES256_GCM,data:P9KDiiVbxOedlUWH5GcedvjT//DSOXE0qvZXmVcnF70HHKRbAdaRGcn173h9yGkB0KQFT0ujPg==,iv:KEoQ0D0mD9Z2GFMeYLaL3lOWeH9Uwv59hgsFZ1FNCwc=,tag:KrJBwJW/giCRn86EKEUDEQ==,type:str] +AUTOSOCIAL_URL: ENC[AES256_GCM,data:5NXPZ1PPlLBEkbo76JUNmXl98F54Jhv64UgSdHOwVeU=,iv:QOatTDOPaJtvq7oUo5aaQsl3tjheSh+vgpdVTCe6h68=,tag:gV4COGfBpyirxvFcs6u0gQ==,type:str] +NEYNAR_API_KEY: ENC[AES256_GCM,data:I5XxTPpPbYCmXkH2dhNMtaPSAJzvnY1WOTw5USQEZ1BKuLuV,iv:SejOjG7SxuNt8PmAOZi2Qh32ZfrQ2njN3J+ByXTDmiM=,tag:WczFOF3+ucW5pBVN1yKzSg==,type:str] sops: kms: [] gcp_kms: @@ -8,8 +10,8 @@ sops: azure_kv: [] hc_vault: [] age: [] - lastmodified: "2023-09-26T23:09:31Z" - mac: ENC[AES256_GCM,data:Tmfg6f3r26QtTfTfxkNArwE+Rb2PFk3Q1aOi1F9jv71ofJwgBcUhllyIHbYEdKpaKCK/LlLLI/qfF+LDiMGYnAemaiTUcaU6UOWYSKMTRll3/IicSir245bW334HAZvk89MPXG49W22SrxuXwcZDug4mesQP1Sg9s4YUcVqRXbQ=,iv:J0M+WJtfiijQ1pj1FYABv3CuW2uw+iSAxCSOEM+ttl0=,tag:wlc/RloBcwrox4lWhVkB+Q==,type:str] + lastmodified: "2023-10-23T18:57:01Z" + mac: ENC[AES256_GCM,data:Yo7MCeulEensGk+1xj0Oy8j86fsCNusA4V/glN6KevqrvDAv9kQLLWtCceDRC9wmV3L2h5o2e/DZtxi7XihtuEaTSdSOnnJfPmpZDIwos3641ypO4B8Vjs7IU5VOB88+QEqLRgp4nRPUvKrzV3VC2mN66qmLLxaDcipiYemMZFY=,iv:tT/ulsRk1awwA51JoT0uVV6lhMGz9ecr2Mr4sLuCagg=,tag:Qz6BPv2X4eEnuQnFOyvW3Q==,type:str] pgp: [] unencrypted_suffix: _unencrypted version: 3.7.3 diff --git a/secrets/local/local/app-local-backend.yaml b/secrets/local/local/app-local-backend.yaml index 69082dc8a..72a5afb88 100644 --- a/secrets/local/local/app-local-backend.yaml +++ b/secrets/local/local/app-local-backend.yaml @@ -43,7 +43,7 @@ RPC_URL: ENC[AES256_GCM,data:Qt54Eru5hohZXpV2pUCx/0+dg7CpVK7bHYEGye/fej+WV2HxjGo GCLOUD_USER_PREF_BUCKET: ENC[AES256_GCM,data:+abxLn4GAtNR/PRAjg==,iv:pywnL1YJzPfgEyHVvCJeJc83WaScMhL81QNCJasTzac=,tag:+91RyaN4clWzmXURuXqxMA==,type:str] EMAIL_QUEUE: ENC[AES256_GCM,data:DZlQknhz+9UvLcin3j4iamhRp4y0ZrsRmcuhEArrXtBro1b58ov6T1PnSHw43pUgc5c=,iv:KrJQSR1FnwVA9Ue0cJTzqmLwz7DG/dF8LvI1aNWMic4=,tag:Bba+2d2hH3vaABLqPc1knQ==,type:str] AUTOSOCIAL_QUEUE: ENC[AES256_GCM,data:VPxQ8izFihl26rbrTe/sBgb3x+IvIobtPWKcDTUGuqAXlwgT4LNxVs4biB3fsP02nsJ7Z5SKug==,iv:CzPRmkSIt6rxkQ9GmWQl1UEbFTnuKZzqO92Jp+gf3MI=,tag:sFqz7HLDHc/6+CFDJZSJLg==,type:str] -AUTOSOCIAL_URL: ENC[AES256_GCM,data:qCx4KiFSi3l7grzCAjU=,iv:m2asHtCG3V5WtUI+/sW34DtteP+sw2ogSLFG7d4L5IU=,tag:pnquIO4OYPKc15ZoVtwbDw==,type:str] +AUTOSOCIAL_URL: ENC[AES256_GCM,data:Jx2v/E4x841LeDjmiUUmTT70IBfJmmzbhmCUTd/aqUM=,iv:n8NkhZosTCwo0AEXuRvQoVvlzBcEdWa1Swa2gWIA8yU=,tag:BH5wARsJ4w31HFkVfMBN7w==,type:str] sops: kms: [] gcp_kms: @@ -53,8 +53,8 @@ sops: azure_kv: [] hc_vault: [] age: [] - lastmodified: "2023-10-03T19:58:39Z" - mac: ENC[AES256_GCM,data:bjQXDq+03Aw7pkZUHBbYf+KKmkTtmn2TDB5Hgkaj0aLBILtXO/x1dQcsqs3iSAMgIJIELKfHj5Ff2pe04VZSnaHwhYwZXyiJvdwDLMmYDlH/QZqnvNxrQlmxhOe8JnP8tQLwsHdVAbSGF77yzaoYtoVG/79gEeUvH5+HimwpP4s=,iv:ZxKRHRVLBwlZSnt7+ioPJWURlit5eEBo7IGcCSP6x/E=,tag:pz8bqAOABfAxc0PZhNHrKQ==,type:str] + lastmodified: "2023-10-23T18:41:58Z" + mac: ENC[AES256_GCM,data:8BrdTVeD9/Gb4YZF8ds94zTFb1UrU/eCtZ4POBUJYdYLgddFCXezomJQNr0QJrOI/pAc6ObKa31yuVpZTkYGc9yC6uNMWF+oVMdtzlJkXuRIG6zlBmaBGZr0MIWWk3P5RY6zGGpBIHbHTCIFrMscXRxIizss63XxTzIsqPTpev8=,iv:epX4hH8qJWhDtSdIfhD2IeLIZUPf8/LrPJPpe9p6Wng=,tag:y31wz30w9UDBgaeub9VW6Q==,type:str] pgp: [] unencrypted_suffix: _unencrypted version: 3.7.3 diff --git a/service/farcaster/neynar.go b/service/farcaster/neynar.go index 070ea465d..37e8239c9 100644 --- a/service/farcaster/neynar.go +++ b/service/farcaster/neynar.go @@ -4,6 +4,7 @@ import ( "context" "encoding/json" "fmt" + "io" "net/http" "github.com/mikeydub/go-gallery/env" @@ -111,6 +112,14 @@ func (n *NeynarAPI) UserByAddress(ctx context.Context, address persist.Address) return NeynarUser{}, err } + if resp.StatusCode != http.StatusOK { + bs, err := io.ReadAll(resp.Body) + if err != nil { + return NeynarUser{}, err + } + return NeynarUser{}, fmt.Errorf("neynar returned status %d (%s)", resp.StatusCode, bs) + } + defer resp.Body.Close() var neynarResp NeynarUserByVerificationResponse diff --git a/service/socialauth/socialauth.go b/service/socialauth/socialauth.go index 79a57b364..fd91163b1 100644 --- a/service/socialauth/socialauth.go +++ b/service/socialauth/socialauth.go @@ -2,6 +2,7 @@ package socialauth import ( "context" + "fmt" "net/http" "github.com/mikeydub/go-gallery/db/gen/coredb" @@ -72,11 +73,11 @@ type FarcasterAuthenticator struct { func (a FarcasterAuthenticator) Authenticate(ctx context.Context) (*SocialAuthResult, error) { api := farcaster.NewNeynarAPI(a.HTTPClient) user, err := a.Queries.GetUserByAddressAndL1(ctx, coredb.GetUserByAddressAndL1Params{ - Address: a.Address, + Address: persist.Address(persist.ChainETH.NormalizeAddress(a.Address)), L1Chain: persist.L1Chain(persist.ChainETH), }) if err != nil { - return nil, err + return nil, fmt.Errorf("get user by address and l1: %w", err) } if user.ID != a.UserID { @@ -88,7 +89,7 @@ func (a FarcasterAuthenticator) Authenticate(ctx context.Context) (*SocialAuthRe fu, err := api.UserByAddress(ctx, a.Address) if err != nil { - return nil, err + return nil, fmt.Errorf("get user by address: %w", err) } return &SocialAuthResult{