From 63eeca916fa5fbcff6052d810ddbb90f3e1079b8 Mon Sep 17 00:00:00 2001 From: Hammad Jutt Date: Wed, 9 Jun 2021 13:01:04 -0600 Subject: [PATCH] Allow users to force-update their linked ETH address Removed sc_identity_id from Player table because it enforces ETH addresses to be used as the single source of truth for users and prevents conflicts when users update their ETH address in the ledger. sc_identity_id is not needing anywhere in our backend / data model. --- README.md | 6 +++ hasura/metadata/tables.yaml | 3 +- .../down.sql | 3 ++ .../up.sql | 1 + .../migrateSourceCredAccounts/handler.ts | 41 ++++------------- .../backend/src/handlers/graphql/mutations.ts | 46 +------------------ packages/discord-bot/.env.sample | 4 ++ packages/discord-bot/package.json | 2 +- .../src/discord/commands/setEthAddress.ts | 22 +++++++-- packages/utils/package.json | 3 +- packages/utils/src/index.ts | 1 + packages/utils/src/sourceCredHelpers.ts | 15 ++++++ schema.graphql | 18 -------- 13 files changed, 61 insertions(+), 104 deletions(-) create mode 100644 hasura/migrations/1623259967735_alter_table_public_player_drop_column_sc_identity_id/down.sql create mode 100644 hasura/migrations/1623259967735_alter_table_public_player_drop_column_sc_identity_id/up.sql create mode 100644 packages/discord-bot/.env.sample create mode 100644 packages/utils/src/sourceCredHelpers.ts diff --git a/README.md b/README.md index 53d83a2e..6e86f467 100644 --- a/README.md +++ b/README.md @@ -83,6 +83,12 @@ yarn backend:dev yarn web:dev ``` +### Run Discord Bot + +```shell script +yarn discord-bot dev +``` + ### Tooling Start Hasura console diff --git a/hasura/metadata/tables.yaml b/hasura/metadata/tables.yaml index 7ea429e1..fee68063 100644 --- a/hasura/metadata/tables.yaml +++ b/hasura/metadata/tables.yaml @@ -310,9 +310,8 @@ - player_type_id - rank - role - - sc_identity_id - - total_xp - timezone + - total_xp - username filter: {} allow_aggregations: true diff --git a/hasura/migrations/1623259967735_alter_table_public_player_drop_column_sc_identity_id/down.sql b/hasura/migrations/1623259967735_alter_table_public_player_drop_column_sc_identity_id/down.sql new file mode 100644 index 00000000..3acf88bd --- /dev/null +++ b/hasura/migrations/1623259967735_alter_table_public_player_drop_column_sc_identity_id/down.sql @@ -0,0 +1,3 @@ +ALTER TABLE "public"."player" ADD COLUMN "sc_identity_id" text; +ALTER TABLE "public"."player" ALTER COLUMN "sc_identity_id" DROP NOT NULL; +ALTER TABLE "public"."player" ADD CONSTRAINT Player_scIdentityId_key UNIQUE (sc_identity_id); diff --git a/hasura/migrations/1623259967735_alter_table_public_player_drop_column_sc_identity_id/up.sql b/hasura/migrations/1623259967735_alter_table_public_player_drop_column_sc_identity_id/up.sql new file mode 100644 index 00000000..d7260e4c --- /dev/null +++ b/hasura/migrations/1623259967735_alter_table_public_player_drop_column_sc_identity_id/up.sql @@ -0,0 +1 @@ +ALTER TABLE "public"."player" DROP COLUMN "sc_identity_id" CASCADE; diff --git a/packages/backend/src/handlers/actions/migrateSourceCredAccounts/handler.ts b/packages/backend/src/handlers/actions/migrateSourceCredAccounts/handler.ts index 629419e2..fc848be3 100644 --- a/packages/backend/src/handlers/actions/migrateSourceCredAccounts/handler.ts +++ b/packages/backend/src/handlers/actions/migrateSourceCredAccounts/handler.ts @@ -1,4 +1,8 @@ -import { Constants, isNotNullOrUndefined } from '@metafam/utils'; +import { + Constants, + getLatestEthAddress, + isNotNullOrUndefined, +} from '@metafam/utils'; import bluebird from 'bluebird'; import { Request, Response } from 'express'; import fetch from 'node-fetch'; @@ -7,9 +11,6 @@ import { SCAccountsData, SCAlias, sourcecred as sc } from 'sourcecred'; import { AccountType_Enum, Player_Account_Constraint, - Player_Constraint, - Player_Insert_Input, - Player_Update_Column, } from '../../../lib/autogen/hasura-sdk'; import { client } from '../../../lib/hasuraClient'; import { computeRank } from '../../../lib/rankHelpers'; @@ -73,10 +74,7 @@ export const migrateSourceCredAccounts = async ( const discordId = linkedAccounts.find(({ type }) => type === 'DISCORD') ?.identifier; - const ethAddress = a.account.identity.aliases.find((alias) => { - const parts = sc.core.graph.NodeAddress.toParts(alias.address); - return parts.indexOf('ethereum') > 0; - })?.description; + const ethAddress = getLatestEthAddress(a.account.identity); if (!ethAddress) return null; @@ -148,32 +146,11 @@ export const migrateSourceCredAccounts = async ( }, { concurrency: 10 }, ); - const usersToInsert: Player_Insert_Input[] = result - .filter(isNotNullOrUndefined) - .map((player) => ({ - username: player.ethereum_address, - ethereum_address: player.ethereum_address, - sc_identity_id: player.scIdentityId, - rank: player.rank, - total_xp: player.totalXp, - })); + const usersSkipped = result.filter(isNotNullOrUndefined); - const resultInsert = await client.UpsertPlayer({ - objects: usersToInsert, - onConflict: { - constraint: Player_Constraint.PlayerEthereumAddressUniqueKey, - update_columns: [ - Player_Update_Column.ScIdentityId, - Player_Update_Column.Username, - Player_Update_Column.TotalXp, - Player_Update_Column.Rank, - ], - }, - }); res.json({ - resultInsert, - numUpdated: accountList.length - usersToInsert.length, - numInserted: usersToInsert.length, + numSkipped: usersSkipped.length, + numUpdated: accountList.length - usersSkipped.length, }); } catch (e) { console.warn('Error migrating players/accounts', e.message); diff --git a/packages/backend/src/handlers/graphql/mutations.ts b/packages/backend/src/handlers/graphql/mutations.ts index a0a3ebbf..d8d90ae5 100644 --- a/packages/backend/src/handlers/graphql/mutations.ts +++ b/packages/backend/src/handlers/graphql/mutations.ts @@ -29,60 +29,17 @@ export const UpsertAccount = gql` } `; -export const UpsertPlayer = gql` - mutation UpsertPlayer( - $objects: [player_insert_input!]! - $onConflict: player_on_conflict - ) { - insert_player(on_conflict: $onConflict, objects: $objects) { - affected_rows - } - } -`; -export const DeleteDuplicatePlayers = gql` - mutation DeleteDuplicatePlayers($scIds: [String!] = "") { - delete_player_account( - where: { Player: { sc_identity_id: { _in: $scIds } } } - ) { - affected_rows - } - delete_player(where: { sc_identity_id: { _in: $scIds } }) { - affected_rows - } - } -`; - export const UpdatePlayer = gql` mutation UpdatePlayer( $ethAddress: String - $identityId: String - $username: String $rank: PlayerRank_enum $totalXp: numeric $discordId: String ) { update_player( - where: { - _or: [ - { - ethereum_address: { _eq: $ethAddress } - sc_identity_id: { _eq: $identityId } - } - { - ethereum_address: { _eq: $ethAddress } - sc_identity_id: { _is_null: true } - } - { - ethereum_address: { _eq: $ethAddress } - username: { _eq: $username } - } - { sc_identity_id: { _eq: $identityId } } - { discord_id: { _eq: $discordId } } - ] - } + where: { ethereum_address: { _eq: $ethAddress } } _set: { ethereum_address: $ethAddress - sc_identity_id: $identityId rank: $rank total_xp: $totalXp discord_id: $discordId @@ -92,7 +49,6 @@ export const UpdatePlayer = gql` returning { id ethereum_address - sc_identity_id username } } diff --git a/packages/discord-bot/.env.sample b/packages/discord-bot/.env.sample new file mode 100644 index 00000000..3dd0be69 --- /dev/null +++ b/packages/discord-bot/.env.sample @@ -0,0 +1,4 @@ +DISCORD_BOT_TOKEN= +DISCORD_BOT_CLIENT_ID= +DISCORD_BOT_CLIENT_SECRET= +GITHUB_API_TOKEN= diff --git a/packages/discord-bot/package.json b/packages/discord-bot/package.json index 6be72de5..86c424ba 100644 --- a/packages/discord-bot/package.json +++ b/packages/discord-bot/package.json @@ -21,7 +21,7 @@ "dependencies": { "@types/express": "4.17.11", "@types/node-fetch": "2.5.10", - "@metafam/utils": "^1.0.0", + "@metafam/utils": "1.0.0", "@typeit/discord": "4.0.10", "discord.js": "12.5.3", "dotenv": "9.0.2", diff --git a/packages/discord-bot/src/discord/commands/setEthAddress.ts b/packages/discord-bot/src/discord/commands/setEthAddress.ts index 86b98d0b..b604d586 100644 --- a/packages/discord-bot/src/discord/commands/setEthAddress.ts +++ b/packages/discord-bot/src/discord/commands/setEthAddress.ts @@ -5,9 +5,14 @@ import { loadSourceCredLedger, manager } from '../../sourcecred'; const addressUtils = sc.plugins.ethereum.utils.address; +type SetEthAddressArgs = { + ethAddress: string; + force: string; +}; + export abstract class SetEthAddress { - @Command('!setAddress :ethAddress') - async setAddress(message: CommandMessage) { + @Command('!setAddress :ethAddress :force') + async setAddress(message: CommandMessage) { const res = await loadSourceCredLedger(); if (res.error) { @@ -47,14 +52,21 @@ export abstract class SetEthAddress { const account = manager.ledger.account(baseIdentityId); - const existing = account.identity.aliases.find((alias) => { + const existingEthAliases = account.identity.aliases.filter((alias) => { const parts = sc.core.graph.NodeAddress.toParts(alias.address); return parts.indexOf('ethereum') > 0; }); - if (existing) { + const latestEthAlias = existingEthAliases[existingEthAliases.length - 1]; + + const shouldForceUpdate = message.args.force === 'force'; + + if (latestEthAlias && !shouldForceUpdate) { await message.reply( - `You already have linked the following ETH Address: \`${existing.description}\`.`, + `You already have linked the following ETH Address: \`${latestEthAlias.description}\`. Are you sure you want to update it? Warning: This cannot be undone and you will have to recreate your MyMeta profile! + +To force update your address, type \`!setAddress ${ethAddress} force\`. + `, ); return; } diff --git a/packages/utils/package.json b/packages/utils/package.json index ed0b9610..547aeb79 100644 --- a/packages/utils/package.json +++ b/packages/utils/package.json @@ -17,6 +17,7 @@ "bignumber.js": "9.0.1", "ethers": "5.3.0", "js-base64": "3.6.1", - "uuid": "8.3.2" + "uuid": "8.3.2", + "sourcecred": "0.9.0" } } diff --git a/packages/utils/src/index.ts b/packages/utils/src/index.ts index 0c06124e..a6754264 100644 --- a/packages/utils/src/index.ts +++ b/packages/utils/src/index.ts @@ -5,3 +5,4 @@ export * as DiscordUtil from './discordHelpers'; export * as numbers from './numbers'; export * from './promiseHelpers'; export * from './rankHelpers'; +export * from './sourceCredHelpers'; diff --git a/packages/utils/src/sourceCredHelpers.ts b/packages/utils/src/sourceCredHelpers.ts new file mode 100644 index 00000000..3649f1de --- /dev/null +++ b/packages/utils/src/sourceCredHelpers.ts @@ -0,0 +1,15 @@ +import { ethers } from 'ethers'; +import { SCIdentity, sourcecred as sc } from 'sourcecred'; + +export const getLatestEthAddress = (identity: SCIdentity): string | null => { + const ethAddress = identity.aliases.find((alias) => { + const parts = sc.core.graph.NodeAddress.toParts(alias.address); + return parts.indexOf('ethereum') > 0; + })?.description; + + if (ethAddress && ethers.utils.isAddress(ethAddress)) { + return ethAddress.toLowerCase(); + } + + return null; +}; diff --git a/schema.graphql b/schema.graphql index a20e8046..b65a5f0a 100644 --- a/schema.graphql +++ b/schema.graphql @@ -3567,7 +3567,6 @@ type player { ): quest_aggregate! rank: PlayerRank_enum role: String - sc_identity_id: String timezone: String """Remote relationship field""" @@ -3847,7 +3846,6 @@ input player_bool_exp { quests: quest_bool_exp rank: PlayerRank_enum_comparison_exp role: String_comparison_exp - sc_identity_id: String_comparison_exp timezone: String_comparison_exp total_xp: numeric_comparison_exp updated_at: timestamptz_comparison_exp @@ -3864,9 +3862,6 @@ enum player_constraint { """unique or primary key constraint""" Player_pkey - """unique or primary key constraint""" - Player_scIdentityId_key - """unique or primary key constraint""" Player_username_unique_key @@ -3904,7 +3899,6 @@ input player_insert_input { quests: quest_arr_rel_insert_input rank: PlayerRank_enum role: String - sc_identity_id: String timezone: String total_xp: numeric updated_at: timestamptz @@ -3921,7 +3915,6 @@ type player_max_fields { id: uuid player_type_id: Int role: String - sc_identity_id: String timezone: String total_xp: numeric updated_at: timestamptz @@ -3940,7 +3933,6 @@ input player_max_order_by { id: order_by player_type_id: order_by role: order_by - sc_identity_id: order_by timezone: order_by total_xp: order_by updated_at: order_by @@ -3957,7 +3949,6 @@ type player_min_fields { id: uuid player_type_id: Int role: String - sc_identity_id: String timezone: String total_xp: numeric updated_at: timestamptz @@ -3976,7 +3967,6 @@ input player_min_order_by { id: order_by player_type_id: order_by role: order_by - sc_identity_id: order_by timezone: order_by total_xp: order_by updated_at: order_by @@ -4031,7 +4021,6 @@ input player_order_by { quests_aggregate: quest_aggregate_order_by rank: order_by role: order_by - sc_identity_id: order_by timezone: order_by total_xp: order_by updated_at: order_by @@ -4076,9 +4065,6 @@ enum player_select_column { """column name""" role - """column name""" - sc_identity_id - """column name""" timezone @@ -4105,7 +4091,6 @@ input player_set_input { player_type_id: Int rank: PlayerRank_enum role: String - sc_identity_id: String timezone: String total_xp: numeric updated_at: timestamptz @@ -4713,9 +4698,6 @@ enum player_update_column { """column name""" role - """column name""" - sc_identity_id - """column name""" timezone