From 49032d24e956b0a7f49747a3e03f2669762d3f1d Mon Sep 17 00:00:00 2001 From: radazen Date: Tue, 19 Sep 2023 06:48:11 -0400 Subject: [PATCH] Add temporary functionality to filter posts by Prohibition project ID (#1185) --- db/gen/coredb/query.sql.go | 72 ++++++++++ db/queries/core/query.sql | 21 +++ graphql/generated/generated.go | 189 ++++++++++++++++++++++++--- graphql/model/models_gen.go | 37 +++--- graphql/resolver/schema.resolvers.go | 10 ++ graphql/schema/schema.graphql | 8 ++ publicapi/contract.go | 74 +++++++++++ 7 files changed, 374 insertions(+), 37 deletions(-) diff --git a/db/gen/coredb/query.sql.go b/db/gen/coredb/query.sql.go index d7deed214..355451f29 100644 --- a/db/gen/coredb/query.sql.go +++ b/db/gen/coredb/query.sql.go @@ -5617,6 +5617,78 @@ func (q *Queries) PaginatePersonalFeedByUserID(ctx context.Context, arg Paginate return items, nil } +const paginatePostsByContractIDAndProjectID = `-- name: PaginatePostsByContractIDAndProjectID :many +with valid_post_ids as ( + SELECT distinct on (posts.id) posts.id + FROM posts + JOIN tokens on tokens.id = ANY(posts.token_ids) + and tokens.displayable + and tokens.deleted = false + and tokens.contract = $7 + and ('x' || lpad(substring(tokens.token_id, 1, 16), 16, '0'))::bit(64)::bigint / 1000000 = $8::int + WHERE $7 = ANY(posts.contract_ids) + AND posts.deleted = false +) +SELECT posts.id, posts.version, posts.token_ids, posts.contract_ids, posts.actor_id, posts.caption, posts.created_at, posts.last_updated, posts.deleted from posts + join valid_post_ids on posts.id = valid_post_ids.id +WHERE (posts.created_at, posts.id) < ($1, $2) + AND (posts.created_at, posts.id) > ($3, $4) +ORDER BY + CASE WHEN $5::bool THEN (posts.created_at, posts.id) END ASC, + CASE WHEN NOT $5::bool THEN (posts.created_at, posts.id) END DESC +LIMIT $6 +` + +type PaginatePostsByContractIDAndProjectIDParams struct { + CurBeforeTime time.Time `json:"cur_before_time"` + CurBeforeID persist.DBID `json:"cur_before_id"` + CurAfterTime time.Time `json:"cur_after_time"` + CurAfterID persist.DBID `json:"cur_after_id"` + PagingForward bool `json:"paging_forward"` + Limit int32 `json:"limit"` + ContractID persist.DBID `json:"contract_id"` + ProjectIDInt int32 `json:"project_id_int"` +} + +func (q *Queries) PaginatePostsByContractIDAndProjectID(ctx context.Context, arg PaginatePostsByContractIDAndProjectIDParams) ([]Post, error) { + rows, err := q.db.Query(ctx, paginatePostsByContractIDAndProjectID, + arg.CurBeforeTime, + arg.CurBeforeID, + arg.CurAfterTime, + arg.CurAfterID, + arg.PagingForward, + arg.Limit, + arg.ContractID, + arg.ProjectIDInt, + ) + if err != nil { + return nil, err + } + defer rows.Close() + var items []Post + for rows.Next() { + var i Post + if err := rows.Scan( + &i.ID, + &i.Version, + &i.TokenIds, + &i.ContractIds, + &i.ActorID, + &i.Caption, + &i.CreatedAt, + &i.LastUpdated, + &i.Deleted, + ); err != nil { + return nil, err + } + items = append(items, i) + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} + const paginateUserFeedByUserID = `-- name: PaginateUserFeedByUserID :many SELECT id, feed_entity_type, created_at, actor_id FROM feed_entities diff --git a/db/queries/core/query.sql b/db/queries/core/query.sql index 4d16b8a2e..9658e8ab9 100644 --- a/db/queries/core/query.sql +++ b/db/queries/core/query.sql @@ -450,6 +450,27 @@ ORDER BY CASE WHEN NOT sqlc.arg('paging_forward')::bool THEN (posts.created_at, posts.id) END DESC LIMIT sqlc.arg('limit'); +-- name: PaginatePostsByContractIDAndProjectID :many +with valid_post_ids as ( + SELECT distinct on (posts.id) posts.id + FROM posts + JOIN tokens on tokens.id = ANY(posts.token_ids) + and tokens.displayable + and tokens.deleted = false + and tokens.contract = sqlc.arg('contract_id') + and ('x' || lpad(substring(tokens.token_id, 1, 16), 16, '0'))::bit(64)::bigint / 1000000 = sqlc.arg('project_id_int')::int + WHERE sqlc.arg('contract_id') = ANY(posts.contract_ids) + AND posts.deleted = false +) +SELECT posts.* from posts + join valid_post_ids on posts.id = valid_post_ids.id +WHERE (posts.created_at, posts.id) < (sqlc.arg('cur_before_time'), sqlc.arg('cur_before_id')) + AND (posts.created_at, posts.id) > (sqlc.arg('cur_after_time'), sqlc.arg('cur_after_id')) +ORDER BY + CASE WHEN sqlc.arg('paging_forward')::bool THEN (posts.created_at, posts.id) END ASC, + CASE WHEN NOT sqlc.arg('paging_forward')::bool THEN (posts.created_at, posts.id) END DESC +LIMIT sqlc.arg('limit'); + -- name: CountPostsByContractID :one select count(*) from posts diff --git a/graphql/generated/generated.go b/graphql/generated/generated.go index a171dd5c7..a5161dbca 100644 --- a/graphql/generated/generated.go +++ b/graphql/generated/generated.go @@ -291,25 +291,26 @@ type ComplexityRoot struct { } Community struct { - BadgeURL func(childComplexity int) int - Chain func(childComplexity int) int - Contract func(childComplexity int) int - ContractAddress func(childComplexity int) int - Creator func(childComplexity int) int - CreatorAddress func(childComplexity int) int - Dbid func(childComplexity int) int - Description func(childComplexity int) int - ID func(childComplexity int) int - LastUpdated func(childComplexity int) int - Name func(childComplexity int) int - Owners func(childComplexity int, before *string, after *string, first *int, last *int, onlyGalleryUsers *bool) int - ParentCommunity func(childComplexity int) int - Posts func(childComplexity int, before *string, after *string, first *int, last *int) int - PreviewImage func(childComplexity int) int - ProfileBannerURL func(childComplexity int) int - ProfileImageURL func(childComplexity int) int - SubCommunities func(childComplexity int, before *string, after *string, first *int, last *int) int - TokensInCommunity func(childComplexity int, before *string, after *string, first *int, last *int, onlyGalleryUsers *bool) int + BadgeURL func(childComplexity int) int + Chain func(childComplexity int) int + Contract func(childComplexity int) int + ContractAddress func(childComplexity int) int + Creator func(childComplexity int) int + CreatorAddress func(childComplexity int) int + Dbid func(childComplexity int) int + Description func(childComplexity int) int + ID func(childComplexity int) int + LastUpdated func(childComplexity int) int + Name func(childComplexity int) int + Owners func(childComplexity int, before *string, after *string, first *int, last *int, onlyGalleryUsers *bool) int + ParentCommunity func(childComplexity int) int + Posts func(childComplexity int, before *string, after *string, first *int, last *int) int + PreviewImage func(childComplexity int) int + ProfileBannerURL func(childComplexity int) int + ProfileImageURL func(childComplexity int) int + SubCommunities func(childComplexity int, before *string, after *string, first *int, last *int) int + TmpPostsWithProjectID func(childComplexity int, projectID int, before *string, after *string, first *int, last *int) int + TokensInCommunity func(childComplexity int, before *string, after *string, first *int, last *int, onlyGalleryUsers *bool) int } CommunityEdge struct { @@ -1631,6 +1632,7 @@ type CommunityResolver interface { TokensInCommunity(ctx context.Context, obj *model.Community, before *string, after *string, first *int, last *int, onlyGalleryUsers *bool) (*model.TokensConnection, error) Owners(ctx context.Context, obj *model.Community, before *string, after *string, first *int, last *int, onlyGalleryUsers *bool) (*model.TokenHoldersConnection, error) Posts(ctx context.Context, obj *model.Community, before *string, after *string, first *int, last *int) (*model.PostsConnection, error) + TmpPostsWithProjectID(ctx context.Context, obj *model.Community, projectID int, before *string, after *string, first *int, last *int) (*model.PostsConnection, error) } type CreateCollectionPayloadResolver interface { FeedEvent(ctx context.Context, obj *model.CreateCollectionPayload) (*model.FeedEvent, error) @@ -2778,6 +2780,18 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in return e.complexity.Community.SubCommunities(childComplexity, args["before"].(*string), args["after"].(*string), args["first"].(*int), args["last"].(*int)), true + case "Community.tmpPostsWithProjectID": + if e.complexity.Community.TmpPostsWithProjectID == nil { + break + } + + args, err := ec.field_Community_tmpPostsWithProjectID_args(context.TODO(), rawArgs) + if err != nil { + return 0, false + } + + return e.complexity.Community.TmpPostsWithProjectID(childComplexity, args["projectID"].(int), args["before"].(*string), args["after"].(*string), args["first"].(*int), args["last"].(*int)), true + case "Community.tokensInCommunity": if e.complexity.Community.TokensInCommunity == nil { break @@ -8813,6 +8827,9 @@ type Community implements Node @goEmbedHelper { posts(before: String, after: String, first: Int, last: Int): PostsConnection @goField(forceResolver: true) + + tmpPostsWithProjectID(projectID: Int!, before: String, after: String, first: Int, last: Int): PostsConnection + @goField(forceResolver: true) } type Contract implements Node { @@ -11112,6 +11129,57 @@ func (ec *executionContext) field_Community_subCommunities_args(ctx context.Cont return args, nil } +func (ec *executionContext) field_Community_tmpPostsWithProjectID_args(ctx context.Context, rawArgs map[string]interface{}) (map[string]interface{}, error) { + var err error + args := map[string]interface{}{} + var arg0 int + if tmp, ok := rawArgs["projectID"]; ok { + ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("projectID")) + arg0, err = ec.unmarshalNInt2int(ctx, tmp) + if err != nil { + return nil, err + } + } + args["projectID"] = arg0 + var arg1 *string + if tmp, ok := rawArgs["before"]; ok { + ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("before")) + arg1, err = ec.unmarshalOString2ᚖstring(ctx, tmp) + if err != nil { + return nil, err + } + } + args["before"] = arg1 + var arg2 *string + if tmp, ok := rawArgs["after"]; ok { + ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("after")) + arg2, err = ec.unmarshalOString2ᚖstring(ctx, tmp) + if err != nil { + return nil, err + } + } + args["after"] = arg2 + var arg3 *int + if tmp, ok := rawArgs["first"]; ok { + ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("first")) + arg3, err = ec.unmarshalOInt2ᚖint(ctx, tmp) + if err != nil { + return nil, err + } + } + args["first"] = arg3 + var arg4 *int + if tmp, ok := rawArgs["last"]; ok { + ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("last")) + arg4, err = ec.unmarshalOInt2ᚖint(ctx, tmp) + if err != nil { + return nil, err + } + } + args["last"] = arg4 + return args, nil +} + func (ec *executionContext) field_Community_tokensInCommunity_args(ctx context.Context, rawArgs map[string]interface{}) (map[string]interface{}, error) { var err error args := map[string]interface{}{} @@ -20281,6 +20349,64 @@ func (ec *executionContext) fieldContext_Community_posts(ctx context.Context, fi return fc, nil } +func (ec *executionContext) _Community_tmpPostsWithProjectID(ctx context.Context, field graphql.CollectedField, obj *model.Community) (ret graphql.Marshaler) { + fc, err := ec.fieldContext_Community_tmpPostsWithProjectID(ctx, field) + if err != nil { + return graphql.Null + } + ctx = graphql.WithFieldContext(ctx, fc) + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { + ctx = rctx // use context from middleware stack in children + return ec.resolvers.Community().TmpPostsWithProjectID(rctx, obj, fc.Args["projectID"].(int), fc.Args["before"].(*string), fc.Args["after"].(*string), fc.Args["first"].(*int), fc.Args["last"].(*int)) + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + if resTmp == nil { + return graphql.Null + } + res := resTmp.(*model.PostsConnection) + fc.Result = res + return ec.marshalOPostsConnection2ᚖgithubᚗcomᚋmikeydubᚋgoᚑgalleryᚋgraphqlᚋmodelᚐPostsConnection(ctx, field.Selections, res) +} + +func (ec *executionContext) fieldContext_Community_tmpPostsWithProjectID(ctx context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + fc = &graphql.FieldContext{ + Object: "Community", + Field: field, + IsMethod: true, + IsResolver: true, + Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + switch field.Name { + case "edges": + return ec.fieldContext_PostsConnection_edges(ctx, field) + case "pageInfo": + return ec.fieldContext_PostsConnection_pageInfo(ctx, field) + } + return nil, fmt.Errorf("no field named %q was found under type PostsConnection", field.Name) + }, + } + defer func() { + if r := recover(); r != nil { + err = ec.Recover(ctx, r) + ec.Error(ctx, err) + } + }() + ctx = graphql.WithFieldContext(ctx, fc) + if fc.Args, err = ec.field_Community_tmpPostsWithProjectID_args(ctx, field.ArgumentMap(ec.Variables)); err != nil { + ec.Error(ctx, err) + return + } + return fc, nil +} + func (ec *executionContext) _CommunityEdge_node(ctx context.Context, field graphql.CollectedField, obj *model.CommunityEdge) (ret graphql.Marshaler) { fc, err := ec.fieldContext_CommunityEdge_node(ctx, field) if err != nil { @@ -20355,6 +20481,8 @@ func (ec *executionContext) fieldContext_CommunityEdge_node(ctx context.Context, return ec.fieldContext_Community_owners(ctx, field) case "posts": return ec.fieldContext_Community_posts(ctx, field) + case "tmpPostsWithProjectID": + return ec.fieldContext_Community_tmpPostsWithProjectID(ctx, field) } return nil, fmt.Errorf("no field named %q was found under type Community", field.Name) }, @@ -20477,6 +20605,8 @@ func (ec *executionContext) fieldContext_CommunityLink_node(ctx context.Context, return ec.fieldContext_Community_owners(ctx, field) case "posts": return ec.fieldContext_Community_posts(ctx, field) + case "tmpPostsWithProjectID": + return ec.fieldContext_Community_tmpPostsWithProjectID(ctx, field) } return nil, fmt.Errorf("no field named %q was found under type Community", field.Name) }, @@ -20558,6 +20688,8 @@ func (ec *executionContext) fieldContext_CommunitySearchResult_community(ctx con return ec.fieldContext_Community_owners(ctx, field) case "posts": return ec.fieldContext_Community_posts(ctx, field) + case "tmpPostsWithProjectID": + return ec.fieldContext_Community_tmpPostsWithProjectID(ctx, field) } return nil, fmt.Errorf("no field named %q was found under type Community", field.Name) }, @@ -49478,6 +49610,8 @@ func (ec *executionContext) fieldContext_Token_community(ctx context.Context, fi return ec.fieldContext_Community_owners(ctx, field) case "posts": return ec.fieldContext_Community_posts(ctx, field) + case "tmpPostsWithProjectID": + return ec.fieldContext_Community_tmpPostsWithProjectID(ctx, field) } return nil, fmt.Errorf("no field named %q was found under type Community", field.Name) }, @@ -65860,6 +65994,23 @@ func (ec *executionContext) _Community(ctx context.Context, sel ast.SelectionSet return res } + out.Concurrently(i, func() graphql.Marshaler { + return innerFunc(ctx) + + }) + case "tmpPostsWithProjectID": + field := field + + innerFunc := func(ctx context.Context) (res graphql.Marshaler) { + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + } + }() + res = ec._Community_tmpPostsWithProjectID(ctx, field, obj) + return res + } + out.Concurrently(i, func() graphql.Marshaler { return innerFunc(ctx) diff --git a/graphql/model/models_gen.go b/graphql/model/models_gen.go index f46c4f03e..89722535c 100644 --- a/graphql/model/models_gen.go +++ b/graphql/model/models_gen.go @@ -703,24 +703,25 @@ type CommunitiesConnection struct { type Community struct { HelperCommunityData - Dbid persist.DBID `json:"dbid"` - LastUpdated *time.Time `json:"lastUpdated"` - Contract *Contract `json:"contract"` - ContractAddress *persist.ChainAddress `json:"contractAddress"` - CreatorAddress *persist.ChainAddress `json:"creatorAddress"` - Creator GalleryUserOrAddress `json:"creator"` - Chain *persist.Chain `json:"chain"` - Name *string `json:"name"` - Description *string `json:"description"` - PreviewImage *string `json:"previewImage"` - ProfileImageURL *string `json:"profileImageURL"` - ProfileBannerURL *string `json:"profileBannerURL"` - BadgeURL *string `json:"badgeURL"` - ParentCommunity *CommunityLink `json:"parentCommunity"` - SubCommunities *CommunitiesConnection `json:"subCommunities"` - TokensInCommunity *TokensConnection `json:"tokensInCommunity"` - Owners *TokenHoldersConnection `json:"owners"` - Posts *PostsConnection `json:"posts"` + Dbid persist.DBID `json:"dbid"` + LastUpdated *time.Time `json:"lastUpdated"` + Contract *Contract `json:"contract"` + ContractAddress *persist.ChainAddress `json:"contractAddress"` + CreatorAddress *persist.ChainAddress `json:"creatorAddress"` + Creator GalleryUserOrAddress `json:"creator"` + Chain *persist.Chain `json:"chain"` + Name *string `json:"name"` + Description *string `json:"description"` + PreviewImage *string `json:"previewImage"` + ProfileImageURL *string `json:"profileImageURL"` + ProfileBannerURL *string `json:"profileBannerURL"` + BadgeURL *string `json:"badgeURL"` + ParentCommunity *CommunityLink `json:"parentCommunity"` + SubCommunities *CommunitiesConnection `json:"subCommunities"` + TokensInCommunity *TokensConnection `json:"tokensInCommunity"` + Owners *TokenHoldersConnection `json:"owners"` + Posts *PostsConnection `json:"posts"` + TmpPostsWithProjectID *PostsConnection `json:"tmpPostsWithProjectID"` } func (Community) IsNode() {} diff --git a/graphql/resolver/schema.resolvers.go b/graphql/resolver/schema.resolvers.go index 05f9b6a18..5b508ccc1 100644 --- a/graphql/resolver/schema.resolvers.go +++ b/graphql/resolver/schema.resolvers.go @@ -292,6 +292,16 @@ func (r *communityResolver) Posts(ctx context.Context, obj *model.Community, bef return resolveCommunityPostsByContractID(ctx, obj.Dbid, before, after, first, last) } +// TmpPostsWithProjectID is the resolver for the tmpPostsWithProjectID field. +func (r *communityResolver) TmpPostsWithProjectID(ctx context.Context, obj *model.Community, projectID int, before *string, after *string, first *int, last *int) (*model.PostsConnection, error) { + posts, pageInfo, err := publicapi.For(ctx).Contract.GetCommunityPostsByContractIDAndProjectID(ctx, obj.Dbid, projectID, before, after, first, last) + if err != nil { + return nil, err + } + connection := postsToConnection(ctx, posts, obj.Dbid, pageInfo) + return &connection, nil +} + // FeedEvent is the resolver for the feedEvent field. func (r *createCollectionPayloadResolver) FeedEvent(ctx context.Context, obj *model.CreateCollectionPayload) (*model.FeedEvent, error) { if obj.FeedEvent.Dbid == "" { diff --git a/graphql/schema/schema.graphql b/graphql/schema/schema.graphql index 1f9841875..a2094224a 100644 --- a/graphql/schema/schema.graphql +++ b/graphql/schema/schema.graphql @@ -603,6 +603,14 @@ type Community implements Node @goEmbedHelper { posts(before: String, after: String, first: Int, last: Int): PostsConnection @goField(forceResolver: true) + + tmpPostsWithProjectID( + projectID: Int! + before: String + after: String + first: Int + last: Int + ): PostsConnection @goField(forceResolver: true) } type Contract implements Node { diff --git a/publicapi/contract.go b/publicapi/contract.go index 6fd0fd04f..90d20af93 100644 --- a/publicapi/contract.go +++ b/publicapi/contract.go @@ -338,6 +338,80 @@ func (api ContractAPI) GetCommunityPostsByContractID(ctx context.Context, contra return posts, pageInfo, err } +// ------ Temporary ------ +func (api ContractAPI) GetCommunityPostsByContractIDAndProjectID(ctx context.Context, contractID persist.DBID, projectID int, before, after *string, first, last *int) ([]db.Post, PageInfo, error) { + // Validate + if err := validate.ValidateFields(api.validator, validate.ValidationMap{ + "contractID": validate.WithTag(contractID, "required"), + "projectID": validate.WithTag(projectID, "required"), + }); err != nil { + return nil, PageInfo{}, err + } + + if err := validatePaginationParams(api.validator, first, last); err != nil { + return nil, PageInfo{}, err + } + + timeFunc := func(params timeIDPagingParams) ([]interface{}, error) { + + posts, err := api.queries.PaginatePostsByContractIDAndProjectID(ctx, db.PaginatePostsByContractIDAndProjectIDParams{ + ContractID: contractID, + ProjectIDInt: int32(projectID), + Limit: params.Limit, + CurBeforeTime: params.CursorBeforeTime, + CurBeforeID: params.CursorBeforeID, + CurAfterTime: params.CursorAfterTime, + CurAfterID: params.CursorAfterID, + PagingForward: params.PagingForward, + }) + + if err != nil { + return nil, err + } + + results := make([]interface{}, len(posts)) + for i, post := range posts { + results[i] = post + } + + return results, nil + } + + countFunc := func() (int, error) { + total, err := api.queries.CountPostsByContractID(ctx, contractID) + return int(total), err + } + + timeCursorFunc := func(i interface{}) (time.Time, persist.DBID, error) { + if user, ok := i.(db.Post); ok { + return user.CreatedAt, user.ID, nil + } + return time.Time{}, "", fmt.Errorf("interface{} is not a post") + } + + paginator := timeIDPaginator{ + QueryFunc: timeFunc, + CursorFunc: timeCursorFunc, + CountFunc: countFunc, + } + + results, pageInfo, err := paginator.paginate(before, after, first, last) + if err != nil { + return nil, PageInfo{}, err + } + + posts := make([]db.Post, len(results)) + for i, result := range results { + if post, ok := result.(db.Post); ok { + posts[i] = post + } + } + + return posts, pageInfo, err +} + +// End of temporary to-be-removed stuff + func (api ContractAPI) GetPreviewURLsByContractIDandUserID(ctx context.Context, userID, contractID persist.DBID) ([]string, error) { return api.queries.GetPreviewURLsByContractIdAndUserId(ctx, db.GetPreviewURLsByContractIdAndUserIdParams{ Contract: contractID,