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
5 changes: 5 additions & 0 deletions .changeset/smooth-crews-rush.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@audius/sdk': patch
---

add filtering support for manager endpoints
18 changes: 17 additions & 1 deletion packages/commands/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -120,4 +120,20 @@ audius-cmd create-user-bank --mint usdc freddie_mercury
audius-cmd mint-tokens --from freddie_mercury --mint usdc 10000000

audius-cmd purchase-content <track-id> --type track --from freddie_mercury
```
```

**Managed accounts**

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.

I didn't even realize there was a readme for this! Props for filling this out


```
# Add a manager
audius-cmd add-manager --from freddie_mercury jim_beach

# Accept request from manager's account
audius-cmd approve-manager-request --from jim_beach freddie_mercury

# (OR) Reject request from manager's account
audius-cmd reject-manager-request --from jim_beach freddie_mercury

# Remove manager
audius-cmd remove-manager --from freddie_mercury jim_beach
```
Original file line number Diff line number Diff line change
@@ -1,13 +1,7 @@
import fs from 'fs'

import chalk from 'chalk'
import { program } from 'commander'

import {
initializeAudiusLibs,
initializeAudiusSdk,
parseUserId
} from './utils.mjs'
import { initializeAudiusLibs, initializeAudiusSdk } from './utils.mjs'

program
.command('add-manager')
Expand Down Expand Up @@ -51,7 +45,7 @@ program
})

program
.command('approve-manager')
.command('approve-manager-request')
.description('Approve a pending manager request')
.argument('[handle]', 'The handle of the user to be managed')
.option('-f, --from <from>', 'The manager account handle')
Expand Down Expand Up @@ -86,3 +80,77 @@ program

process.exit(0)
})

program
.command('reject-manager-request')
.description('Reject a pending manager request')
.argument('[handle]', 'The handle of the user to be managed')
.option('-f, --from <from>', 'The manager account handle')
.action(async (handle, { from }) => {
const audiusLibs = await initializeAudiusLibs(from)
// extract privkey and pubkey from hedgehog
// only works with accounts created via audius-cmd
const wallet = audiusLibs?.hedgehog?.getWallet()
const privKey = wallet?.getPrivateKeyString()
const pubKey = wallet?.getAddressString()

// init sdk with priv and pub keys as api keys and secret
// this enables writes via sdk
const audiusSdk = await initializeAudiusSdk({
apiKey: pubKey,
apiSecret: privKey
})

try {
const {
data: { id: userId }
} = await audiusSdk.users.getUserByHandle({ handle })
const {
data: { id: managerUserId }
} = await audiusSdk.users.getUserByHandle({ handle: from })

await audiusSdk.grants.removeManager({ userId, managerUserId })
console.log(chalk.green(`Manager request rejected.`))
} catch (err) {
program.error(err.message)
}

process.exit(0)
})

program
.command('remove-manager')
.description('Remove a manager')
.argument('[handle]', 'The handle of the manager')
.option('-f, --from <from>', 'The handle of the manager user')
.action(async (handle, { from }) => {
const audiusLibs = await initializeAudiusLibs(from)
// extract privkey and pubkey from hedgehog
// only works with accounts created via audius-cmd
const wallet = audiusLibs?.hedgehog?.getWallet()
const privKey = wallet?.getPrivateKeyString()
const pubKey = wallet?.getAddressString()

// init sdk with priv and pub keys as api keys and secret
// this enables writes via sdk
const audiusSdk = await initializeAudiusSdk({
apiKey: pubKey,
apiSecret: privKey
})

try {
const {
data: { id: userId }
} = await audiusSdk.users.getUserByHandle({ handle: from })
const {
data: { id: managerUserId }
} = await audiusSdk.users.getUserByHandle({ handle })

await audiusSdk.grants.removeManager({ userId, managerUserId })
console.log(chalk.green(`Manager removed.`))
} catch (err) {
program.error(err.message)
}

process.exit(0)
})
2 changes: 1 addition & 1 deletion packages/commands/src/index.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ import './create-user-bank.mjs'
import './purchase-content.mjs'
import './route-tokens-to-user-bank.mjs'
import './withdraw-tokens.mjs'
import './add-manager.mjs'
import './account-managers.mjs'
import './claim-reward.mjs'

async function main() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,42 +11,56 @@
from src.utils.db_session import get_db

new_grants_data = [
# Grant from user 1 -> dev app F4C4
{
"user_id": 1,
"grantee_address": "0x3a388671bb4D6E1Ea08D79Ee191b40FB45A8F4C4",
"is_user_grant": False,
},
# Grant from user 1 -> dev app 2e0d
{
"user_id": 1,
"grantee_address": "0x04c9fc3784120f50932436f84c59aebebb12e0d",
"is_user_grant": False,
},
# Grant from user 1 -> user 3
{
"user_id": 1,
"grantee_address": "user3Wallet",
"is_user_grant": True,
},
# Grant from user 2 -> user 3
{
"user_id": 2,
"grantee_address": "user3Wallet",
"is_user_grant": True,
},
# Grant from user 3 -> user 4
{"user_id": 3, "grantee_address": "user4Wallet", "is_user_grant": True},
# Grant from user 1 -> user 4
{
"user_id": 1,
"grantee_address": "user4Wallet",
"is_user_grant": True,
},
# Grant from user 5 -> user 1
{
"user_id": 5,
"grantee_address": "user1Wallet",
"is_user_grant": True,
},
# Grant from user 4 -> user 5
{
"user_id": 4,
"grantee_address": "user5wallet",
"is_user_grant": True,
},
# Grant from user 3 -> user 5
{
"user_id": 3,
"grantee_address": "user5wallet",
"is_user_grant": True,
},
]

NUM_VALID_GRANTS = len(new_grants_data) + 1
Expand Down Expand Up @@ -174,6 +188,20 @@ def test_index_grant(app, mocker):
)
},
],
"CreateGrantTx9": [
{
"args": AttributeDict(
{
"_entityId": 0,
"_entityType": EntityType.GRANT,
"_userId": new_grants_data[8]["user_id"],
"_metadata": f"""{{"grantee_address": "{new_grants_data[8]["grantee_address"]}"}}""",
"_action": Action.CREATE,
"_signer": f"user{new_grants_data[8]['user_id']}wallet",
}
)
},
],
}

entity_manager_txs = [
Expand Down Expand Up @@ -522,6 +550,7 @@ def get_events_side_effect(_, tx_receipt):
assert len(found_matches) == 2
for match in found_matches:
assert match.is_approved == False
assert match.is_revoked == True

# Invalid reject grant
tx_receipts = {
Expand Down Expand Up @@ -722,8 +751,8 @@ def get_events_side_effect(_, tx_receipt):
{
"_entityId": 0,
"_entityType": EntityType.GRANT,
"_userId": new_grants_data[7]["user_id"],
"_metadata": f"""{{"grantee_address": "{new_grants_data[7]["grantee_address"]}"}}""",
"_userId": new_grants_data[8]["user_id"],
"_metadata": f"""{{"grantee_address": "{new_grants_data[8]["grantee_address"]}"}}""",
"_action": Action.DELETE,
"_signer": "user1wallet",
}
Expand Down Expand Up @@ -751,7 +780,7 @@ def get_events_side_effect(_, tx_receipt):
# validate db records
all_grants: List[Grant] = session.query(Grant).all()
assert len(all_grants) == NUM_VALID_GRANTS
deleted_grants_indices = [0, 1, 2, 5, 7]
deleted_grants_indices = [0, 1, 2, 5, 8]
for index in deleted_grants_indices:
expected_grant = new_grants_data[index]
found_matches = [
Expand Down
44 changes: 38 additions & 6 deletions packages/discovery-provider/src/api/v1/users.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

from eth_account.messages import encode_defunct
from flask import Response, request
from flask_restx import Namespace, Resource, fields, reqparse
from flask_restx import Namespace, Resource, fields, inputs, reqparse

from src.api.v1.helpers import (
DescriptiveArgument,
Expand Down Expand Up @@ -2081,6 +2081,22 @@ def get(self, id):
"managed_users_response", full_ns, fields.List(fields.Nested(managed_user))
)

managed_users_route_parser = reqparse.RequestParser(argument_class=DescriptiveArgument)
managed_users_route_parser.add_argument(
"is_approved",
required=False,
type=inputs.boolean,
default=None,
description="If true, only show users where the management request has been accepted. If false, only show those where the request was rejected. If omitted, shows all users regardless of approval status.",
)
managed_users_route_parser.add_argument(
"is_revoked",
required=False,
type=inputs.boolean,
default=False,
description="If true, only show users where the management request has been revoked. If false, only show those with a pending or accepted request. Defaults to false.",
)


@full_ns.route("/<string:id>/managed_users")
class ManagedUsers(Resource):
Expand All @@ -2097,12 +2113,20 @@ class ManagedUsers(Resource):
500: "Server error",
},
)
@auth_middleware(require_auth=True)
@full_ns.expect(managed_users_route_parser)
@auth_middleware(managed_users_route_parser, require_auth=True)
@full_ns.marshal_with(managed_users_response)
def get(self, id, authed_user_id):
user_id = decode_with_abort(id, full_ns)
check_authorized(user_id, authed_user_id)
users = get_managed_users_with_grants(GetManagedUsersArgs(user_id=user_id))
args = managed_users_route_parser.parse_args()
is_approved = args.get("is_approved", None)
is_revoked = args.get("is_revoked", False)
users = get_managed_users_with_grants(
GetManagedUsersArgs(
user_id=user_id, is_approved=is_approved, is_revoked=is_revoked
)
)
users = list(map(format_managed_user, users))

return success_response(users)
Expand All @@ -2128,14 +2152,22 @@ class Managers(Resource):
500: "Server error",
},
)
@auth_middleware(require_auth=True)
@full_ns.expect(managed_users_route_parser)
@auth_middleware(managed_users_route_parser, require_auth=True)
@full_ns.marshal_with(managers_response)
def get(self, id, authed_user_id):
user_id = decode_with_abort(id, full_ns)
check_authorized(user_id, authed_user_id)

args = GetUserManagersArgs(user_id=user_id)
managers = get_user_managers_with_grants(args)
args = managed_users_route_parser.parse_args()
logger.info(f"DEBUG::args: {args}")
is_approved = args.get("is_approved", None)
is_revoked = args.get("is_revoked", False)
managers = get_user_managers_with_grants(
GetUserManagersArgs(
user_id=user_id, is_approved=is_approved, is_revoked=is_revoked
)
)
managers = list(map(format_user_manager, managers))

return success_response(managers)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -211,6 +211,7 @@ def create_grant(params: ManageEntityParameters):
), # cast to assert non null (since we validated above)
is_current=True,
is_approved=None if grantee_type == "user" else True,
is_revoked=False,
txhash=params.txhash,
blockhash=params.event_blockhash,
blocknumber=params.block_number,
Expand Down Expand Up @@ -295,6 +296,7 @@ def reject_grant(params: ManageEntityParameters):
)

rejected_grant.is_approved = False
rejected_grant.is_revoked = True

validate_grant_record(rejected_grant)
params.add_record(grant_key, rejected_grant)
Expand Down
Loading