Browse Source

Update to Hydra 3.0.0 + PostDeleted handling

Leszek Wiesner 3 years ago
parent
commit
75b76e03c3

+ 2 - 2
docker-compose.yml

@@ -124,7 +124,7 @@ services:
     command: ["yarn", "workspace", "query-node-root", "processor:start"]
 
   indexer:
-    image: joystream/hydra-indexer:3.0.0-beta.6
+    image: joystream/hydra-indexer:3.0.0
     restart: unless-stopped
     env_file:
       # relative to working directory where docker-compose was run from
@@ -146,7 +146,7 @@ services:
       sh -c "yarn db:bootstrap && yarn start:prod"
 
   indexer-api-gateway:
-    image: joystream/hydra-indexer-gateway:3.0.0-beta.6
+    image: joystream/hydra-indexer-gateway:3.0.0
     restart: unless-stopped
     env_file:
       # relative to working directory where docker-compose was run from

+ 2 - 2
query-node/codegen/package.json

@@ -5,7 +5,7 @@
   "author": "",
   "license": "ISC",
   "dependencies": {
-    "@dzlzv/hydra-cli": "3.0.0-beta.6",
-    "@dzlzv/hydra-typegen": "3.0.0-beta.6"
+    "@dzlzv/hydra-cli": "3.0.0",
+    "@dzlzv/hydra-typegen": "3.0.0"
   }
 }

+ 8 - 8
query-node/codegen/yarn.lock

@@ -76,10 +76,10 @@
   dependencies:
     regenerator-runtime "^0.13.4"
 
-"@dzlzv/hydra-cli@3.0.0-beta.6":
-  version "3.0.0-beta.6"
-  resolved "https://registry.yarnpkg.com/@dzlzv/hydra-cli/-/hydra-cli-3.0.0-beta.6.tgz#b85504ed9f4ec939b7108094fd7969d60a9b1f45"
-  integrity sha512-3ENzap6vq2DKOV8gA5Qq9ZX9xDJvP6WXpRvp+Aedf2yTms/gWB9vbhkp2oCZGnjqZqGZ9d2jIBBFJOwiRWpqsA==
+"@dzlzv/hydra-cli@3.0.0":
+  version "3.0.0"
+  resolved "https://registry.yarnpkg.com/@dzlzv/hydra-cli/-/hydra-cli-3.0.0.tgz#0672e03f1175596a4adaac6ed422b13fa03ab357"
+  integrity sha512-4z0+IvqGmw0U6HiSxNJ8E0Iz2z89X2O/ud79ILYBv30KoSwoCeCShncIovlJ45JxgR2zasRb1OhtFRGKdhiXZA==
   dependencies:
     "@inquirer/input" "^0.0.13-alpha.0"
     "@inquirer/password" "^0.0.12-alpha.0"
@@ -111,10 +111,10 @@
     tslib "1.11.2"
     warthog "https://github.com/metmirr/warthog/releases/download/v2.30.0/warthog-v2.30.0.tgz"
 
-"@dzlzv/hydra-typegen@3.0.0-beta.6":
-  version "3.0.0-beta.6"
-  resolved "https://registry.yarnpkg.com/@dzlzv/hydra-typegen/-/hydra-typegen-3.0.0-beta.6.tgz#665364fdde787a293df58196f81abd517847e976"
-  integrity sha512-mYDh3G8tHjPIy6lUhXS2lNNOs27SxW580ODd3Ib87sR/scHv3Xmdv2Tgcu3lDj367MeKfhQ0hRfFDdLZnxaHeA==
+"@dzlzv/hydra-typegen@3.0.0":
+  version "3.0.0"
+  resolved "https://registry.yarnpkg.com/@dzlzv/hydra-typegen/-/hydra-typegen-3.0.0.tgz#cf01d5eb983fe8c73b66c3bedd1e76bba3dd248a"
+  integrity sha512-VLO7AuDaj2bowR5mBk03laTY+WIgMg8+YxGxWQcrRKrI0zZ3OCJ+K91mGk189Yr3Yv87PZM2d+e6SrwSAV8zhQ==
   dependencies:
     "@oclif/command" "^1.8.0"
     "@oclif/config" "^1"

+ 1 - 2
query-node/manifest.yml

@@ -63,8 +63,7 @@ typegen:
     - forum.VoteOnPoll
     - forum.PostAdded
     - forum.PostModerated
-    # FIXME: https://github.com/Joystream/hydra/issues/398
-    # - forum.PostDeleted
+    - forum.PostDeleted
     - forum.PostTextUpdated
     - forum.PostReacted
     - forum.CategoryStickyThreadUpdate

+ 24 - 1
query-node/mappings/forum.ts

@@ -44,6 +44,8 @@ import {
   PostReactionResultValid,
   PostReactionResultInvalid,
   PostTextUpdatedEvent,
+  PostDeletedEvent,
+  PostStatusRemoved,
 } from 'query-node/dist/model'
 import { Forum } from './generated/types'
 import { PostReactionId, PrivilegedActor } from '@joystream/types/augment/all'
@@ -541,5 +543,26 @@ export async function forum_PostTextUpdated(db: DatabaseManager, event_: Substra
 }
 
 export async function forum_PostDeleted(db: DatabaseManager, event_: SubstrateEvent): Promise<void> {
-  // TODO
+  const [rationaleBytes, userId, postsData] = new Forum.PostDeletedEvent(event_).params
+  const eventTime = new Date(event_.blockTimestamp)
+
+  const postDeletedEvent = new PostDeletedEvent({
+    ...genericEventFields(event_),
+    actor: new Membership({ id: userId.toString() }),
+    rationale: bytesToString(rationaleBytes),
+  })
+
+  await db.save<PostDeletedEvent>(postDeletedEvent)
+
+  await Promise.all(
+    postsData.map(async ([, , postId, hideFlag]) => {
+      const post = await getPost(db, postId.toString())
+      const newStatus = hideFlag.valueOf() ? new PostStatusRemoved() : new PostStatusLocked()
+      newStatus.postDeletedEventId = postDeletedEvent.id
+      post.updatedAt = eventTime
+      post.status = newStatus
+      post.deletedInEvent = postDeletedEvent
+      await db.save<ForumPost>(post)
+    })
+  )
 }

+ 2 - 2
query-node/mappings/package.json

@@ -10,8 +10,8 @@
     "clean": "rm -rf lib"
   },
   "dependencies": {
-    "@dzlzv/hydra-common": "3.0.0-beta.6",
-    "@dzlzv/hydra-db-utils": "3.0.0-beta.6",
+    "@dzlzv/hydra-common": "3.0.0",
+    "@dzlzv/hydra-db-utils": "3.0.0",
     "@joystream/types": "^0.15.0",
     "warthog": "https://github.com/metmirr/warthog/releases/download/v2.30.0/warthog-v2.30.0.tgz"
   },

+ 1 - 1
query-node/package.json

@@ -42,7 +42,7 @@
     "tslib": "^2.0.0",
     "@types/bn.js": "^4.11.6",
     "bn.js": "^5.1.2",
-    "@dzlzv/hydra-processor": "3.0.0-beta.6",
+    "@dzlzv/hydra-processor": "3.0.0",
     "envsub": "4.0.7"
   },
   "volta": {

+ 3 - 0
query-node/schemas/forumEvents.graphql

@@ -331,6 +331,9 @@ type PostDeletedEvent @entity {
 
   "The actor responsible for the removal"
   actor: Membership!
+
+  "Posts deletion rationale"
+  rationale: String!
 }
 
 type PostTextUpdatedEvent @entity {

+ 12 - 0
tests/integration-tests/src/QueryNodeApi.ts

@@ -247,6 +247,10 @@ import {
   GetPostTextUpdatedEventsByEventIdsQuery,
   GetPostTextUpdatedEventsByEventIdsQueryVariables,
   GetPostTextUpdatedEventsByEventIds,
+  PostDeletedEventFieldsFragment,
+  GetPostDeletedEventsByEventIdsQuery,
+  GetPostDeletedEventsByEventIdsQueryVariables,
+  GetPostDeletedEventsByEventIds,
 } from './graphql/generated/queries'
 import { Maybe } from './graphql/generated/schema'
 import { OperationDefinitionNode } from 'graphql'
@@ -905,4 +909,12 @@ export class QueryNodeApi {
       GetPostTextUpdatedEventsByEventIdsQueryVariables
     >(GetPostTextUpdatedEventsByEventIds, { eventIds }, 'postTextUpdatedEvents')
   }
+
+  public async getPostDeletedEvents(events: EventDetails[]): Promise<PostDeletedEventFieldsFragment[]> {
+    const eventIds = events.map((e) => this.getQueryNodeEventId(e.blockNumber, e.indexInBlock))
+    return this.multipleEntitiesQuery<
+      GetPostDeletedEventsByEventIdsQuery,
+      GetPostDeletedEventsByEventIdsQueryVariables
+    >(GetPostDeletedEventsByEventIds, { eventIds }, 'postDeletedEvents')
+  }
 }

+ 100 - 0
tests/integration-tests/src/fixtures/forum/DeletePostsFixture.ts

@@ -0,0 +1,100 @@
+import { Api } from '../../Api'
+import { QueryNodeApi } from '../../QueryNodeApi'
+import { EventDetails, PostPath } from '../../types'
+import { SubmittableExtrinsic } from '@polkadot/api/types'
+import { Utils } from '../../utils'
+import { ISubmittableResult } from '@polkadot/types/types/'
+import { ForumPostFieldsFragment, PostDeletedEventFieldsFragment } from '../../graphql/generated/queries'
+import { assert } from 'chai'
+import { StandardizedFixture } from '../../Fixture'
+import { MemberId, PostId, ThreadId } from '@joystream/types/common'
+import { CategoryId } from '@joystream/types/forum'
+
+const DEFAULT_RATIONALE = 'State cleanup'
+
+type SinglePostRemovalInput = PostPath & {
+  hide?: boolean // defaults to "true"
+}
+
+export type PostsRemovalInput = {
+  posts: SinglePostRemovalInput[]
+  asMember: MemberId
+  rationale?: string
+}
+
+export class DeletePostsFixture extends StandardizedFixture {
+  protected removals: PostsRemovalInput[]
+
+  public constructor(api: Api, query: QueryNodeApi, removals: PostsRemovalInput[]) {
+    super(api, query)
+    this.removals = removals
+  }
+
+  protected async getSignerAccountOrAccounts(): Promise<string[]> {
+    return await Promise.all(
+      this.removals.map(async ({ asMember }) =>
+        (await this.api.query.members.membershipById(asMember)).controller_account.toString()
+      )
+    )
+  }
+
+  protected async getExtrinsics(): Promise<SubmittableExtrinsic<'promise'>[]> {
+    return this.removals.map((r) =>
+      this.api.tx.forum.deletePosts(
+        r.asMember,
+        r.posts.map(
+          ({ categoryId, threadId, postId, hide }) =>
+            [categoryId, threadId, postId, hide === undefined || hide] as [CategoryId, ThreadId, PostId, boolean]
+        ),
+        r.rationale || DEFAULT_RATIONALE
+      )
+    )
+  }
+
+  protected async getEventFromResult(result: ISubmittableResult): Promise<EventDetails> {
+    return this.api.retrieveForumEventDetails(result, 'PostDeleted')
+  }
+
+  protected assertQueriedPostsAreValid(
+    qPosts: ForumPostFieldsFragment[],
+    qEvents: PostDeletedEventFieldsFragment[]
+  ): void {
+    this.events.map((e, i) => {
+      const removal = this.removals[i]
+      const qEvent = this.findMatchingQueryNodeEvent(e, qEvents)
+      removal.posts.forEach((postRemoval) => {
+        const hidden = postRemoval.hide === undefined || postRemoval.hide
+        const expectedStatus = hidden ? 'PostStatusRemoved' : 'PostStatusLocked'
+        const qPost = qPosts.find((p) => p.id === postRemoval.postId.toString())
+        Utils.assert(qPost, 'Query node: Post not found')
+        Utils.assert(qPost.status.__typename === expectedStatus, `Invalid post status. Expected: ${expectedStatus}`)
+        Utils.assert(qPost.status.postDeletedEvent, 'Query node: Missing PostDeletedEvent ref')
+        assert.equal(qPost.status.postDeletedEvent.id, qEvent.id)
+      })
+    })
+  }
+
+  protected assertQueryNodeEventIsValid(qEvent: PostDeletedEventFieldsFragment, i: number): void {
+    assert.equal(qEvent.actor.id, this.removals[i].asMember.toString())
+    assert.sameMembers(
+      qEvent.posts.map((p) => p.id),
+      this.removals[i].posts.map((p) => p.postId.toString())
+    )
+    assert.equal(qEvent.rationale, this.removals[i].rationale || DEFAULT_RATIONALE)
+  }
+
+  async runQueryNodeChecks(): Promise<void> {
+    await super.runQueryNodeChecks()
+    // Query the events
+    const qEvents = await this.query.tryQueryWithTimeout(
+      () => this.query.getPostDeletedEvents(this.events),
+      (qEvents) => this.assertQueryNodeEventsAreValid(qEvents)
+    )
+
+    // Query the posts
+    const qPosts = await this.query.getPostsByIds(
+      this.removals.reduce((allPostsIds, { posts }) => allPostsIds.concat(posts.map((p) => p.postId)), [] as PostId[])
+    )
+    this.assertQueriedPostsAreValid(qPosts, qEvents)
+  }
+}

+ 1 - 0
tests/integration-tests/src/fixtures/forum/index.ts

@@ -14,3 +14,4 @@ export { ModeratePostsFixture, PostModerationInput } from './ModeratePostsFixtur
 export { InitializeForumFixture, InitializeForumConfig } from './InitializeForumFixture'
 export { ReactToPostsFixture, PostReactionParams } from './ReactToPostsFixture'
 export { UpdatePostsTextFixture, PostTextUpdate } from './UpdatePostsTextFixture'
+export { DeletePostsFixture, PostsRemovalInput } from './DeletePostsFixture'

+ 46 - 1
tests/integration-tests/src/flows/forum/posts.ts

@@ -3,9 +3,11 @@ import Debugger from 'debug'
 import { FixtureRunner } from '../../Fixture'
 import {
   AddPostsFixture,
+  DeletePostsFixture,
   InitializeForumFixture,
   PostParams,
   PostReactionParams,
+  PostsRemovalInput,
   PostTextUpdate,
   ReactToPostsFixture,
   UpdatePostsTextFixture,
@@ -51,6 +53,11 @@ export default async function threads({ api, query }: FlowProps): Promise<void>
       metadata: { value: { text: '' } },
       asMember: memberIds[3],
     },
+    {
+      ...threadPaths[3],
+      metadata: { value: { text: 'Second post by member 3' } },
+      asMember: memberIds[3],
+    },
     // Invalid cases
     {
       ...threadPaths[0],
@@ -148,7 +155,45 @@ export default async function threads({ api, query }: FlowProps): Promise<void>
   ]
   const updatePostsTextFixture = new UpdatePostsTextFixture(api, query, postTextUpdates)
   const updatePostsTextRunner = new FixtureRunner(updatePostsTextFixture)
-  await updatePostsTextRunner.runWithQueryNodeChecks()
+  await updatePostsTextRunner.run()
+
+  const postRemovals: PostsRemovalInput[] = [
+    {
+      posts: [
+        {
+          ...threadPaths[0],
+          postId: postIds[0],
+          hide: true,
+        },
+      ],
+      asMember: memberIds[0],
+      rationale: 'Clearing first post test',
+    },
+    {
+      posts: [
+        {
+          ...threadPaths[3],
+          postId: postIds[3],
+          hide: true,
+        },
+        {
+          ...threadPaths[3],
+          postId: postIds[3],
+          hide: false,
+        },
+      ],
+      asMember: memberIds[3],
+      rationale: 'Lock+remove in one extrinsic test',
+    },
+  ]
+  const deletePostsFixture = new DeletePostsFixture(api, query, postRemovals)
+  const deletePostsRunner = new FixtureRunner(deletePostsFixture)
+  await deletePostsRunner.run()
+
+  // Run compound query node checks
+  await Promise.all([updatePostsTextRunner.runQueryNodeChecks(), deletePostsRunner.runQueryNodeChecks()])
+
+  // TODO: Delete posts as any member? Would require waiting PostLifetime (currently 3600 blocks)
 
   debug('Done')
 }

+ 43 - 0
tests/integration-tests/src/graphql/generated/queries.ts

@@ -359,6 +359,24 @@ export type GetPostTextUpdatedEventsByEventIdsQuery = {
   postTextUpdatedEvents: Array<PostTextUpdatedEventFieldsFragment>
 }
 
+export type PostDeletedEventFieldsFragment = {
+  id: string
+  createdAt: any
+  inBlock: number
+  network: Types.Network
+  inExtrinsic?: Types.Maybe<string>
+  indexInBlock: number
+  rationale: string
+  posts: Array<{ id: string }>
+  actor: { id: string }
+}
+
+export type GetPostDeletedEventsByEventIdsQueryVariables = Types.Exact<{
+  eventIds?: Types.Maybe<Array<Types.Scalars['ID']> | Types.Scalars['ID']>
+}>
+
+export type GetPostDeletedEventsByEventIdsQuery = { postDeletedEvents: Array<PostDeletedEventFieldsFragment> }
+
 export type MemberMetadataFieldsFragment = { name?: Types.Maybe<string>; about?: Types.Maybe<string> }
 
 export type MembershipFieldsFragment = {
@@ -1609,6 +1627,23 @@ export const PostTextUpdatedEventFields = gql`
     newText
   }
 `
+export const PostDeletedEventFields = gql`
+  fragment PostDeletedEventFields on PostDeletedEvent {
+    id
+    createdAt
+    inBlock
+    network
+    inExtrinsic
+    indexInBlock
+    posts {
+      id
+    }
+    actor {
+      id
+    }
+    rationale
+  }
+`
 export const MemberMetadataFields = gql`
   fragment MemberMetadataFields on MemberMetadata {
     name
@@ -2552,6 +2587,14 @@ export const GetPostTextUpdatedEventsByEventIds = gql`
   }
   ${PostTextUpdatedEventFields}
 `
+export const GetPostDeletedEventsByEventIds = gql`
+  query getPostDeletedEventsByEventIds($eventIds: [ID!]) {
+    postDeletedEvents(where: { id_in: $eventIds }) {
+      ...PostDeletedEventFields
+    }
+  }
+  ${PostDeletedEventFields}
+`
 export const GetMemberById = gql`
   query getMemberById($id: ID!) {
     membershipByUniqueInput(where: { id: $id }) {

+ 15 - 1
tests/integration-tests/src/graphql/generated/schema.ts

@@ -6023,6 +6023,8 @@ export type PostDeletedEvent = BaseGraphQlObject & {
   posts: Array<ForumPost>
   actor: Membership
   actorId: Scalars['String']
+  /** Posts deletion rationale */
+  rationale: Scalars['String']
 }
 
 export type PostDeletedEventConnection = {
@@ -6037,6 +6039,7 @@ export type PostDeletedEventCreateInput = {
   network: Network
   indexInBlock: Scalars['Float']
   actor: Scalars['ID']
+  rationale: Scalars['String']
 }
 
 export type PostDeletedEventEdge = {
@@ -6061,6 +6064,8 @@ export enum PostDeletedEventOrderByInput {
   IndexInBlockDesc = 'indexInBlock_DESC',
   ActorAsc = 'actor_ASC',
   ActorDesc = 'actor_DESC',
+  RationaleAsc = 'rationale_ASC',
+  RationaleDesc = 'rationale_DESC',
 }
 
 export type PostDeletedEventUpdateInput = {
@@ -6069,6 +6074,7 @@ export type PostDeletedEventUpdateInput = {
   network?: Maybe<Network>
   indexInBlock?: Maybe<Scalars['Float']>
   actor?: Maybe<Scalars['ID']>
+  rationale?: Maybe<Scalars['String']>
 }
 
 export type PostDeletedEventWhereInput = {
@@ -6117,6 +6123,11 @@ export type PostDeletedEventWhereInput = {
   indexInBlock_in?: Maybe<Array<Scalars['Int']>>
   actor_eq?: Maybe<Scalars['ID']>
   actor_in?: Maybe<Array<Scalars['ID']>>
+  rationale_eq?: Maybe<Scalars['String']>
+  rationale_contains?: Maybe<Scalars['String']>
+  rationale_startsWith?: Maybe<Scalars['String']>
+  rationale_endsWith?: Maybe<Scalars['String']>
+  rationale_in?: Maybe<Array<Scalars['String']>>
   posts_none?: Maybe<ForumPostWhereInput>
   posts_some?: Maybe<ForumPostWhereInput>
   posts_every?: Maybe<ForumPostWhereInput>
@@ -6302,7 +6313,10 @@ export type PostReactedEvent = BaseGraphQlObject & {
   indexInBlock: Scalars['Int']
   post: ForumPost
   postId: Scalars['String']
-  /** The reaction result (new reaction, cancelation of previous reaction or invalid reaction (no effect)) */
+  /**
+   * The reaction result - new valid reaction, cancelation of previous reaction or
+   * invalid reaction (which also cancels the previous one)
+   */
   reactionResult: PostReactionResult
   reactingMember: Membership
   reactingMemberId: Scalars['String']

+ 22 - 0
tests/integration-tests/src/graphql/queries/forumEvents.graphql

@@ -328,3 +328,25 @@ query getPostTextUpdatedEventsByEventIds($eventIds: [ID!]) {
     ...PostTextUpdatedEventFields
   }
 }
+
+fragment PostDeletedEventFields on PostDeletedEvent {
+  id
+  createdAt
+  inBlock
+  network
+  inExtrinsic
+  indexInBlock
+  posts {
+    id
+  }
+  actor {
+    id
+  }
+  rationale
+}
+
+query getPostDeletedEventsByEventIds($eventIds: [ID!]) {
+  postDeletedEvents(where: { id_in: $eventIds }) {
+    ...PostDeletedEventFields
+  }
+}

+ 15 - 35
yarn.lock

@@ -1564,39 +1564,19 @@
     ajv "^6.12.0"
     ajv-keywords "^3.4.1"
 
-"@dzlzv/hydra-common@3.0.0-beta.6":
-  version "3.0.0-beta.6"
-  resolved "https://registry.yarnpkg.com/@dzlzv/hydra-common/-/hydra-common-3.0.0-beta.6.tgz#7d6e3cb13a1a6487faa4a5975b77003c27c7e8ca"
-  integrity sha512-VFtp9JEJ4PEheACHo2AMLcFW0sudFlo9DWZ3ZPLKFm880QWqxvORLCpog5HoNMyK2BqucC8FDDkcpFo16ueMAA==
-  dependencies:
-    bn.js "^5.1.3"
-
-"@dzlzv/hydra-common@^3.0.0-beta.6", "@dzlzv/hydra-common@^3.0.0-hydra-v3.0":
-  version "3.0.0-hydra-v3.0"
-  resolved "https://registry.yarnpkg.com/@dzlzv/hydra-common/-/hydra-common-3.0.0-hydra-v3.0.tgz#e7ca2a578e8a607317600945cdd7ea4eed2d0dae"
-  integrity sha512-WLEQ22U+j/3RmmZFKz4/CbJ3tgbGlNqfMTh67fJhx/UAuXKQx6YukLvfFIjd1onDDqgAXbf8c5+i0damjrkO3w==
-  dependencies:
-    bn.js "^5.1.3"
-
-"@dzlzv/hydra-db-utils@3.0.0-beta.6":
-  version "3.0.0-beta.6"
-  resolved "https://registry.yarnpkg.com/@dzlzv/hydra-db-utils/-/hydra-db-utils-3.0.0-beta.6.tgz#b221a24bef89a2493e42ef520fe1abbdb8d83298"
-  integrity sha512-zxH9LialjFu4mxH+0EJhHPRSewy2tbaxqr992w2gJ2hULp2kJAGRLY1kpHFG/EEE1QWvCTmcJnFw9LyC61Q8Wg==
+"@dzlzv/hydra-common@3.0.0", "@dzlzv/hydra-common@^3.0.0":
+  version "3.0.0"
+  resolved "https://registry.yarnpkg.com/@dzlzv/hydra-common/-/hydra-common-3.0.0.tgz#e25719d796c2abece0e7236a8fc963dbd886d93c"
+  integrity sha512-GRDDue6eZLBsn/jVO5RK+HxLloAhP+QzmXQ4pcqfObPzpOTZrzhmwwVwaDjZL2TxtLEjNUvyfYlHbuWYg13+0g==
   dependencies:
-    "@dzlzv/hydra-common" "^3.0.0-beta.6"
-    "@types/ioredis" "^4.17.4"
     bn.js "^5.1.3"
-    ioredis "^4.17.3"
-    lodash "^4.17.20"
-    shortid "^2.2.16"
-    typeorm "^0.2.25"
 
-"@dzlzv/hydra-db-utils@^3.0.0-beta.6":
-  version "3.0.0-hydra-v3.0"
-  resolved "https://registry.yarnpkg.com/@dzlzv/hydra-db-utils/-/hydra-db-utils-3.0.0-hydra-v3.0.tgz#c61ff2b6cd8f2229d2377c5c0a09770faead8d14"
-  integrity sha512-qryrvseTrxFeJnJwXqCnKhcM7HmwqIXWdkN0kPK8+HLDg5kFXL88a3mxgyC1XL56SWQKXuCbdj3MTeJeglj4Fw==
+"@dzlzv/hydra-db-utils@3.0.0", "@dzlzv/hydra-db-utils@^3.0.0":
+  version "3.0.0"
+  resolved "https://registry.yarnpkg.com/@dzlzv/hydra-db-utils/-/hydra-db-utils-3.0.0.tgz#d21506b2266ba8a25e15ac0e5dc39ac62257292e"
+  integrity sha512-8tC4nKnNS0VRffjgRt1qUL1mAvMZ+q20Vne0kElTNOd3YIyl+biM9cIYEwU6KAzJqOeFtMItScp8Twr9+diMyA==
   dependencies:
-    "@dzlzv/hydra-common" "^3.0.0-hydra-v3.0"
+    "@dzlzv/hydra-common" "^3.0.0"
     "@types/ioredis" "^4.17.4"
     bn.js "^5.1.3"
     ioredis "^4.17.3"
@@ -1604,13 +1584,13 @@
     shortid "^2.2.16"
     typeorm "^0.2.25"
 
-"@dzlzv/hydra-processor@3.0.0-beta.6":
-  version "3.0.0-beta.6"
-  resolved "https://registry.yarnpkg.com/@dzlzv/hydra-processor/-/hydra-processor-3.0.0-beta.6.tgz#2a79ef2df8288216886881ec3b0adba6f209437e"
-  integrity sha512-J4Vj11jpIC443ffV95xjwiqJIKllA5X0q3q47cvmf38pRZ8BZx5UhHHykxzZpR4MqjrxAze5c/QZTaoWGptNcg==
+"@dzlzv/hydra-processor@3.0.0":
+  version "3.0.0"
+  resolved "https://registry.yarnpkg.com/@dzlzv/hydra-processor/-/hydra-processor-3.0.0.tgz#1c0af56675dbf15e1d72d309af7b1afc532615ab"
+  integrity sha512-aqfk8cYaeVHAv23L3Tw04BxcdPvxKIKD7iTaHmtO49urkjUSW2PaFt639sZXtJdCvdvU1H1ftVSbyIHJ6wlVqQ==
   dependencies:
-    "@dzlzv/hydra-common" "^3.0.0-beta.6"
-    "@dzlzv/hydra-db-utils" "^3.0.0-beta.6"
+    "@dzlzv/hydra-common" "^3.0.0"
+    "@dzlzv/hydra-db-utils" "^3.0.0"
     "@oclif/command" "^1.8.0"
     "@oclif/config" "^1"
     "@oclif/errors" "^1.3.3"