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