From 6181aeb7503e133ef247f2b26fe84596f7dbd1f0 Mon Sep 17 00:00:00 2001
From: mateuszmiko <dmastah92@gmail.com>
Date: Thu, 5 Oct 2023 14:25:51 +0200
Subject: [PATCH] feat: add content query (MIN-61)

---
 .gitignore                                    |  1 +
 .husky/commit-msg                             |  4 -
 .husky/pre-commit                             | 33 ++++++++
 .husky/precommit                              | 17 ----
 package-lock.json                             |  2 +-
 package.json                                  | 15 +---
 pages/redux-api-poc.tsx                       | 32 ++++++++
 src/constants/api.ts                          |  1 -
 src/constants/index.ts                        |  3 +
 src/constants/mapId.ts                        |  1 -
 src/constants/zodSeed.ts                      |  1 -
 src/models/bioEntityContentSchema.ts          |  7 ++
 .../fixtures/bioEntityContentsFixture.ts      | 10 +++
 src/models/fixtures/drugFixtures.ts           |  6 +-
 src/models/fixtures/mirnasFixture.ts          |  6 +-
 src/queries/getDrugsStringWithQuery.test.ts   | 10 ---
 src/queries/getDrugsStringWithQuery.ts        |  4 -
 src/queries/getMirnasStringWithQuery.test.ts  | 10 ---
 src/queries/getMirnasStringWithQuery.ts       |  4 -
 src/redux/apiPath.test.ts                     | 22 +++++
 src/redux/apiPath.ts                          | 10 +++
 .../bioEntityContents.reducers.test.ts        | 80 +++++++++++++++++++
 .../bioEntityContents.reducers.ts             | 19 +++++
 .../bioEntityContents.slice.ts                | 20 +++++
 .../bioEntityContents.thunks.test.ts          | 39 +++++++++
 .../bioEntityContents.thunks.ts               | 20 +++++
 .../bioEntityContents.types.ts                |  4 +
 src/redux/drugs/drugs.reducers.test.ts        |  8 +-
 src/redux/drugs/drugs.thunks.test.ts          |  6 +-
 src/redux/drugs/drugs.thunks.ts               |  4 +-
 src/redux/mirnas/mirnas.reducers.test.ts      | 10 +--
 src/redux/mirnas/mirnas.thunks.test.ts        |  6 +-
 src/redux/mirnas/mirnas.thunks.ts             |  6 +-
 src/services/api/utils/axiosInstance.ts       |  2 +-
 src/services/api/utils/useApiQuery.ts         |  4 +-
 src/types/models.ts                           |  2 +
 36 files changed, 337 insertions(+), 92 deletions(-)
 delete mode 100755 .husky/commit-msg
 create mode 100755 .husky/pre-commit
 delete mode 100644 .husky/precommit
 create mode 100644 pages/redux-api-poc.tsx
 delete mode 100644 src/constants/api.ts
 create mode 100644 src/constants/index.ts
 delete mode 100644 src/constants/mapId.ts
 delete mode 100644 src/constants/zodSeed.ts
 create mode 100644 src/models/bioEntityContentSchema.ts
 create mode 100644 src/models/fixtures/bioEntityContentsFixture.ts
 delete mode 100644 src/queries/getDrugsStringWithQuery.test.ts
 delete mode 100644 src/queries/getDrugsStringWithQuery.ts
 delete mode 100644 src/queries/getMirnasStringWithQuery.test.ts
 delete mode 100644 src/queries/getMirnasStringWithQuery.ts
 create mode 100644 src/redux/apiPath.test.ts
 create mode 100644 src/redux/apiPath.ts
 create mode 100644 src/redux/bioEntityContents/bioEntityContents.reducers.test.ts
 create mode 100644 src/redux/bioEntityContents/bioEntityContents.reducers.ts
 create mode 100644 src/redux/bioEntityContents/bioEntityContents.slice.ts
 create mode 100644 src/redux/bioEntityContents/bioEntityContents.thunks.test.ts
 create mode 100644 src/redux/bioEntityContents/bioEntityContents.thunks.ts
 create mode 100644 src/redux/bioEntityContents/bioEntityContents.types.ts

diff --git a/.gitignore b/.gitignore
index 4f410100..a99a9da8 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,6 +1,7 @@
 node_modules
 .next
 .next/
+tsconfig.tsbuildinfo
 
 # testing
 /coverage
diff --git a/.husky/commit-msg b/.husky/commit-msg
deleted file mode 100755
index c160a771..00000000
--- a/.husky/commit-msg
+++ /dev/null
@@ -1,4 +0,0 @@
-#!/usr/bin/env sh
-. "$(dirname -- "$0")/_/husky.sh"
-
-npx --no -- commitlint --edit ${1}
diff --git a/.husky/pre-commit b/.husky/pre-commit
new file mode 100755
index 00000000..3194577c
--- /dev/null
+++ b/.husky/pre-commit
@@ -0,0 +1,33 @@
+#!/bin/sh
+. "$(dirname "$0")/_/husky.sh"
+
+npx --no -- commitlint --edit ${1}
+ 
+echo '🏗️👷 Styling your project before committing👷‍♂️🏗️'
+echo 'please be patient, this may take a while...'
+ 
+# Check ESLint and Prettier Standards
+npm run format ||
+(
+    echo '🔨❌ Yoo, you have a problem in your code. Check linter and prettier 🔨❌
+          Run npm run format:fix, add changes and try commit again.';
+    false;
+)
+
+# Check tsconfig Standards
+npm run check-types ||
+(
+    echo '😂❌ Failed type check. 😂❌
+            Please correct the types and try commit again.'
+    false;
+)
+
+# Check Tests in Jest
+yarn run test ||
+(
+    echo '🤡❌ Failed tests. 🤡❌
+            Check the test result and fix the tests.'
+    false;
+)
+ 
+echo '🎉 No error found: committing this now.... ✨🚀🏄‍♂️🍻'
diff --git a/.husky/precommit b/.husky/precommit
deleted file mode 100644
index bd6c3b94..00000000
--- a/.husky/precommit
+++ /dev/null
@@ -1,17 +0,0 @@
-#!/bin/sh
-. "$(dirname "$0")/_/husky.sh"
- 
-echo '🏗️👷 Styling your project before committing👷‍♂️🏗️'
-echo 'please be patient, this may take a while...'
- 
-# Check ESLint Standards
-npm lint ||
-(
-    echo '🔨❌ Yoo, you have a problem in your code. Check linter 🔨❌
-          Run yarn lint, add changes and try commit again.';
-    false;
-)
- 
-echo '🎉 No error found: committing this now.... ✨🚀🏄‍♂️🍻'
- 
-npx lint-staged
\ No newline at end of file
diff --git a/package-lock.json b/package-lock.json
index 790d099b..998d2ae1 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -52,7 +52,7 @@
         "eslint-plugin-react-hooks": "^4.6.0",
         "eslint-plugin-tailwindcss": "^3.13.0",
         "eslint-plugin-testing-library": "^6.0.1",
-        "husky": "^8.0.3",
+        "husky": "^8.0.0",
         "jest": "^29.7.0",
         "jest-environment-jsdom": "^29.7.0",
         "jest-junit": "^16.0.0",
diff --git a/package.json b/package.json
index c4ee3467..9cc8f727 100644
--- a/package.json
+++ b/package.json
@@ -11,24 +11,17 @@
     "prettier:ci": "./node_modules/.bin/prettier --check .",
     "format": "next lint && ./node_modules/.bin/prettier --check .",
     "format:fix": "next lint --fix && ./node_modules/.bin/prettier --write .",
+    "check-types": "tsc --pretty --noEmit",
     "prepare": "husky install",
     "postinstall": "husky install",
-    "test": "jest --watch --config ./jest.config.ts",
+    "test": "jest --config ./jest.config.ts",
+    "test:watch": "jest --watch --config ./jest.config.ts",
     "test:ci": "jest --config ./jest.config.ts --collectCoverage --coverageDirectory=\"./coverage\" --ci --reporters=default --reporters=jest-junit --watchAll=false --passWithNoTests",
     "test:coverage": "jest --watchAll --coverage --config ./jest.config.ts",
     "test:coveragee": "jest --coverage",
     "coverage": "open ./coverage/lcov-report/index.html",
     "cypress": "cypress open"
   },
-  "lint-staged": {
-    "**/*.{js,jsx,ts,tsx}": [
-      "eslint --fix",
-      "prettier --config ./.prettierrc.js --write"
-    ],
-    "**/*.{css,scss,md,html,json}": [
-      "prettier --config ./.prettierrc.js --write"
-    ]
-  },
   "dependencies": {
     "@next/font": "^13.5.2",
     "@reduxjs/toolkit": "^1.9.6",
@@ -73,7 +66,7 @@
     "eslint-plugin-react-hooks": "^4.6.0",
     "eslint-plugin-tailwindcss": "^3.13.0",
     "eslint-plugin-testing-library": "^6.0.1",
-    "husky": "^8.0.3",
+    "husky": "^8.0.0",
     "jest": "^29.7.0",
     "jest-environment-jsdom": "^29.7.0",
     "jest-junit": "^16.0.0",
diff --git a/pages/redux-api-poc.tsx b/pages/redux-api-poc.tsx
new file mode 100644
index 00000000..86f36932
--- /dev/null
+++ b/pages/redux-api-poc.tsx
@@ -0,0 +1,32 @@
+import { getBioEntityContents } from '@/redux/bioEntityContents/bioEntityContents.thunks';
+import { getDrugs } from '@/redux/drugs/drugs.thunks';
+import { useAppDispatch } from '@/redux/hooks/useAppDispatch';
+import { getMirnas } from '@/redux/mirnas/mirnas.thunks';
+import { selectSearchValue } from '@/redux/search/search.selectors';
+import { setSearchValue } from '@/redux/search/search.slice';
+import { useSelector } from 'react-redux';
+
+const ReduxPage = (): JSX.Element => {
+  const dispatch = useAppDispatch();
+  const searchValue = useSelector(selectSearchValue);
+
+  const triggerSyncUpdate = (): void => {
+    // eslint-disable-next-line prefer-template
+    const newValue = searchValue + 'test';
+    dispatch(setSearchValue(newValue));
+    dispatch(getDrugs('aspirin'));
+    dispatch(getMirnas('hsa-miR-302b-3p'));
+    dispatch(getBioEntityContents('park7'));
+  };
+
+  return (
+    <div>
+      {searchValue}
+      <button type="button" onClick={triggerSyncUpdate}>
+        sync update
+      </button>
+    </div>
+  );
+};
+
+export default ReduxPage;
diff --git a/src/constants/api.ts b/src/constants/api.ts
deleted file mode 100644
index d768f3c6..00000000
--- a/src/constants/api.ts
+++ /dev/null
@@ -1 +0,0 @@
-export const BASE_API_URL = process.env.NEXT_PUBLIC_BASE_API_URL || '';
diff --git a/src/constants/index.ts b/src/constants/index.ts
new file mode 100644
index 00000000..43423ae1
--- /dev/null
+++ b/src/constants/index.ts
@@ -0,0 +1,3 @@
+export const BASE_API_URL = process.env.NEXT_PUBLIC_BASE_API_URL || '';
+export const PROJECT_ID = process.env.NEXT_PUBLIC_PROJECT_ID || '';
+export const ZOD_SEED = 997;
diff --git a/src/constants/mapId.ts b/src/constants/mapId.ts
deleted file mode 100644
index 52dd8ff8..00000000
--- a/src/constants/mapId.ts
+++ /dev/null
@@ -1 +0,0 @@
-export const PROJECT_ID = process.env.NEXT_PUBLIC_PROJECT_ID || '';
diff --git a/src/constants/zodSeed.ts b/src/constants/zodSeed.ts
deleted file mode 100644
index 1f6c322d..00000000
--- a/src/constants/zodSeed.ts
+++ /dev/null
@@ -1 +0,0 @@
-export const ZOD_SEED = 997;
diff --git a/src/models/bioEntityContentSchema.ts b/src/models/bioEntityContentSchema.ts
new file mode 100644
index 00000000..1eb0d704
--- /dev/null
+++ b/src/models/bioEntityContentSchema.ts
@@ -0,0 +1,7 @@
+import { z } from 'zod';
+
+export const bioEntityContentSchema = z.object({
+  id: z.number(),
+  modelId: z.number(),
+  type: z.string(),
+});
diff --git a/src/models/fixtures/bioEntityContentsFixture.ts b/src/models/fixtures/bioEntityContentsFixture.ts
new file mode 100644
index 00000000..adc441d3
--- /dev/null
+++ b/src/models/fixtures/bioEntityContentsFixture.ts
@@ -0,0 +1,10 @@
+import { ZOD_SEED } from '@/constants';
+import { bioEntityContentSchema } from '@/models/bioEntityContentSchema';
+import { z } from 'zod';
+// eslint-disable-next-line import/no-extraneous-dependencies
+import { createFixture } from 'zod-fixture';
+
+export const bioEntityContentsFixture = createFixture(z.array(bioEntityContentSchema), {
+  seed: ZOD_SEED,
+  array: { min: 2, max: 2 },
+});
diff --git a/src/models/fixtures/drugFixtures.ts b/src/models/fixtures/drugFixtures.ts
index 76c0b54c..44c60b79 100644
--- a/src/models/fixtures/drugFixtures.ts
+++ b/src/models/fixtures/drugFixtures.ts
@@ -1,8 +1,8 @@
+import { ZOD_SEED } from '@/constants';
+import { drugSchema } from '@/models/drugSchema';
+import { z } from 'zod';
 // eslint-disable-next-line import/no-extraneous-dependencies
 import { createFixture } from 'zod-fixture';
-import { z } from 'zod';
-import { drugSchema } from '@/models/drugSchema';
-import { ZOD_SEED } from '@/constants/zodSeed';
 
 export const drugsFixture = createFixture(z.array(drugSchema), {
   seed: ZOD_SEED,
diff --git a/src/models/fixtures/mirnasFixture.ts b/src/models/fixtures/mirnasFixture.ts
index d0c33263..129ee9a7 100644
--- a/src/models/fixtures/mirnasFixture.ts
+++ b/src/models/fixtures/mirnasFixture.ts
@@ -1,8 +1,8 @@
+import { ZOD_SEED } from '@/constants';
+import { mirnaSchema } from '@/models/mirnaSchema';
+import { z } from 'zod';
 // eslint-disable-next-line import/no-extraneous-dependencies
 import { createFixture } from 'zod-fixture';
-import { z } from 'zod';
-import { mirnaSchema } from '@/models/mirnaSchema';
-import { ZOD_SEED } from '@/constants/zodSeed';
 
 export const mirnasFixture = createFixture(z.array(mirnaSchema), {
   seed: ZOD_SEED,
diff --git a/src/queries/getDrugsStringWithQuery.test.ts b/src/queries/getDrugsStringWithQuery.test.ts
deleted file mode 100644
index 194f6704..00000000
--- a/src/queries/getDrugsStringWithQuery.test.ts
+++ /dev/null
@@ -1,10 +0,0 @@
-import { PROJECT_ID } from '@/constants/mapId';
-import { getDrugsStringWithQuery } from './getDrugsStringWithQuery';
-
-describe('getDrugsStringWithQuery', () => {
-  it('should return url string', () => {
-    expect(getDrugsStringWithQuery('aspirin')).toBe(
-      `projects/${PROJECT_ID}/drugs:search?query=aspirin`,
-    );
-  });
-});
diff --git a/src/queries/getDrugsStringWithQuery.ts b/src/queries/getDrugsStringWithQuery.ts
deleted file mode 100644
index 527b0484..00000000
--- a/src/queries/getDrugsStringWithQuery.ts
+++ /dev/null
@@ -1,4 +0,0 @@
-import { PROJECT_ID } from '@/constants/mapId';
-
-export const getDrugsStringWithQuery = (searchQuery: string): string =>
-  `projects/${PROJECT_ID}/drugs:search?query=${searchQuery}`;
diff --git a/src/queries/getMirnasStringWithQuery.test.ts b/src/queries/getMirnasStringWithQuery.test.ts
deleted file mode 100644
index a56ebeed..00000000
--- a/src/queries/getMirnasStringWithQuery.test.ts
+++ /dev/null
@@ -1,10 +0,0 @@
-import { PROJECT_ID } from '@/constants/mapId';
-import { getMirnasStringWithQuery } from './getMirnasStringWithQuery';
-
-describe('getMirnasStringWithQuery', () => {
-  it('should return url string', () => {
-    expect(getMirnasStringWithQuery('hsa-miR-302b-3p')).toBe(
-      `projects/${PROJECT_ID}/miRnas:search?query=hsa-miR-302b-3p`,
-    );
-  });
-});
diff --git a/src/queries/getMirnasStringWithQuery.ts b/src/queries/getMirnasStringWithQuery.ts
deleted file mode 100644
index c5969da4..00000000
--- a/src/queries/getMirnasStringWithQuery.ts
+++ /dev/null
@@ -1,4 +0,0 @@
-import { PROJECT_ID } from '@/constants/mapId';
-
-export const getMirnasStringWithQuery = (searchQuery: string): string =>
-  `projects/${PROJECT_ID}/miRnas:search?query=${searchQuery}`;
diff --git a/src/redux/apiPath.test.ts b/src/redux/apiPath.test.ts
new file mode 100644
index 00000000..ca36caae
--- /dev/null
+++ b/src/redux/apiPath.test.ts
@@ -0,0 +1,22 @@
+import { PROJECT_ID } from '@/constants';
+import { apiPath } from '@/redux/apiPath';
+
+describe('api path', () => {
+  it('should return url string for drugs', () => {
+    expect(apiPath.getDrugsStringWithQuery('aspirin')).toBe(
+      `projects/${PROJECT_ID}/drugs:search?query=aspirin`,
+    );
+  });
+
+  it('should return url string for miRNA', () => {
+    expect(apiPath.getMirnasStringWithQuery('hsa-miR-302b-3p')).toBe(
+      `projects/${PROJECT_ID}/miRnas:search?query=hsa-miR-302b-3p`,
+    );
+  });
+
+  it('should return url string for bio entity content', () => {
+    expect(apiPath.getBioEntityContentsStringWithQuery('park7')).toBe(
+      `projects/${PROJECT_ID}/models/*/bioEntities:search?query=park7`,
+    );
+  });
+});
diff --git a/src/redux/apiPath.ts b/src/redux/apiPath.ts
new file mode 100644
index 00000000..d199de1e
--- /dev/null
+++ b/src/redux/apiPath.ts
@@ -0,0 +1,10 @@
+import { PROJECT_ID } from '@/constants';
+
+export const apiPath = {
+  getBioEntityContentsStringWithQuery: (searchQuery: string): string =>
+    `projects/${PROJECT_ID}/models/*/bioEntities:search?query=${searchQuery}`,
+  getDrugsStringWithQuery: (searchQuery: string): string =>
+    `projects/${PROJECT_ID}/drugs:search?query=${searchQuery}`,
+  getMirnasStringWithQuery: (searchQuery: string): string =>
+    `projects/${PROJECT_ID}/miRnas:search?query=${searchQuery}`,
+};
diff --git a/src/redux/bioEntityContents/bioEntityContents.reducers.test.ts b/src/redux/bioEntityContents/bioEntityContents.reducers.test.ts
new file mode 100644
index 00000000..12831d5c
--- /dev/null
+++ b/src/redux/bioEntityContents/bioEntityContents.reducers.test.ts
@@ -0,0 +1,80 @@
+import { bioEntityContentsFixture } from '@/models/fixtures/bioEntityContentsFixture';
+import { mockNetworkResponse } from '@/utils/mockNetworkResponse';
+import {
+  ToolkitStoreWithSingleSlice,
+  createStoreInstanceUsingSliceReducer,
+} from '@/utils/createStoreInstanceUsingSliceReducer';
+import { HttpStatusCode } from 'axios';
+import { apiPath } from '@/redux/apiPath';
+import { getBioEntityContents } from './bioEntityContents.thunks';
+import bioEntityContentsReducer from './bioEntityContents.slice';
+import { BioEntityContentsState } from './bioEntityContents.types';
+
+const mockedAxiosClient = mockNetworkResponse();
+const SEARCH_QUERY = 'park7';
+
+const INITIAL_STATE: BioEntityContentsState = {
+  data: [],
+  loading: 'idle',
+  error: { name: '', message: '' },
+};
+
+describe('bioEntityContents reducer', () => {
+  let store = {} as ToolkitStoreWithSingleSlice<BioEntityContentsState>;
+  beforeEach(() => {
+    store = createStoreInstanceUsingSliceReducer('bioEntityContents', bioEntityContentsReducer);
+  });
+
+  it('should match initial state', () => {
+    const action = { type: 'unknown' };
+
+    expect(bioEntityContentsReducer(undefined, action)).toEqual(INITIAL_STATE);
+  });
+  it('should update store after succesfull getBioEntityContents query', async () => {
+    mockedAxiosClient
+      .onGet(apiPath.getBioEntityContentsStringWithQuery(SEARCH_QUERY))
+      .reply(HttpStatusCode.Ok, bioEntityContentsFixture);
+
+    const { type } = await store.dispatch(getBioEntityContents(SEARCH_QUERY));
+    const { data, loading, error } = store.getState().bioEntityContents;
+
+    expect(type).toBe('project/getBioEntityContents/fulfilled');
+    expect(loading).toEqual('succeeded');
+    expect(error).toEqual({ message: '', name: '' });
+    expect(data).toEqual(bioEntityContentsFixture);
+  });
+
+  it('should update store after failed getBioEntityContents query', async () => {
+    mockedAxiosClient
+      .onGet(apiPath.getBioEntityContentsStringWithQuery(SEARCH_QUERY))
+      .reply(HttpStatusCode.NotFound, bioEntityContentsFixture);
+
+    const { type } = await store.dispatch(getBioEntityContents(SEARCH_QUERY));
+    const { data, loading, error } = store.getState().bioEntityContents;
+
+    expect(type).toBe('project/getBioEntityContents/rejected');
+    expect(loading).toEqual('failed');
+    expect(error).toEqual({ message: '', name: '' });
+    expect(data).toEqual([]);
+  });
+
+  it('should update store on loading getBioEntityContents query', async () => {
+    mockedAxiosClient
+      .onGet(apiPath.getBioEntityContentsStringWithQuery(SEARCH_QUERY))
+      .reply(HttpStatusCode.Ok, bioEntityContentsFixture);
+
+    const bioEntityContentsPromise = store.dispatch(getBioEntityContents(SEARCH_QUERY));
+
+    const { data, loading } = store.getState().bioEntityContents;
+    expect(data).toEqual([]);
+    expect(loading).toEqual('pending');
+
+    bioEntityContentsPromise.then(() => {
+      const { data: dataPromiseFulfilled, loading: promiseFulfilled } =
+        store.getState().bioEntityContents;
+
+      expect(dataPromiseFulfilled).toEqual(bioEntityContentsFixture);
+      expect(promiseFulfilled).toEqual('succeeded');
+    });
+  });
+});
diff --git a/src/redux/bioEntityContents/bioEntityContents.reducers.ts b/src/redux/bioEntityContents/bioEntityContents.reducers.ts
new file mode 100644
index 00000000..d8806feb
--- /dev/null
+++ b/src/redux/bioEntityContents/bioEntityContents.reducers.ts
@@ -0,0 +1,19 @@
+import { ActionReducerMapBuilder } from '@reduxjs/toolkit';
+import { BioEntityContentsState } from './bioEntityContents.types';
+import { getBioEntityContents } from './bioEntityContents.thunks';
+
+export const getBioEntityContentsReducer = (
+  builder: ActionReducerMapBuilder<BioEntityContentsState>,
+): void => {
+  builder.addCase(getBioEntityContents.pending, state => {
+    state.loading = 'pending';
+  });
+  builder.addCase(getBioEntityContents.fulfilled, (state, action) => {
+    state.data = action.payload;
+    state.loading = 'succeeded';
+  });
+  builder.addCase(getBioEntityContents.rejected, state => {
+    state.loading = 'failed';
+    // TODO: error management to be discussed in the team
+  });
+};
diff --git a/src/redux/bioEntityContents/bioEntityContents.slice.ts b/src/redux/bioEntityContents/bioEntityContents.slice.ts
new file mode 100644
index 00000000..97e3b73c
--- /dev/null
+++ b/src/redux/bioEntityContents/bioEntityContents.slice.ts
@@ -0,0 +1,20 @@
+import { createSlice } from '@reduxjs/toolkit';
+import { BioEntityContentsState } from '@/redux/bioEntityContents/bioEntityContents.types';
+import { getBioEntityContentsReducer } from './bioEntityContents.reducers';
+
+const initialState: BioEntityContentsState = {
+  data: [],
+  loading: 'idle',
+  error: { name: '', message: '' },
+};
+
+export const bioEntityContentsSlice = createSlice({
+  name: 'bioEntityContents',
+  initialState,
+  reducers: {},
+  extraReducers: builder => {
+    getBioEntityContentsReducer(builder);
+  },
+});
+
+export default bioEntityContentsSlice.reducer;
diff --git a/src/redux/bioEntityContents/bioEntityContents.thunks.test.ts b/src/redux/bioEntityContents/bioEntityContents.thunks.test.ts
new file mode 100644
index 00000000..540e29ce
--- /dev/null
+++ b/src/redux/bioEntityContents/bioEntityContents.thunks.test.ts
@@ -0,0 +1,39 @@
+import { bioEntityContentsFixture } from '@/models/fixtures/bioEntityContentsFixture';
+import { mockNetworkResponse } from '@/utils/mockNetworkResponse';
+import {
+  ToolkitStoreWithSingleSlice,
+  createStoreInstanceUsingSliceReducer,
+} from '@/utils/createStoreInstanceUsingSliceReducer';
+import { HttpStatusCode } from 'axios';
+import { apiPath } from '@/redux/apiPath';
+import { getBioEntityContents } from './bioEntityContents.thunks';
+import contentsReducer from './bioEntityContents.slice';
+import { BioEntityContentsState } from './bioEntityContents.types';
+
+const mockedAxiosClient = mockNetworkResponse();
+const SEARCH_QUERY = 'park7';
+
+describe('bioEntityContents thunks', () => {
+  let store = {} as ToolkitStoreWithSingleSlice<BioEntityContentsState>;
+  beforeEach(() => {
+    store = createStoreInstanceUsingSliceReducer('bioEntityContents', contentsReducer);
+  });
+  describe('getBioEntityContents', () => {
+    it('should return data when data response from API is valid', async () => {
+      mockedAxiosClient
+        .onGet(apiPath.getBioEntityContentsStringWithQuery(SEARCH_QUERY))
+        .reply(HttpStatusCode.Ok, bioEntityContentsFixture);
+
+      const { payload } = await store.dispatch(getBioEntityContents(SEARCH_QUERY));
+      expect(payload).toEqual(bioEntityContentsFixture);
+    });
+    it('should return undefined when data response from API is not valid ', async () => {
+      mockedAxiosClient
+        .onGet(apiPath.getBioEntityContentsStringWithQuery(SEARCH_QUERY))
+        .reply(HttpStatusCode.Ok, { randomProperty: 'randomValue' });
+
+      const { payload } = await store.dispatch(getBioEntityContents(SEARCH_QUERY));
+      expect(payload).toEqual(undefined);
+    });
+  });
+});
diff --git a/src/redux/bioEntityContents/bioEntityContents.thunks.ts b/src/redux/bioEntityContents/bioEntityContents.thunks.ts
new file mode 100644
index 00000000..6ccda7e2
--- /dev/null
+++ b/src/redux/bioEntityContents/bioEntityContents.thunks.ts
@@ -0,0 +1,20 @@
+import { z } from 'zod';
+import { createAsyncThunk } from '@reduxjs/toolkit';
+import { axiosInstance } from '@/services/api/utils/axiosInstance';
+import { BioEntityContent } from '@/types/models';
+import { bioEntityContentSchema } from '@/models/bioEntityContentSchema';
+import { validateDataUsingZodSchema } from '@/utils/validateDataUsingZodSchema';
+import { apiPath } from '@/redux/apiPath';
+
+export const getBioEntityContents = createAsyncThunk(
+  'project/getBioEntityContents',
+  async (searchQuery: string): Promise<BioEntityContent[] | undefined> => {
+    const response = await axiosInstance.get<BioEntityContent[]>(
+      apiPath.getBioEntityContentsStringWithQuery(searchQuery),
+    );
+
+    const isDataValid = validateDataUsingZodSchema(response.data, z.array(bioEntityContentSchema));
+
+    return isDataValid ? response.data : undefined;
+  },
+);
diff --git a/src/redux/bioEntityContents/bioEntityContents.types.ts b/src/redux/bioEntityContents/bioEntityContents.types.ts
new file mode 100644
index 00000000..3efecc0f
--- /dev/null
+++ b/src/redux/bioEntityContents/bioEntityContents.types.ts
@@ -0,0 +1,4 @@
+import { FetchDataState } from '@/types/fetchDataState';
+import { BioEntityContent } from '@/types/models';
+
+export type BioEntityContentsState = FetchDataState<BioEntityContent[]>;
diff --git a/src/redux/drugs/drugs.reducers.test.ts b/src/redux/drugs/drugs.reducers.test.ts
index 465ccf70..c1cee057 100644
--- a/src/redux/drugs/drugs.reducers.test.ts
+++ b/src/redux/drugs/drugs.reducers.test.ts
@@ -5,7 +5,7 @@ import {
   ToolkitStoreWithSingleSlice,
   createStoreInstanceUsingSliceReducer,
 } from '@/utils/createStoreInstanceUsingSliceReducer';
-import { getDrugsStringWithQuery } from '@/queries/getDrugsStringWithQuery';
+import { apiPath } from '@/redux/apiPath';
 import { getDrugs } from './drugs.thunks';
 import drugsReducer from './drugs.slice';
 import { DrugsState } from './drugs.types';
@@ -32,7 +32,7 @@ describe('drugs reducer', () => {
   });
   it('should update store after succesfull getDrugs query', async () => {
     mockedAxiosClient
-      .onGet(getDrugsStringWithQuery(SEARCH_QUERY))
+      .onGet(apiPath.getDrugsStringWithQuery(SEARCH_QUERY))
       .reply(HttpStatusCode.Ok, drugsFixture);
 
     const { type } = await store.dispatch(getDrugs(SEARCH_QUERY));
@@ -46,7 +46,7 @@ describe('drugs reducer', () => {
 
   it('should update store after failed getDrugs query', async () => {
     mockedAxiosClient
-      .onGet(getDrugsStringWithQuery(SEARCH_QUERY))
+      .onGet(apiPath.getDrugsStringWithQuery(SEARCH_QUERY))
       .reply(HttpStatusCode.NotFound, []);
 
     const { type } = await store.dispatch(getDrugs(SEARCH_QUERY));
@@ -60,7 +60,7 @@ describe('drugs reducer', () => {
 
   it('should update store on loading getDrugs query', async () => {
     mockedAxiosClient
-      .onGet(getDrugsStringWithQuery(SEARCH_QUERY))
+      .onGet(apiPath.getDrugsStringWithQuery(SEARCH_QUERY))
       .reply(HttpStatusCode.Ok, drugsFixture);
 
     const drugsPromise = store.dispatch(getDrugs(SEARCH_QUERY));
diff --git a/src/redux/drugs/drugs.thunks.test.ts b/src/redux/drugs/drugs.thunks.test.ts
index 97402ddd..514ffccf 100644
--- a/src/redux/drugs/drugs.thunks.test.ts
+++ b/src/redux/drugs/drugs.thunks.test.ts
@@ -1,11 +1,11 @@
 import { HttpStatusCode } from 'axios';
 import { drugsFixture } from '@/models/fixtures/drugFixtures';
 import { mockNetworkResponse } from '@/utils/mockNetworkResponse';
-import { getDrugsStringWithQuery } from '@/queries/getDrugsStringWithQuery';
 import {
   ToolkitStoreWithSingleSlice,
   createStoreInstanceUsingSliceReducer,
 } from '@/utils/createStoreInstanceUsingSliceReducer';
+import { apiPath } from '@/redux/apiPath';
 import { getDrugs } from './drugs.thunks';
 import drugsReducer from './drugs.slice';
 import { DrugsState } from './drugs.types';
@@ -21,7 +21,7 @@ describe('drugs thunks', () => {
   describe('getDrugs', () => {
     it('should return data when data response from API is valid', async () => {
       mockedAxiosClient
-        .onGet(getDrugsStringWithQuery(SEARCH_QUERY))
+        .onGet(apiPath.getDrugsStringWithQuery(SEARCH_QUERY))
         .reply(HttpStatusCode.Ok, drugsFixture);
 
       const { payload } = await store.dispatch(getDrugs(SEARCH_QUERY));
@@ -29,7 +29,7 @@ describe('drugs thunks', () => {
     });
     it('should return undefined when data response from API is not valid ', async () => {
       mockedAxiosClient
-        .onGet(getDrugsStringWithQuery(SEARCH_QUERY))
+        .onGet(apiPath.getDrugsStringWithQuery(SEARCH_QUERY))
         .reply(HttpStatusCode.Ok, { randomProperty: 'randomValue' });
 
       const { payload } = await store.dispatch(getDrugs(SEARCH_QUERY));
diff --git a/src/redux/drugs/drugs.thunks.ts b/src/redux/drugs/drugs.thunks.ts
index cbe4ecc2..fc10b2de 100644
--- a/src/redux/drugs/drugs.thunks.ts
+++ b/src/redux/drugs/drugs.thunks.ts
@@ -1,15 +1,15 @@
 import { z } from 'zod';
-import { getDrugsStringWithQuery } from '@/queries/getDrugsStringWithQuery';
 import { createAsyncThunk } from '@reduxjs/toolkit';
 import { axiosInstance } from '@/services/api/utils/axiosInstance';
 import { Drug } from '@/types/models';
 import { drugSchema } from '@/models/drugSchema';
 import { validateDataUsingZodSchema } from '@/utils/validateDataUsingZodSchema';
+import { apiPath } from '@/redux/apiPath';
 
 export const getDrugs = createAsyncThunk(
   'project/getDrugs',
   async (searchQuery: string): Promise<Drug[] | undefined> => {
-    const response = await axiosInstance.get<Drug[]>(getDrugsStringWithQuery(searchQuery));
+    const response = await axiosInstance.get<Drug[]>(apiPath.getDrugsStringWithQuery(searchQuery));
 
     const isDataValid = validateDataUsingZodSchema(response.data, z.array(drugSchema));
 
diff --git a/src/redux/mirnas/mirnas.reducers.test.ts b/src/redux/mirnas/mirnas.reducers.test.ts
index c29544db..5dd6651a 100644
--- a/src/redux/mirnas/mirnas.reducers.test.ts
+++ b/src/redux/mirnas/mirnas.reducers.test.ts
@@ -4,14 +4,14 @@ import {
   ToolkitStoreWithSingleSlice,
   createStoreInstanceUsingSliceReducer,
 } from '@/utils/createStoreInstanceUsingSliceReducer';
-import { getMirnasStringWithQuery } from '@/queries/getMirnasStringWithQuery';
 import { HttpStatusCode } from 'axios';
+import { apiPath } from '@/redux/apiPath';
 import { getMirnas } from './mirnas.thunks';
 import mirnasReducer from './mirnas.slice';
 import { MirnasState } from './mirnas.types';
 
 const mockedAxiosClient = mockNetworkResponse();
-const SEARCH_QUERY = 'aspirin';
+const SEARCH_QUERY = 'hsa-miR-302b-3p';
 
 const INITIAL_STATE: MirnasState = {
   data: [],
@@ -32,7 +32,7 @@ describe('mirnas reducer', () => {
   });
   it('should update store after succesfull getMirnas query', async () => {
     mockedAxiosClient
-      .onGet(getMirnasStringWithQuery(SEARCH_QUERY))
+      .onGet(apiPath.getMirnasStringWithQuery(SEARCH_QUERY))
       .reply(HttpStatusCode.Ok, mirnasFixture);
 
     const { type } = await store.dispatch(getMirnas(SEARCH_QUERY));
@@ -46,7 +46,7 @@ describe('mirnas reducer', () => {
 
   it('should update store after failed getMirnas query', async () => {
     mockedAxiosClient
-      .onGet(getMirnasStringWithQuery(SEARCH_QUERY))
+      .onGet(apiPath.getMirnasStringWithQuery(SEARCH_QUERY))
       .reply(HttpStatusCode.NotFound, []);
 
     const { type } = await store.dispatch(getMirnas(SEARCH_QUERY));
@@ -60,7 +60,7 @@ describe('mirnas reducer', () => {
 
   it('should update store on loading getMirnas query', async () => {
     mockedAxiosClient
-      .onGet(getMirnasStringWithQuery(SEARCH_QUERY))
+      .onGet(apiPath.getMirnasStringWithQuery(SEARCH_QUERY))
       .reply(HttpStatusCode.Ok, mirnasFixture);
 
     const mirnasPromise = store.dispatch(getMirnas(SEARCH_QUERY));
diff --git a/src/redux/mirnas/mirnas.thunks.test.ts b/src/redux/mirnas/mirnas.thunks.test.ts
index 6732a6ab..2712a5ff 100644
--- a/src/redux/mirnas/mirnas.thunks.test.ts
+++ b/src/redux/mirnas/mirnas.thunks.test.ts
@@ -4,8 +4,8 @@ import {
   ToolkitStoreWithSingleSlice,
   createStoreInstanceUsingSliceReducer,
 } from '@/utils/createStoreInstanceUsingSliceReducer';
-import { getMirnasStringWithQuery } from '@/queries/getMirnasStringWithQuery';
 import { HttpStatusCode } from 'axios';
+import { apiPath } from '@/redux/apiPath';
 import { getMirnas } from './mirnas.thunks';
 import mirnasReducer from './mirnas.slice';
 import { MirnasState } from './mirnas.types';
@@ -21,7 +21,7 @@ describe('mirnas thunks', () => {
   describe('getMirnas', () => {
     it('should return data when data response from API is valid', async () => {
       mockedAxiosClient
-        .onGet(getMirnasStringWithQuery(SEARCH_QUERY))
+        .onGet(apiPath.getMirnasStringWithQuery(SEARCH_QUERY))
         .reply(HttpStatusCode.Ok, mirnasFixture);
 
       const { payload } = await store.dispatch(getMirnas(SEARCH_QUERY));
@@ -29,7 +29,7 @@ describe('mirnas thunks', () => {
     });
     it('should return undefined when data response from API is not valid ', async () => {
       mockedAxiosClient
-        .onGet(getMirnasStringWithQuery(SEARCH_QUERY))
+        .onGet(apiPath.getMirnasStringWithQuery(SEARCH_QUERY))
         .reply(HttpStatusCode.Ok, { randomProperty: 'randomValue' });
 
       const { payload } = await store.dispatch(getMirnas(SEARCH_QUERY));
diff --git a/src/redux/mirnas/mirnas.thunks.ts b/src/redux/mirnas/mirnas.thunks.ts
index 440fe7a6..8274e868 100644
--- a/src/redux/mirnas/mirnas.thunks.ts
+++ b/src/redux/mirnas/mirnas.thunks.ts
@@ -4,12 +4,14 @@ import { axiosInstance } from '@/services/api/utils/axiosInstance';
 import { Mirna } from '@/types/models';
 import { mirnaSchema } from '@/models/mirnaSchema';
 import { validateDataUsingZodSchema } from '@/utils/validateDataUsingZodSchema';
-import { getMirnasStringWithQuery } from '@/queries/getMirnasStringWithQuery';
+import { apiPath } from '@/redux/apiPath';
 
 export const getMirnas = createAsyncThunk(
   'project/getMirnas',
   async (searchQuery: string): Promise<Mirna[] | undefined> => {
-    const response = await axiosInstance.get<Mirna[]>(getMirnasStringWithQuery(searchQuery));
+    const response = await axiosInstance.get<Mirna[]>(
+      apiPath.getMirnasStringWithQuery(searchQuery),
+    );
 
     const isDataValid = validateDataUsingZodSchema(response.data, z.array(mirnaSchema));
 
diff --git a/src/services/api/utils/axiosInstance.ts b/src/services/api/utils/axiosInstance.ts
index 5e50fba7..5912be01 100644
--- a/src/services/api/utils/axiosInstance.ts
+++ b/src/services/api/utils/axiosInstance.ts
@@ -1,5 +1,5 @@
+import { BASE_API_URL } from '@/constants';
 import axios from 'axios';
-import { BASE_API_URL } from '@/constants/api';
 
 export const axiosInstance = axios.create({
   baseURL: BASE_API_URL,
diff --git a/src/services/api/utils/useApiQuery.ts b/src/services/api/utils/useApiQuery.ts
index d150d656..917227a6 100644
--- a/src/services/api/utils/useApiQuery.ts
+++ b/src/services/api/utils/useApiQuery.ts
@@ -1,4 +1,4 @@
-import { BASE_API_URL } from '@/constants/api';
+import { BASE_API_URL } from '@/constants';
 import { QueryOptions } from '@/types/api';
 
 import useAxios, { UseAxiosResult } from 'axios-hooks';
@@ -19,7 +19,7 @@ export const useApiQuery: UseApiQuery = <TResponse extends AnyZodObject>({
     url: `${BASE_API_URL}${path}`,
   });
 
-  const dataValidation: { success: boolean; error?: ZodError<TResponse> } = useMemo(() => {
+  const dataValidation: { success: boolean; error?: ZodError } = useMemo(() => {
     if (!fetchData) {
       return { success: false, error: undefined };
     }
diff --git a/src/types/models.ts b/src/types/models.ts
index 12d62515..52aca9ac 100644
--- a/src/types/models.ts
+++ b/src/types/models.ts
@@ -3,6 +3,7 @@ import { drugSchema } from '@/models/drugSchema';
 import { organism } from '@/models/organism';
 import { projectSchema } from '@/models/project';
 import { mirnaSchema } from '@/models/mirnaSchema';
+import { bioEntityContentSchema } from '@/models/bioEntityContentSchema';
 import { z } from 'zod';
 
 export type Project = z.infer<typeof projectSchema>;
@@ -10,3 +11,4 @@ export type Organism = z.infer<typeof organism>;
 export type Disease = z.infer<typeof disease>;
 export type Drug = z.infer<typeof drugSchema>;
 export type Mirna = z.infer<typeof mirnaSchema>;
+export type BioEntityContent = z.infer<typeof bioEntityContentSchema>;
-- 
GitLab