Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions packages/discovery-provider/src/api/v1/models/users.py
Original file line number Diff line number Diff line change
Expand Up @@ -171,3 +171,12 @@
"access": StringEnumToLower(required=True),
},
)

sales_aggregate = ns.model(
"sales_aggregate",
{
"content_type": StringEnumToLower(required=True, discriminator=True),
"content_id": fields.String(required=True),
"purchase_count": fields.Integer(required=True),
},
)
33 changes: 33 additions & 0 deletions packages/discovery-provider/src/api/v1/users.py
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@
decoded_user_token,
encoded_user_id,
purchase,
sales_aggregate,
user_model,
user_model_full,
user_subscribers,
Expand Down Expand Up @@ -118,6 +119,7 @@
from src.queries.get_related_artists import get_related_artists
from src.queries.get_remixers import GetRemixersArgs, get_remixers, get_remixers_count
from src.queries.get_repost_feed_for_user import get_repost_feed_for_user
from src.queries.get_sales_aggregate import GetSalesAggregateArgs, get_sales_aggregate
from src.queries.get_saves import get_saves
from src.queries.get_subscribers import (
get_subscribers_for_user,
Expand Down Expand Up @@ -2339,6 +2341,37 @@ def get(self, id, authed_user_id):
return success_response(count)


sales_aggregate_parser = pagination_with_current_user_parser.copy()

sales_aggregate_response = make_response(
"sales_aggregate_response", ns, fields.List(fields.Nested(sales_aggregate))
)


@ns.route("/<string:id>/sales/aggregate")
class SalesAggregate(Resource):
@ns.doc(
id="Get Sales Aggregate",
description="Gets the aggregated sales data for the user",
params={"id": "A User ID"},
)
@ns.expect(sales_aggregate_parser)
@auth_middleware(sales_aggregate_parser)
def get(self, id, authed_user_id):
decoded_id = decode_with_abort(id, ns)
check_authorized(decoded_id, authed_user_id)
args = sales_aggregate_parser.parse_args()
limit = get_default_max(args.get("limit"), 10, 100)
offset = get_default_max(args.get("offset"), 0)
args = GetSalesAggregateArgs(
seller_user_id=decoded_id,
limit=limit,
offset=offset,
)
sales_aggregate = get_sales_aggregate(args)
return success_response(list(sales_aggregate))


purchases_download_parser = current_user_parser.copy()


Expand Down
41 changes: 41 additions & 0 deletions packages/discovery-provider/src/queries/get_sales_aggregate.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
from typing import Optional, TypedDict

from sqlalchemy import func

from src.models.users.usdc_purchase import USDCPurchase
from src.queries.query_helpers import add_query_pagination
from src.utils.db_session import get_db_read_replica


class GetSalesAggregateArgs(TypedDict):
seller_user_id: Optional[int]
limit: int
offset: int


def _get_sales_aggregate(session, args: GetSalesAggregateArgs):
query = (
session.query(
USDCPurchase.content_id,
USDCPurchase.content_type,
func.count(USDCPurchase.buyer_user_id).label("purchase_count"),
)
.filter(USDCPurchase.seller_user_id == args.get("seller_user_id"))
.group_by(USDCPurchase.content_id, USDCPurchase.content_type)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

super nit: maybe order by purchase_count desc?

)

return query


def get_sales_aggregate(args: GetSalesAggregateArgs):
"""Aggregates USDCPurchase count by content_id and content_type for a given seller_user_id."""
db = get_db_read_replica()
with db.scoped_session() as session:
query = _get_sales_aggregate(session, args)
limit = args.get("limit")
offset = args.get("offset")

results = add_query_pagination(query, limit, offset).all()
sales_aggregate_list = [row._asdict() for row in results]

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤯 why haven't we been using this instead of query_result_to_list


return sales_aggregate_list
59 changes: 59 additions & 0 deletions packages/libs/src/sdk/api/generated/default/apis/UsersApi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -184,6 +184,15 @@ export interface GetRepostsRequest {
userId?: string;
}

export interface GetSalesAggregateRequest {
id: string;
offset?: number;
limit?: number;
userId?: string;
encodedDataMessage?: string;
encodedDataSignature?: string;
}

export interface GetSubscribersRequest {
id: string;
offset?: number;
Expand Down Expand Up @@ -890,6 +899,56 @@ export class UsersApi extends runtime.BaseAPI {
return await response.value();
}

/**
* @hidden
* Gets the aggregated sales data for the user
*/
async getSalesAggregateRaw(params: GetSalesAggregateRequest, initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise<runtime.ApiResponse<void>> {
if (params.id === null || params.id === undefined) {
throw new runtime.RequiredError('id','Required parameter params.id was null or undefined when calling getSalesAggregate.');
}

const queryParameters: any = {};

if (params.offset !== undefined) {
queryParameters['offset'] = params.offset;
}

if (params.limit !== undefined) {
queryParameters['limit'] = params.limit;
}

if (params.userId !== undefined) {
queryParameters['user_id'] = params.userId;
}

const headerParameters: runtime.HTTPHeaders = {};

if (params.encodedDataMessage !== undefined && params.encodedDataMessage !== null) {
headerParameters['Encoded-Data-Message'] = String(params.encodedDataMessage);
}

if (params.encodedDataSignature !== undefined && params.encodedDataSignature !== null) {
headerParameters['Encoded-Data-Signature'] = String(params.encodedDataSignature);
}

const response = await this.request({
path: `/users/{id}/sales/aggregate`.replace(`{${"id"}}`, encodeURIComponent(String(params.id))),
method: 'GET',
headers: headerParameters,
query: queryParameters,
}, initOverrides);

return new runtime.VoidApiResponse(response);
}

/**
* Gets the aggregated sales data for the user
*/
async getSalesAggregate(params: GetSalesAggregateRequest, initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise<void> {
await this.getSalesAggregateRaw(params, initOverrides);
}

/**
* @hidden
* All users that subscribe to the provided user
Expand Down