diff --git a/.changeset/sixty-bugs-eat.md b/.changeset/sixty-bugs-eat.md new file mode 100644 index 00000000000..b7373a5e925 --- /dev/null +++ b/.changeset/sixty-bugs-eat.md @@ -0,0 +1,5 @@ +--- +'@audius/sdk': minor +--- + +added endpoints for fetching user management relationships diff --git a/packages/discovery-provider/integration_tests/queries/test_get_managed_users.py b/packages/discovery-provider/integration_tests/queries/test_get_managed_users.py new file mode 100644 index 00000000000..ebaa4941bae --- /dev/null +++ b/packages/discovery-provider/integration_tests/queries/test_get_managed_users.py @@ -0,0 +1,251 @@ +import copy + +from integration_tests.utils import populate_mock_db +from src.queries.get_managed_users import get_managed_users_with_grants, get_user_managers_with_grants +from src.utils.db_session import get_db + + +test_users = [ + {"user_id": 10, "name": "a", "wallet": "0x10"}, + {"user_id": 20, "name": "b", "wallet": "0x20"}, + {"user_id": 30, "name": "c", "wallet": "0x30"}, + {"user_id": 40, "name": "d", "wallet": "0x40"}, + {"user_id": 50, "name": "e", "wallet": "0x50"}, + {"user_id": 60, "name": "f", "wallet": "0x60"}, +] + +test_managed_user_grants = [ + # Active grants + { + "user_id": 20, + "grantee_address": "0x10", + "is_approved": True, + "is_revoked": False, + }, + { + "user_id": 30, + "grantee_address": "0x10", + "is_approved": True, + "is_revoked": False, + }, + # Not yet approved + { + "user_id": 40, + "grantee_address": "0x10", + "is_approved": False, + "is_revoked": False, + }, + # Approved then Revoked + { + "user_id": 50, + "grantee_address": "0x10", + "is_approved": True, + "is_revoked": True, + }, + # Revoked before approval + { + "user_id": 60, + "grantee_address": "0x10", + "is_approved": False, + "is_revoked": True, + }, +] + +test_user_manager_grants = [ + # Active grants + { + "user_id": 10, + "grantee_address": "0x20", + "is_approved": True, + "is_revoked": False, + }, + { + "user_id": 10, + "grantee_address": "0x30", + "is_approved": True, + "is_revoked": False, + }, + # Not yet approved + { + "user_id": 10, + "grantee_address": "0x40", + "is_approved": False, + "is_revoked": False, + }, + # Approved then Revoked + { + "user_id": 10, + "grantee_address": "0x50", + "is_approved": True, + "is_revoked": True, + }, + # Revoked before approval + { + "user_id": 10, + "grantee_address": "0x60", + "is_approved": False, + "is_revoked": True, + } +] + +# ### get_managed_users ### # + + +def test_get_managed_users_default(app): + with app.app_context(): + db = get_db() + populate_mock_db(db, {"users": test_users, "grants": test_managed_user_grants}) + + managed_users = get_managed_users_with_grants( + {"manager_wallet_address": "0x10", "current_user_id": 10} + ) + + # return all non-revoked records by default + assert len(managed_users) == 3, "Expected exactly 3 records" + assert ( + record["grant"]["is_revoked"] == False for record in managed_users + ), "Revoked records returned" + + +def test_get_managed_users_no_filters(app): + with app.app_context(): + db = get_db() + populate_mock_db(db, {"users": test_users, "grants": test_managed_user_grants}) + + managed_users = get_managed_users_with_grants( + { + "manager_wallet_address": "0x10", + "current_user_id": 10, + "is_approved": None, + "is_revoked": None, + } + ) + + # return all records which map to users + assert len(managed_users) == 5, "Expected exactly 5 records" + + +def test_get_managed_users_grants_without_users(app): + with app.app_context(): + db = get_db() + + entities = ({"users": copy.deepcopy(test_users), "grants": copy.deepcopy(test_managed_user_grants)}) + # Record for a user which won't be found + entities["grants"].append( + { + "user_id": 70, + "grantee_address": "0x10", + "is_approved": False, + "is_revoked": False, + } + ) + populate_mock_db(db, entities) + + managed_users = get_managed_users_with_grants( + {"manager_wallet_address": "0x10", "current_user_id": 10} + ) + + # return all non-revoked records by default + assert len(managed_users) == 3, "Expected exactly 3 records" + assert ( + record["grant"]["user_id"] != 70 for record in managed_users + ), "Revoked records returned" + + +def test_get_managed_users_invalid_parameters(app): + with app.app_context(): + db = get_db() + populate_mock_db(db, {"users": test_users, "grants": test_managed_user_grants}) + + try: + get_managed_users_with_grants( + {"manager_wallet_address": None, "current_user_id": 10} + ) + assert False, "Should have thrown an error for missing wallet address" + except ValueError as e: + assert str(e) == "manager_wallet_address is required" + + try: + get_managed_users_with_grants( + {"manager_wallet_address": "0x10", "current_user_id": None} + ) + assert False, "Should have thrown an error for missing current user id" + except ValueError as e: + assert str(e) == "current_user_id is required" + + +# ### get_user_managers ### # + + +def test_get_user_managers_default(app): + with app.app_context(): + db = get_db() + populate_mock_db(db, {"users": test_users, "grants": test_user_manager_grants}) + + user_managers = get_user_managers_with_grants( + {"user_id": 10} + ) + + # return all non-revoked records by default + assert len(user_managers) == 3, "Expected exactly 3 records" + assert ( + record["grant"]["is_revoked"] == False for record in user_managers + ), "Revoked records returned" + + +def test_get_user_managers_no_filters(app): + with app.app_context(): + db = get_db() + populate_mock_db(db, {"users": test_users, "grants": test_user_manager_grants}) + + user_managers = get_user_managers_with_grants( + { + "user_id": 10, + "is_approved": None, + "is_revoked": None, + } + ) + + # return all records which map to users + assert len(user_managers) == 5, "Expected exactly 5 records" + + +def test_get_user_managers_grants_without_users(app): + with app.app_context(): + db = get_db() + + entities = {"users": copy.deepcopy(test_users), "grants": copy.deepcopy(test_user_manager_grants)} + # Record for a user which won't be found + entities["grants"].append( + { + "user_id": 10, + "grantee_address": "0x70", + "is_approved": False, + "is_revoked": False, + } + ) + populate_mock_db(db, entities) + + user_managers = get_user_managers_with_grants( + {"user_id": 10} + ) + + # return all non-revoked records by default + assert len(user_managers) == 3, "Expected exactly 3 records" + assert ( + record["grant"]["grantee_address"] != "0x70" for record in user_managers + ), "Revoked records returned" + + +def test_get_user_managers_invalid_parameters(app): + with app.app_context(): + db = get_db() + populate_mock_db(db, {"users": test_users, "grants": test_user_manager_grants}) + + try: + get_user_managers_with_grants( + {} + ) + assert False, "Should have thrown an error for missing user id" + except ValueError as e: + assert str(e) == "user_id is required" diff --git a/packages/discovery-provider/src/api/v1/helpers.py b/packages/discovery-provider/src/api/v1/helpers.py index 97bd22b8c14..e6121ed21cd 100644 --- a/packages/discovery-provider/src/api/v1/helpers.py +++ b/packages/discovery-provider/src/api/v1/helpers.py @@ -987,6 +987,31 @@ def format_authorized_app(authorized_app): } +def format_grant(grant): + return { + "grantee_address": grant["grantee_address"], + "user_id": encode_int_id(grant["user_id"]), + "is_approved": grant["is_approved"], + "is_revoked": grant["is_revoked"], + "created_at": grant["created_at"], + "updated_at": grant["updated_at"], + } + + +def format_managed_user(managed_user): + return { + "user": extend_user(managed_user["user"]), + "grant": format_grant(managed_user["grant"]), + } + + +def format_user_manager(user_manager): + return { + "manager": extend_user(user_manager["manager"]), + "grant": format_grant(user_manager["grant"]), + } + + def format_dashboard_wallet_user(dashboard_wallet_user): return { "wallet": dashboard_wallet_user["wallet"], diff --git a/packages/discovery-provider/src/api/v1/models/grants.py b/packages/discovery-provider/src/api/v1/models/grants.py new file mode 100644 index 00000000000..5d112789155 --- /dev/null +++ b/packages/discovery-provider/src/api/v1/models/grants.py @@ -0,0 +1,33 @@ +from flask_restx import fields + +from .common import ns +from .users import user_model_full + +# Describes a grant made from user to another user +grant = ns.model( + "grant", + { + "grantee_address": fields.String(required=True), + "user_id": fields.String(required=True), + "is_revoked": fields.Boolean(required=True), + "is_approved": fields.Boolean(required=True), + "created_at": fields.String(required=True), + "updated_at": fields.String(required=True), + }, +) + +managed_user = ns.model( + "managed_user", + { + "user": fields.Nested(user_model_full, required=True), + "grant": fields.Nested(grant, required=True), + }, +) + +user_manager = ns.model( + "user_manager", + { + "manager": fields.Nested(user_model_full, required=True), + "grant": fields.Nested(grant, required=True), + }, +) diff --git a/packages/discovery-provider/src/api/v1/users.py b/packages/discovery-provider/src/api/v1/users.py index 8b5915e2c39..84205c57019 100644 --- a/packages/discovery-provider/src/api/v1/users.py +++ b/packages/discovery-provider/src/api/v1/users.py @@ -11,6 +11,7 @@ abort_bad_request_param, abort_forbidden, abort_not_found, + abort_unauthorized, current_user_parser, decode_with_abort, extend_activity, @@ -26,10 +27,12 @@ format_developer_app, format_library_filter, format_limit, + format_managed_user, format_offset, format_query, format_sort_direction, format_sort_method, + format_user_manager, get_current_user_id, get_default_max, make_full_response, @@ -55,6 +58,7 @@ ) from src.api.v1.models.common import favorite from src.api.v1.models.developer_apps import authorized_app, developer_app +from src.api.v1.models.grants import managed_user, user_manager from src.api.v1.models.support import ( supporter_response, supporter_response_full, @@ -99,6 +103,12 @@ ) from src.queries.get_followees_for_user import get_followees_for_user from src.queries.get_followers_for_user import get_followers_for_user +from src.queries.get_managed_users import ( + GetManagedUsersArgs, + GetUserManagersArgs, + get_managed_users_with_grants, + get_user_managers_with_grants, +) from src.queries.get_related_artists import get_related_artists from src.queries.get_repost_feed_for_user import get_repost_feed_for_user from src.queries.get_saves import get_saves @@ -2105,6 +2115,89 @@ def get(self, id): return success_response(authorized_apps) +managed_users_response = make_response( + "managed_users_response", full_ns, fields.List(fields.Nested(managed_user)) +) + + +@full_ns.route("//managed_users") +class ManagedUsers(Resource): + @record_metrics + @full_ns.doc( + id="""Get Managed Users""", + description="""Gets a list of users managed by the given user""", + params={"id": "A user id for the manager"}, + responses={ + 200: "Success", + 400: "Bad request", + 401: "Unauthorized", + 403: "Forbidden", + 500: "Server error", + }, + ) + @auth_middleware(include_wallet=True) + @full_ns.marshal_with(managed_users_response) + def get(self, id, authed_user_id, authed_user_wallet): + user_id = decode_with_abort(id, full_ns) + + if authed_user_id is None: + abort_unauthorized(full_ns) + + if authed_user_id != user_id: + abort_forbidden(full_ns) + + args = GetManagedUsersArgs( + manager_wallet_address=authed_user_wallet, current_user_id=user_id + ) + users = get_managed_users_with_grants(args) + users = list(map(format_managed_user, users)) + + return success_response(users) + + +managers_response = make_response( + "managers_response", full_ns, fields.List(fields.Nested(user_manager)) +) + + +@full_ns.route("//managers") +class Managers(Resource): + @record_metrics + @full_ns.doc( + id="""Get Managers""", + description="""Gets a list of users managing the given user""", + params={"id": "An id for the managed user"}, + responses={ + 200: "Success", + 400: "Bad request", + 401: "Unauthorized", + 403: "Forbidden", + 500: "Server error", + }, + ) + @auth_middleware(include_wallet=True) + @full_ns.marshal_with(managers_response) + def get(self, id, authed_user_id, authed_user_wallet): + user_id = decode_with_abort(id, full_ns) + + if authed_user_id is None: + abort_unauthorized(full_ns) + + # TODO: If accessing this endpoint as a manager, this check will not + # work correctly. + # https://linear.app/audius/issue/PAY-2780/support-getting-target-user-in-auth-middleware + if authed_user_id != user_id: + abort_forbidden(full_ns) + + args = GetUserManagersArgs( + manager_wallet_address=authed_user_wallet, user_id=user_id + ) + managers = get_user_managers_with_grants(args) + managers = list(map(format_user_manager, managers)) + + return success_response(managers) + + purchases_and_sales_parser = pagination_with_current_user_parser.copy() purchases_and_sales_parser.add_argument( "sort_method", diff --git a/packages/discovery-provider/src/queries/get_managed_users.py b/packages/discovery-provider/src/queries/get_managed_users.py new file mode 100644 index 00000000000..b7d170e63ac --- /dev/null +++ b/packages/discovery-provider/src/queries/get_managed_users.py @@ -0,0 +1,155 @@ +import logging +from typing import Dict, List, Optional, TypedDict + +from src.models.grants.grant import Grant +from src.queries.get_unpopulated_users import ( + get_unpopulated_users, + get_unpopulated_users_by_wallet, +) +from src.queries.query_helpers import populate_user_metadata +from src.utils import db_session +from src.utils.helpers import query_result_to_list + +logger = logging.getLogger(__name__) + + +class GetManagedUsersArgs(TypedDict): + manager_wallet_address: str + current_user_id: int + is_approved: Optional[bool] + is_revoked: Optional[bool] + + +class GetUserManagersArgs(TypedDict): + user_id: int + is_approved: Optional[bool] + is_revoked: Optional[bool] + + +def make_user_managers_list(managers: List[Dict], grants: List[Dict]) -> List[Dict]: + user_managers = [] + grants_map = {grant.get("grantee_address"): grant for grant in grants} + + for user in managers: + grant = grants_map.get(user.get("wallet")) + if grant is None: + continue + + user_managers.append( + { + "manager": user, + "grant": grant, + } + ) + + return user_managers + + +def get_user_managers_with_grants(args: GetUserManagersArgs) -> List[Dict]: + """ + Returns users which manage the given user + + Args: + user_id: id of the managed user + is_approved: Optional[bool] If set, filters by approval status + is_revoked: Optional[bool] If set, filters by revocation status, defaults to False + + Returns: + List of Users with grant information + """ + is_approved = args.get("is_approved", None) + is_revoked = args.get("is_revoked", False) + user_id = args.get("user_id") + + if user_id is None: + raise ValueError("user_id is required") + + db = db_session.get_db_read_replica() + with db.scoped_session() as session: + query = session.query(Grant).filter( + Grant.user_id == user_id, Grant.is_current == True + ) + + if is_approved is not None: + query = query.filter(Grant.is_approved == is_approved) + if is_revoked is not None: + query = query.filter(Grant.is_revoked == is_revoked) + + grants = query.all() + if len(grants) == 0: + return [] + + wallet_addresses = [grant.grantee_address for grant in grants] + users = get_unpopulated_users_by_wallet(session, wallet_addresses) + user_ids = [user.get("user_id") for user in users] + managers = populate_user_metadata( + session, user_ids, users, current_user_id=user_id + ) + + grants = query_result_to_list(grants) + + return make_user_managers_list(managers, grants) + + +def make_managed_users_list(users: List[Dict], grants: List[Dict]) -> List[Dict]: + managed_users = [] + grants_map = {grant.get("user_id"): grant for grant in grants} + + for user in users: + grant = grants_map.get(user.get("user_id")) + if grant is None: + continue + + managed_users.append( + { + "user": user, + "grant": grant, + } + ) + + return managed_users + + +def get_managed_users_with_grants(args: GetManagedUsersArgs) -> List[Dict]: + """ + Returns users managed by the given wallet address + + Args: + manager_wallet_address: str wallet address of the manager + is_approved: Optional[bool] If set, filters by approval status + is_revoked: Optional[bool] If set, filters by revocation status, defaults to False + + Returns: + List of Users with grant information + """ + is_approved = args.get("is_approved", None) + is_revoked = args.get("is_revoked", False) + current_user_id = args.get("current_user_id") + grantee_address = args.get("manager_wallet_address") + if grantee_address is None: + raise ValueError("manager_wallet_address is required") + if current_user_id is None: + raise ValueError("current_user_id is required") + + db = db_session.get_db_read_replica() + with db.scoped_session() as session: + query = session.query(Grant).filter( + Grant.grantee_address == grantee_address, Grant.is_current == True + ) + + if is_approved is not None: + query = query.filter(Grant.is_approved == is_approved) + if is_revoked is not None: + query = query.filter(Grant.is_revoked == is_revoked) + + grants = query.all() + if len(grants) == 0: + return [] + + user_ids = [grant.user_id for grant in grants] + users = get_unpopulated_users(session, user_ids) + users = populate_user_metadata(session, user_ids, users, current_user_id) + + grants = query_result_to_list(grants) + + return make_managed_users_list(users, grants) diff --git a/packages/discovery-provider/src/queries/get_unpopulated_users.py b/packages/discovery-provider/src/queries/get_unpopulated_users.py index d09615bc348..d80d85b67ae 100644 --- a/packages/discovery-provider/src/queries/get_unpopulated_users.py +++ b/packages/discovery-provider/src/queries/get_unpopulated_users.py @@ -14,8 +14,7 @@ def get_unpopulated_users(session, user_ids): """ - Fetches users by checking the redis cache first then - going to DB and writes to cache if not present + Fetches a list of users based on an input array of ids Args: session: DB session @@ -40,3 +39,33 @@ def get_unpopulated_users(session, user_ids): users_response.append(queried_users[user_id]) return users_response + + +def get_unpopulated_users_by_wallet(session, wallet_addresses): + """ + Fetch users based on an input array of wallet addresses + + Args: + session: DB session + wallet_addresses: array A list of wallet addresses + + Returns: + Array of users + """ + + wallets_lower = [wallet.lower() for wallet in wallet_addresses] + users = ( + session.query(User) + .filter(User.is_current == True, User.handle != None) + .filter(User.wallet.in_(wallets_lower)) + .all() + ) + users = helpers.query_result_to_list(users) + queried_users = {user["wallet"]: user for user in users} + + users_response = [] + for wallet in wallets_lower: + if wallet in queried_users: + users_response.append(queried_users[wallet]) + + return users_response diff --git a/packages/discovery-provider/src/utils/auth_middleware.py b/packages/discovery-provider/src/utils/auth_middleware.py index 0231ba99f38..ca6c5c1a661 100644 --- a/packages/discovery-provider/src/utils/auth_middleware.py +++ b/packages/discovery-provider/src/utils/auth_middleware.py @@ -14,7 +14,9 @@ SIGNATURE_HEADER = "Encoded-Data-Signature" -def auth_middleware(parser: reqparse.RequestParser = None): +def auth_middleware( + parser: reqparse.RequestParser = None, include_wallet: bool = False +): """ Auth middleware decorator. @@ -60,6 +62,7 @@ def decorator(func): def wrapper(*args, **kwargs): message = request.headers.get(MESSAGE_HEADER) signature = request.headers.get(SIGNATURE_HEADER) + wallet_lower = None authed_user_id = None if message and signature: @@ -68,13 +71,14 @@ def wrapper(*args, **kwargs): wallet = web3.eth.account.recover_message( encoded_to_recover, signature=signature ) + wallet_lower = wallet.lower() db = db_session.get_db_read_replica() with db.scoped_session() as session: user = ( session.query(User.user_id) .filter( # Convert checksum wallet to lowercase - User.wallet == wallet.lower(), + User.wallet == wallet_lower, User.is_current == True, ) # In the case that multiple wallets match (not enforced on the data layer), @@ -87,7 +91,10 @@ def wrapper(*args, **kwargs): logger.info( f"auth_middleware.py | authed_user_id: {authed_user_id}" ) - return func(*args, **kwargs, authed_user_id=authed_user_id) + kwargs["authed_user_id"] = authed_user_id + if include_wallet: + kwargs["authed_user_wallet"] = wallet_lower + return func(*args, **kwargs) return wrapper diff --git a/packages/libs/src/sdk/api/generated/default/models/Grant.ts b/packages/libs/src/sdk/api/generated/default/models/Grant.ts new file mode 100644 index 00000000000..2c2fee58696 --- /dev/null +++ b/packages/libs/src/sdk/api/generated/default/models/Grant.ts @@ -0,0 +1,112 @@ +/* tslint:disable */ +/* eslint-disable */ +// @ts-nocheck +/** + * API + * Audius V1 API + * + * The version of the OpenAPI document: 1.0 + * + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * https://openapi-generator.tech + * Do not edit the class manually. + */ + +import { exists, mapValues } from '../runtime'; +/** + * + * @export + * @interface Grant + */ +export interface Grant { + /** + * + * @type {string} + * @memberof Grant + */ + granteeAddress: string; + /** + * + * @type {string} + * @memberof Grant + */ + userId: string; + /** + * + * @type {boolean} + * @memberof Grant + */ + isRevoked: boolean; + /** + * + * @type {boolean} + * @memberof Grant + */ + isApproved: boolean; + /** + * + * @type {string} + * @memberof Grant + */ + createdAt: string; + /** + * + * @type {string} + * @memberof Grant + */ + updatedAt: string; +} + +/** + * Check if a given object implements the Grant interface. + */ +export function instanceOfGrant(value: object): boolean { + let isInstance = true; + isInstance = isInstance && "granteeAddress" in value; + isInstance = isInstance && "userId" in value; + isInstance = isInstance && "isRevoked" in value; + isInstance = isInstance && "isApproved" in value; + isInstance = isInstance && "createdAt" in value; + isInstance = isInstance && "updatedAt" in value; + + return isInstance; +} + +export function GrantFromJSON(json: any): Grant { + return GrantFromJSONTyped(json, false); +} + +export function GrantFromJSONTyped(json: any, ignoreDiscriminator: boolean): Grant { + if ((json === undefined) || (json === null)) { + return json; + } + return { + + 'granteeAddress': json['grantee_address'], + 'userId': json['user_id'], + 'isRevoked': json['is_revoked'], + 'isApproved': json['is_approved'], + 'createdAt': json['created_at'], + 'updatedAt': json['updated_at'], + }; +} + +export function GrantToJSON(value?: Grant | null): any { + if (value === undefined) { + return undefined; + } + if (value === null) { + return null; + } + return { + + 'grantee_address': value.granteeAddress, + 'user_id': value.userId, + 'is_revoked': value.isRevoked, + 'is_approved': value.isApproved, + 'created_at': value.createdAt, + 'updated_at': value.updatedAt, + }; +} + diff --git a/packages/libs/src/sdk/api/generated/default/models/ManagedUser.ts b/packages/libs/src/sdk/api/generated/default/models/ManagedUser.ts new file mode 100644 index 00000000000..e8cb0110e5d --- /dev/null +++ b/packages/libs/src/sdk/api/generated/default/models/ManagedUser.ts @@ -0,0 +1,89 @@ +/* tslint:disable */ +/* eslint-disable */ +// @ts-nocheck +/** + * API + * Audius V1 API + * + * The version of the OpenAPI document: 1.0 + * + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * https://openapi-generator.tech + * Do not edit the class manually. + */ + +import { exists, mapValues } from '../runtime'; +import type { Grant } from './Grant'; +import { + GrantFromJSON, + GrantFromJSONTyped, + GrantToJSON, +} from './Grant'; +import type { UserFull } from './UserFull'; +import { + UserFullFromJSON, + UserFullFromJSONTyped, + UserFullToJSON, +} from './UserFull'; + +/** + * + * @export + * @interface ManagedUser + */ +export interface ManagedUser { + /** + * + * @type {UserFull} + * @memberof ManagedUser + */ + user: UserFull; + /** + * + * @type {Grant} + * @memberof ManagedUser + */ + grant: Grant; +} + +/** + * Check if a given object implements the ManagedUser interface. + */ +export function instanceOfManagedUser(value: object): boolean { + let isInstance = true; + isInstance = isInstance && "user" in value; + isInstance = isInstance && "grant" in value; + + return isInstance; +} + +export function ManagedUserFromJSON(json: any): ManagedUser { + return ManagedUserFromJSONTyped(json, false); +} + +export function ManagedUserFromJSONTyped(json: any, ignoreDiscriminator: boolean): ManagedUser { + if ((json === undefined) || (json === null)) { + return json; + } + return { + + 'user': UserFullFromJSON(json['user']), + 'grant': GrantFromJSON(json['grant']), + }; +} + +export function ManagedUserToJSON(value?: ManagedUser | null): any { + if (value === undefined) { + return undefined; + } + if (value === null) { + return null; + } + return { + + 'user': UserFullToJSON(value.user), + 'grant': GrantToJSON(value.grant), + }; +} + diff --git a/packages/libs/src/sdk/api/generated/default/models/ManagedUsers.ts b/packages/libs/src/sdk/api/generated/default/models/ManagedUsers.ts new file mode 100644 index 00000000000..97d457c9afb --- /dev/null +++ b/packages/libs/src/sdk/api/generated/default/models/ManagedUsers.ts @@ -0,0 +1,73 @@ +/* tslint:disable */ +/* eslint-disable */ +// @ts-nocheck +/** + * API + * Audius V1 API + * + * The version of the OpenAPI document: 1.0 + * + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * https://openapi-generator.tech + * Do not edit the class manually. + */ + +import { exists, mapValues } from '../runtime'; +import type { ManagedUser } from './ManagedUser'; +import { + ManagedUserFromJSON, + ManagedUserFromJSONTyped, + ManagedUserToJSON, +} from './ManagedUser'; + +/** + * + * @export + * @interface ManagedUsers + */ +export interface ManagedUsers { + /** + * + * @type {Array} + * @memberof ManagedUsers + */ + data?: Array; +} + +/** + * Check if a given object implements the ManagedUsers interface. + */ +export function instanceOfManagedUsers(value: object): boolean { + let isInstance = true; + + return isInstance; +} + +export function ManagedUsersFromJSON(json: any): ManagedUsers { + return ManagedUsersFromJSONTyped(json, false); +} + +export function ManagedUsersFromJSONTyped(json: any, ignoreDiscriminator: boolean): ManagedUsers { + if ((json === undefined) || (json === null)) { + return json; + } + return { + + 'data': !exists(json, 'data') ? undefined : ((json['data'] as Array).map(ManagedUserFromJSON)), + }; +} + +export function ManagedUsersToJSON(value?: ManagedUsers | null): any { + if (value === undefined) { + return undefined; + } + if (value === null) { + return null; + } + return { + + 'data': value.data === undefined ? undefined : ((value.data as Array).map(ManagedUserToJSON)), + }; +} + diff --git a/packages/libs/src/sdk/api/generated/full/.openapi-generator/FILES b/packages/libs/src/sdk/api/generated/full/.openapi-generator/FILES index bc5760cf9c8..7de06a125a5 100644 --- a/packages/libs/src/sdk/api/generated/full/.openapi-generator/FILES +++ b/packages/libs/src/sdk/api/generated/full/.openapi-generator/FILES @@ -42,7 +42,11 @@ models/FullTracksResponse.ts models/FullTrendingPlaylistsResponse.ts models/FullUserResponse.ts models/GetTipsResponse.ts +models/Grant.ts models/HistoryResponseFull.ts +models/ManagedUser.ts +models/ManagedUsersResponse.ts +models/ManagersResponse.ts models/PlaylistAddedTimestamp.ts models/PlaylistArtwork.ts models/PlaylistFull.ts @@ -80,6 +84,7 @@ models/TransactionHistoryResponse.ts models/TrendingIdsResponse.ts models/TrendingTimesIds.ts models/UserFull.ts +models/UserManager.ts models/UserReplicaSet.ts models/UserSubscribers.ts models/UsersByContentNode.ts diff --git a/packages/libs/src/sdk/api/generated/full/apis/UsersApi.ts b/packages/libs/src/sdk/api/generated/full/apis/UsersApi.ts index f0fa404f406..48aa57fa6db 100644 --- a/packages/libs/src/sdk/api/generated/full/apis/UsersApi.ts +++ b/packages/libs/src/sdk/api/generated/full/apis/UsersApi.ts @@ -28,6 +28,8 @@ import type { FullTracks, FullUserResponse, HistoryResponseFull, + ManagedUsersResponse, + ManagersResponse, PurchasesCountResponse, PurchasesResponse, RelatedArtistResponseFull, @@ -63,6 +65,10 @@ import { FullUserResponseToJSON, HistoryResponseFullFromJSON, HistoryResponseFullToJSON, + ManagedUsersResponseFromJSON, + ManagedUsersResponseToJSON, + ManagersResponseFromJSON, + ManagersResponseToJSON, PurchasesCountResponseFromJSON, PurchasesCountResponseToJSON, PurchasesResponseFromJSON, @@ -145,6 +151,14 @@ export interface GetFollowingRequest { userId?: string; } +export interface GetManagedUsersRequest { + id: string; +} + +export interface GetManagersRequest { + id: string; +} + export interface GetPurchasesRequest { id: string; offset?: number; @@ -744,6 +758,68 @@ export class UsersApi extends runtime.BaseAPI { return await response.value(); } + /** + * @hidden + * Gets a list of users managed by the given user + */ + async getManagedUsersRaw(params: GetManagedUsersRequest, initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise> { + if (params.id === null || params.id === undefined) { + throw new runtime.RequiredError('id','Required parameter params.id was null or undefined when calling getManagedUsers.'); + } + + const queryParameters: any = {}; + + const headerParameters: runtime.HTTPHeaders = {}; + + const response = await this.request({ + path: `/users/{id}/managed_users`.replace(`{${"id"}}`, encodeURIComponent(String(params.id))), + method: 'GET', + headers: headerParameters, + query: queryParameters, + }, initOverrides); + + return new runtime.JSONApiResponse(response, (jsonValue) => ManagedUsersResponseFromJSON(jsonValue)); + } + + /** + * Gets a list of users managed by the given user + */ + async getManagedUsers(params: GetManagedUsersRequest, initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise { + const response = await this.getManagedUsersRaw(params, initOverrides); + return await response.value(); + } + + /** + * @hidden + * Gets a list of users managing the given user + */ + async getManagersRaw(params: GetManagersRequest, initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise> { + if (params.id === null || params.id === undefined) { + throw new runtime.RequiredError('id','Required parameter params.id was null or undefined when calling getManagers.'); + } + + const queryParameters: any = {}; + + const headerParameters: runtime.HTTPHeaders = {}; + + const response = await this.request({ + path: `/users/{id}/managers`.replace(`{${"id"}}`, encodeURIComponent(String(params.id))), + method: 'GET', + headers: headerParameters, + query: queryParameters, + }, initOverrides); + + return new runtime.JSONApiResponse(response, (jsonValue) => ManagersResponseFromJSON(jsonValue)); + } + + /** + * Gets a list of users managing the given user + */ + async getManagers(params: GetManagersRequest, initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise { + const response = await this.getManagersRaw(params, initOverrides); + return await response.value(); + } + /** * @hidden * Gets the purchases the user has made diff --git a/packages/libs/src/sdk/api/generated/full/models/Grant.ts b/packages/libs/src/sdk/api/generated/full/models/Grant.ts new file mode 100644 index 00000000000..5b6f1202e5c --- /dev/null +++ b/packages/libs/src/sdk/api/generated/full/models/Grant.ts @@ -0,0 +1,112 @@ +/* tslint:disable */ +/* eslint-disable */ +// @ts-nocheck +/** + * API + * No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator) + * + * The version of the OpenAPI document: 1.0 + * + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * https://openapi-generator.tech + * Do not edit the class manually. + */ + +import { exists, mapValues } from '../runtime'; +/** + * + * @export + * @interface Grant + */ +export interface Grant { + /** + * + * @type {string} + * @memberof Grant + */ + granteeAddress: string; + /** + * + * @type {string} + * @memberof Grant + */ + userId: string; + /** + * + * @type {boolean} + * @memberof Grant + */ + isRevoked: boolean; + /** + * + * @type {boolean} + * @memberof Grant + */ + isApproved: boolean; + /** + * + * @type {string} + * @memberof Grant + */ + createdAt: string; + /** + * + * @type {string} + * @memberof Grant + */ + updatedAt: string; +} + +/** + * Check if a given object implements the Grant interface. + */ +export function instanceOfGrant(value: object): boolean { + let isInstance = true; + isInstance = isInstance && "granteeAddress" in value; + isInstance = isInstance && "userId" in value; + isInstance = isInstance && "isRevoked" in value; + isInstance = isInstance && "isApproved" in value; + isInstance = isInstance && "createdAt" in value; + isInstance = isInstance && "updatedAt" in value; + + return isInstance; +} + +export function GrantFromJSON(json: any): Grant { + return GrantFromJSONTyped(json, false); +} + +export function GrantFromJSONTyped(json: any, ignoreDiscriminator: boolean): Grant { + if ((json === undefined) || (json === null)) { + return json; + } + return { + + 'granteeAddress': json['grantee_address'], + 'userId': json['user_id'], + 'isRevoked': json['is_revoked'], + 'isApproved': json['is_approved'], + 'createdAt': json['created_at'], + 'updatedAt': json['updated_at'], + }; +} + +export function GrantToJSON(value?: Grant | null): any { + if (value === undefined) { + return undefined; + } + if (value === null) { + return null; + } + return { + + 'grantee_address': value.granteeAddress, + 'user_id': value.userId, + 'is_revoked': value.isRevoked, + 'is_approved': value.isApproved, + 'created_at': value.createdAt, + 'updated_at': value.updatedAt, + }; +} + diff --git a/packages/libs/src/sdk/api/generated/full/models/ManagedUser.ts b/packages/libs/src/sdk/api/generated/full/models/ManagedUser.ts new file mode 100644 index 00000000000..b0eca3d878f --- /dev/null +++ b/packages/libs/src/sdk/api/generated/full/models/ManagedUser.ts @@ -0,0 +1,89 @@ +/* tslint:disable */ +/* eslint-disable */ +// @ts-nocheck +/** + * API + * No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator) + * + * The version of the OpenAPI document: 1.0 + * + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * https://openapi-generator.tech + * Do not edit the class manually. + */ + +import { exists, mapValues } from '../runtime'; +import type { Grant } from './Grant'; +import { + GrantFromJSON, + GrantFromJSONTyped, + GrantToJSON, +} from './Grant'; +import type { UserFull } from './UserFull'; +import { + UserFullFromJSON, + UserFullFromJSONTyped, + UserFullToJSON, +} from './UserFull'; + +/** + * + * @export + * @interface ManagedUser + */ +export interface ManagedUser { + /** + * + * @type {UserFull} + * @memberof ManagedUser + */ + user: UserFull; + /** + * + * @type {Grant} + * @memberof ManagedUser + */ + grant: Grant; +} + +/** + * Check if a given object implements the ManagedUser interface. + */ +export function instanceOfManagedUser(value: object): boolean { + let isInstance = true; + isInstance = isInstance && "user" in value; + isInstance = isInstance && "grant" in value; + + return isInstance; +} + +export function ManagedUserFromJSON(json: any): ManagedUser { + return ManagedUserFromJSONTyped(json, false); +} + +export function ManagedUserFromJSONTyped(json: any, ignoreDiscriminator: boolean): ManagedUser { + if ((json === undefined) || (json === null)) { + return json; + } + return { + + 'user': UserFullFromJSON(json['user']), + 'grant': GrantFromJSON(json['grant']), + }; +} + +export function ManagedUserToJSON(value?: ManagedUser | null): any { + if (value === undefined) { + return undefined; + } + if (value === null) { + return null; + } + return { + + 'user': UserFullToJSON(value.user), + 'grant': GrantToJSON(value.grant), + }; +} + diff --git a/packages/libs/src/sdk/api/generated/full/models/ManagedUsersResponse.ts b/packages/libs/src/sdk/api/generated/full/models/ManagedUsersResponse.ts new file mode 100644 index 00000000000..f6881d7871d --- /dev/null +++ b/packages/libs/src/sdk/api/generated/full/models/ManagedUsersResponse.ts @@ -0,0 +1,73 @@ +/* tslint:disable */ +/* eslint-disable */ +// @ts-nocheck +/** + * API + * No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator) + * + * The version of the OpenAPI document: 1.0 + * + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * https://openapi-generator.tech + * Do not edit the class manually. + */ + +import { exists, mapValues } from '../runtime'; +import type { ManagedUser } from './ManagedUser'; +import { + ManagedUserFromJSON, + ManagedUserFromJSONTyped, + ManagedUserToJSON, +} from './ManagedUser'; + +/** + * + * @export + * @interface ManagedUsersResponse + */ +export interface ManagedUsersResponse { + /** + * + * @type {Array} + * @memberof ManagedUsersResponse + */ + data?: Array; +} + +/** + * Check if a given object implements the ManagedUsersResponse interface. + */ +export function instanceOfManagedUsersResponse(value: object): boolean { + let isInstance = true; + + return isInstance; +} + +export function ManagedUsersResponseFromJSON(json: any): ManagedUsersResponse { + return ManagedUsersResponseFromJSONTyped(json, false); +} + +export function ManagedUsersResponseFromJSONTyped(json: any, ignoreDiscriminator: boolean): ManagedUsersResponse { + if ((json === undefined) || (json === null)) { + return json; + } + return { + + 'data': !exists(json, 'data') ? undefined : ((json['data'] as Array).map(ManagedUserFromJSON)), + }; +} + +export function ManagedUsersResponseToJSON(value?: ManagedUsersResponse | null): any { + if (value === undefined) { + return undefined; + } + if (value === null) { + return null; + } + return { + + 'data': value.data === undefined ? undefined : ((value.data as Array).map(ManagedUserToJSON)), + }; +} + diff --git a/packages/libs/src/sdk/api/generated/full/models/ManagersResponse.ts b/packages/libs/src/sdk/api/generated/full/models/ManagersResponse.ts new file mode 100644 index 00000000000..f9f7fd470ed --- /dev/null +++ b/packages/libs/src/sdk/api/generated/full/models/ManagersResponse.ts @@ -0,0 +1,73 @@ +/* tslint:disable */ +/* eslint-disable */ +// @ts-nocheck +/** + * API + * No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator) + * + * The version of the OpenAPI document: 1.0 + * + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * https://openapi-generator.tech + * Do not edit the class manually. + */ + +import { exists, mapValues } from '../runtime'; +import type { UserManager } from './UserManager'; +import { + UserManagerFromJSON, + UserManagerFromJSONTyped, + UserManagerToJSON, +} from './UserManager'; + +/** + * + * @export + * @interface ManagersResponse + */ +export interface ManagersResponse { + /** + * + * @type {Array} + * @memberof ManagersResponse + */ + data?: Array; +} + +/** + * Check if a given object implements the ManagersResponse interface. + */ +export function instanceOfManagersResponse(value: object): boolean { + let isInstance = true; + + return isInstance; +} + +export function ManagersResponseFromJSON(json: any): ManagersResponse { + return ManagersResponseFromJSONTyped(json, false); +} + +export function ManagersResponseFromJSONTyped(json: any, ignoreDiscriminator: boolean): ManagersResponse { + if ((json === undefined) || (json === null)) { + return json; + } + return { + + 'data': !exists(json, 'data') ? undefined : ((json['data'] as Array).map(UserManagerFromJSON)), + }; +} + +export function ManagersResponseToJSON(value?: ManagersResponse | null): any { + if (value === undefined) { + return undefined; + } + if (value === null) { + return null; + } + return { + + 'data': value.data === undefined ? undefined : ((value.data as Array).map(UserManagerToJSON)), + }; +} + diff --git a/packages/libs/src/sdk/api/generated/full/models/UserManager.ts b/packages/libs/src/sdk/api/generated/full/models/UserManager.ts new file mode 100644 index 00000000000..73b775dcd09 --- /dev/null +++ b/packages/libs/src/sdk/api/generated/full/models/UserManager.ts @@ -0,0 +1,89 @@ +/* tslint:disable */ +/* eslint-disable */ +// @ts-nocheck +/** + * API + * No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator) + * + * The version of the OpenAPI document: 1.0 + * + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * https://openapi-generator.tech + * Do not edit the class manually. + */ + +import { exists, mapValues } from '../runtime'; +import type { Grant } from './Grant'; +import { + GrantFromJSON, + GrantFromJSONTyped, + GrantToJSON, +} from './Grant'; +import type { UserFull } from './UserFull'; +import { + UserFullFromJSON, + UserFullFromJSONTyped, + UserFullToJSON, +} from './UserFull'; + +/** + * + * @export + * @interface UserManager + */ +export interface UserManager { + /** + * + * @type {UserFull} + * @memberof UserManager + */ + manager: UserFull; + /** + * + * @type {Grant} + * @memberof UserManager + */ + grant: Grant; +} + +/** + * Check if a given object implements the UserManager interface. + */ +export function instanceOfUserManager(value: object): boolean { + let isInstance = true; + isInstance = isInstance && "manager" in value; + isInstance = isInstance && "grant" in value; + + return isInstance; +} + +export function UserManagerFromJSON(json: any): UserManager { + return UserManagerFromJSONTyped(json, false); +} + +export function UserManagerFromJSONTyped(json: any, ignoreDiscriminator: boolean): UserManager { + if ((json === undefined) || (json === null)) { + return json; + } + return { + + 'manager': UserFullFromJSON(json['manager']), + 'grant': GrantFromJSON(json['grant']), + }; +} + +export function UserManagerToJSON(value?: UserManager | null): any { + if (value === undefined) { + return undefined; + } + if (value === null) { + return null; + } + return { + + 'manager': UserFullToJSON(value.manager), + 'grant': GrantToJSON(value.grant), + }; +} + diff --git a/packages/libs/src/sdk/api/generated/full/models/index.ts b/packages/libs/src/sdk/api/generated/full/models/index.ts index d748115604c..f89a84ae65b 100644 --- a/packages/libs/src/sdk/api/generated/full/models/index.ts +++ b/packages/libs/src/sdk/api/generated/full/models/index.ts @@ -34,7 +34,11 @@ export * from './FullTracksResponse'; export * from './FullTrendingPlaylistsResponse'; export * from './FullUserResponse'; export * from './GetTipsResponse'; +export * from './Grant'; export * from './HistoryResponseFull'; +export * from './ManagedUser'; +export * from './ManagedUsersResponse'; +export * from './ManagersResponse'; export * from './PlaylistAddedTimestamp'; export * from './PlaylistArtwork'; export * from './PlaylistFull'; @@ -72,6 +76,7 @@ export * from './TransactionHistoryResponse'; export * from './TrendingIdsResponse'; export * from './TrendingTimesIds'; export * from './UserFull'; +export * from './UserManager'; export * from './UserReplicaSet'; export * from './UserSubscribers'; export * from './UsersByContentNode';