diff --git a/.syncpackrc b/.syncpackrc index 936acd18c23c..8c62807f0bc1 100644 --- a/.syncpackrc +++ b/.syncpackrc @@ -28,6 +28,7 @@ "@refinedev/appwrite", "@refinedev/graphql", "@refinedev/nestjsx-crud", + "@refinedev/rest", "@refinedev/supabase" ], "isIgnored": true diff --git a/cypress/e2e/data-provider-strapi-v4/all.cy.ts b/cypress/e2e/data-provider-strapi-v4/all.cy.ts index e4664710aa98..2d06e6e95702 100644 --- a/cypress/e2e/data-provider-strapi-v4/all.cy.ts +++ b/cypress/e2e/data-provider-strapi-v4/all.cy.ts @@ -14,11 +14,12 @@ describe("data-provider-strapi-v4", () => { cy.get("#password").type(auth.password); }); + cy.interceptStrapiV4Login(); + submitAuthForm(); }; beforeEach(() => { - cy.wait(2000); cy.clearAllCookies(); cy.clearAllLocalStorage(); cy.clearAllSessionStorage(); @@ -158,7 +159,7 @@ describe("data-provider-strapi-v4", () => { cy.wait("@strapiV4GetPosts").then(({ request }) => { const query = request.query; console.log(query); - expect(query?.["filters["]).to.deep.equal({ + expect(query?.filters).to.deep.equal({ category: { id: { $in: ["1"], diff --git a/cypress/support/commands/intercepts/strapi-v4.ts b/cypress/support/commands/intercepts/strapi-v4.ts index 27395cbbbf15..50030f49c50b 100644 --- a/cypress/support/commands/intercepts/strapi-v4.ts +++ b/cypress/support/commands/intercepts/strapi-v4.ts @@ -6,6 +6,18 @@ import { getIdFromURL } from "../../../utils"; const hostname = "api.strapi-v4.refine.dev"; const BASE_PATH = "/api"; +Cypress.Commands.add("interceptStrapiV4Login", () => { + return cy + .intercept( + { method: "GET", hostname, pathname: `${BASE_PATH}/me` }, + { + statusCode: 200, + body: { id: 1, username: "demo", email: "demo@refine.dev" }, + }, + ) + .as("strapiV4Login"); +}); + Cypress.Commands.add("interceptStrapiV4GETPosts", () => { return cy .intercept( diff --git a/cypress/support/index.d.ts b/cypress/support/index.d.ts index 87773580e259..6bb3474fbe05 100644 --- a/cypress/support/index.d.ts +++ b/cypress/support/index.d.ts @@ -132,6 +132,7 @@ declare namespace Cypress { interceptSupabaseDELETEPost(): Chainable; interceptSupabaseGETCategories(): Chainable; + interceptStrapiV4Login(): Chainable; interceptStrapiV4GETPost(): Chainable; interceptStrapiV4GETPosts(): Chainable; interceptStrapiV4POSTPost(): Chainable; diff --git a/documentation/docs/data/packages/rest-data-provider/index.md b/documentation/docs/data/packages/rest-data-provider/index.md new file mode 100644 index 000000000000..db4d9603cb22 --- /dev/null +++ b/documentation/docs/data/packages/rest-data-provider/index.md @@ -0,0 +1,1921 @@ +--- +title: Rest Data Provider +source: https://github.com/refinedev/refine/tree/main/packages/rest +swizzle: true +--- + +We've created `@refinedev/rest` data provider to make it easier to create custom data providers for REST APIs. Creating custom data provider previously required swizzling simple-rest or other data providers. Now with rest data provider, we aim to streamline the process of creating custom data providers for REST APIs. + +It uses KY library under the hood, which is a tiny and elegant HTTP client based on Fetch API. + +2nd parameter of createDataProvider is options for the data provider itself. It has common data provider methods such as getList, getOne, create, update and inside they have additional methods such as `getEndpoint`, `buildHeaders`, `buildQueryParams`, `mapResponse`. + +All of these methods accepts respective parameters from the relevant action. Additionally, `mapResponse` and `getTotalCount` methods also accepts response object. + +, and 3rd parameter is options for KY client. You can find KY options [here](https://github.com/sindresorhus/ky#options). + +## Installation + + + +## Usage + +```tsx +import { createDataProvider } from "@refinedev/rest"; + +const MyDataProvider = createDataProvider( + "https://example.com", + {}, // Create Data Provider Options + {}, // KY Options +); +``` + +## Create Data Provider Options Interface + +A data provider is simply an object that implements a set of methods. +Each method corresponds to an operation that Refine can perform: fetching a list, creating a record, updating, deleting, etc. + +ach operation such as `getList`, `getOne`, `create` has atomic methods to build your request and format their responses, errors. + +- **getEndpoint(params)** → returns the API endpoint. +- **buildHeaders(params)** → adds additional headers. +- **buildQueryParams(params)** → accepts Refine params, such as resource, id, filters, sorters, pagination +- **buildBodyParams(params)** → prepares the request body. +- **mapResponse(response, params)** → transforms API response into the format refine expects. +- **transformError(response, params)** → converts API errors into Refine compatible HttpError. See server side errors sections. Formatted errors can be handled by UI libraries to show form errors in specific fields. + +```ts +export type CreateDataProviderOptions = { + getList?: { + /* list records */ + }; + getOne?: { + /* get record by id */ + }; + getMany?: { + /* get many by ids */ + }; + create?: { + /* create record */ + }; + update?: { + /* update record */ + }; + deleteOne?: { + /* delete record */ + }; + custom?: { + /* anything special (search, export, etc.) */ + }; +}; +``` + +E + +## How to create a custom REST data provider + +Refine comes with many built-in data providers (`simple-rest`, `strapi-v4`, `supabase`, etc.). +However, sometimes you need to connect to a custom API or handle very specific request/response formats. +In such cases, you can easily create your own Data Provider with `@refinedev/rest`. + +:::simple Quick mental model (keep this while coding) + +- **Refine** → **You**: sends params (resource, id, filters, sorters, pagination, variables, meta). +- You → API: build endpoint, query, headers, body. +- API → You: raw response/error. +- You → Refine: mapResponse (data only) and transformError (message + statusCode). + ::: + +### getList + +The `getList` method is used whenever refine needs to **fetch a list of records**. +This usually powers your list pages, tables, and anything with pagination, sorting, and filtering. + +

Understanding the Data Flow

+ +To implement `getList` effectively, you need to understand how data flows between Refine, your data provider, and your API: + +**Refine Hooks → Your Data Provider → API → Your Data Provider → Refine** + +

What Refine Provides

+ +When Refine calls `getList`, it provides these parameters: + +- `resource`: the name of the collection (e.g. `"posts"`) +- `pagination`: `{ current, pageSize }` +- `sorters`: `[ { field, order } ]` +- `filters`: `[ { field, operator, value } ]` + +

What Your API Expects

+ +Your API likely expects a different request format. For example: + +:::simple +**Example API Format:** + +- Endpoint: `https://example.com/posts` +- Pagination: `?page=&size=` +- Sorting: `?sort=-createdAt,title` (prefix `-` for descending) +- Filters: `?status=PUBLISHED&title_like=react` +- Response: `{ "data": [...], "total": 123 }` + ::: + +

What Refine Expects Back

+ +Refine only cares about two things from your response: + +1. **An array of records** - the actual data items +2. **Total count** - for pagination calculations + +Even if your API returns `{ data, total }`, you must extract these separately using `mapResponse` and `getTotalCount`. + +

Available Methods

+ +The `getList` configuration object provides these methods to transform requests and responses: + +- **`getEndpoint`**: Returns the API endpoint path (defaults to resource name) +- **`buildHeaders`**: Adds custom headers to the request +- **`buildQueryParams`**: Transforms Refine params into your API's query format +- **`mapResponse`**: Extracts the data array from your API response +- **`getTotalCount`**: Extracts the total count from your API response + +

Implementation Example

+ +```tsx +export const myDataProvider: CreateDataProviderOptions = { + getList: { + // 1. Define the endpoint (optional - defaults to resource name) + getEndpoint: ({ resource }) => resource, // "posts" → "/posts" + + // 2. Transform Refine's parameters into your API's query format + buildQueryParams: async ({ pagination, filters, sorters }) => { + const query: Record = {}; + + // Handle pagination + // Refine provides: { current: 1, pageSize: 10 } + // API expects: ?page=1&size=10 + query.page = pagination?.current ?? 1; + query.size = pagination?.pageSize ?? 10; + + // Handle sorting + // Refine provides: [{ field: "createdAt", order: "desc" }] + // API expects: ?sort[createdAt]=title + if (sorters?.length) { + query.sort = sorters.map({ field, order }) => ({ field: order }) + } + + // Handle filters + // Refine provides: [{ field: "status", operator: "eq", value: "PUBLISHED" }] + // API expects: ?status=PUBLISHED&title_like=react + for (const filter of filters ?? []) { + if (filter.operator === 'eq') { + query[filter.field] = filter.value; + } + if (filter.operator === 'contains') { + query[`${filter.field}_like`] = filter.value; + } + // Add more operators as needed (ne, gt, lt, etc.) + } + + return query; + }, + + // 3. Extract the data array from API response + mapResponse: async (response) => { + const json = await response.json(); + // Your API returns: { data: [...], total: 123 } + // Refine needs: [...] + return json.data; + }, + + // 4. Extract the total count for pagination + getTotalCount: async (response) => { + const json = await response.json(); + // Your API returns: { data: [...], total: 123 } + // Refine needs: 123 + return json.total; + }, + }, +}; +``` + +With this implementation, you've created a complete bridge between Refine and your API. Here's what happens when a user interacts with your list component: + +1. **User action**: User clicks "next page", sorts a column, or applies a filter (like searching for "Published" posts) +2. **Refine processes**: Refine calculates new parameters (`current: 2`, `pageSize: 10`, `filters: [{ field: "status", operator: "eq", value: "PUBLISHED" }]`) +3. **Your transformation**: `buildQueryParams` converts these to `?page=2&size=10&status=PUBLISHED` +4. **API call**: Request goes to `https://example.com/posts?page=2&size=10&status=PUBLISHED` +5. **Response processing**: `mapResponse` extracts the data array, `getTotalCount` extracts the total +6. **UI update**: Refine renders the filtered data with updated pagination + +This pattern makes it easier for your API's specific conventions to adapt to Refine. + +### getOne + +The `getOne` method fetches a **single record by its ID**. This powers your detail pages, edit forms, and any component that needs to display or modify a specific record. + +

Understanding the Data Flow

+ +The data flow for `getOne` is straightforward since you're fetching just one record: + +**Refine Hooks → Your Data Provider → API → Your Data Provider → Refine** + +Refine provides an ID, you build the endpoint, make the request, and return the single record object. + +

What Refine Provides

+ +Refine calls `getOne` with these parameters: + +- `resource`: the collection name (e.g. `"posts"`) +- `id`: the unique identifier of the record to fetch +- `meta`: optional metadata for custom behavior + +

What Your API Expects

+ +Your API likely expects a simple ID-based request: + +:::simple +**Example API Format:** + +- Endpoint: `https://example.com/posts/123` +- Method: `GET` +- Response: `{ "data": { "id": 123, "title": "My Post", "content": "..." } }` + ::: + +

What Refine Expects Back

+ +Refine expects just the record object - no wrapping, no arrays, just the data: + +Your API returns: + +```json +{ "data": { "id": 123, "title": "My Post", "content": "..." } } +``` + +Refine expects: + +```json +{ "id": 123, "title": "My Post", "content": "..." } +``` + +If your API wraps the record in a `data` property, you'll need to extract it. + +

Available Methods

+ +The `getOne` configuration object provides these methods to transform requests and responses: + +- **`getEndpoint`**: Builds the API endpoint path with the record ID +- **`buildHeaders`**: Adds authentication tokens or other custom headers +- **`buildQueryParams`**: Adds query parameters to the request (e.g., for API versioning or extra options) +- **`mapResponse`**: Extracts the record object from your API response + +

Implementation Example

+ +```tsx +export const myDataProvider: CreateDataProviderOptions = { + getOne: { + // Build the endpoint with the ID + getEndpoint: ({ resource, id }) => `${resource}/${id}`, // "posts/123" + + // Add custom header + buildHeaders: async ({ resource, id }) => ({ + "Accept-Language": "en-US", + }), + + // Add query parameters if needed + buildQueryParams: async ({ resource, id }) => { + const params: Record = {}; + + if (resource === "posts") { + // Load author details with the post + params.expand = "author"; + } + + return params; + }, + + // Extract the record from API response + mapResponse: async (response, params) => { + const json = await response.json(); + + // Your API returns different response only for categories. + if (params.resource === "categories") { + return json.result; + } + + // Your API wraps the record in a "data" property + // API returns: { "data": { "id": 123, "title": "My Post" } } + // Refine needs: { "id": 123, "title": "My Post" } + return json.data; + }, + }, +}; +``` + +With this `getOne` implementation, here's what happens when a user views a specific record: + +1. **User action**: User clicks on a record or navigates to a detail page +2. **Refine processes**: Refine calls `getOne` with the record ID (`id: 123`) +3. **Your transformation**: `getEndpoint` builds the URL (`posts/123`), `buildQueryParams` adds `?expand=author` +4. **API call**: Request goes to `https://example.com/posts/123?expand=author` +5. **Response processing**: `mapResponse` extracts the record object from the wrapped response +6. **UI update**: Refine displays the record with author details in forms, detail views, or other components + +This pattern makes it easier for your API's inconsistenties to adapt to Refine. + +### create + +The `create` method handles **creating new records**. This powers your create forms, quick-add modals, and any component that needs to save new data to your API. + +

Understanding the Data Flow

+ +The data flow for `create` involves sending form data to your API: + +**Refine Hooks → Your Data Provider → API → Your Data Provider → Refine** + +Refine provides form variables, you build the endpoint and request body, send the data, and return the created record. + +

What Refine Provides

+ +Refine calls `create` with these parameters: + +- `resource`: the collection name (e.g. `"posts"`) +- `variables`: the form data to be saved (e.g. `{ title: "My Post", content: "..." }`) +- `meta`: optional metadata for custom behavior + +

What Your API Expects

+ +Your API likely expects a POST request with the data in the request body: + +:::simple +**Example API Format:** + +- Endpoint: `https://example.com/posts` +- Method: `POST` +- Body: `{ dto: { "title": "My Post", "content": "Hello world" }}` +- Response: `{ "data": { "id": 124, "title": "My Post", "content": "Hello world" } }` + ::: + +

What Refine Expects Back

+ +Refine expects the newly created record object with its assigned ID: + +API returns: + +```json +{ "data": { "id": 124, "title": "My Post", "content": "Hello world" } } +``` + +Refine expects: + +```json +{ "id": 124, "title": "My Post", "content": "Hello world" } +``` + +

Available Methods

+ +The `create` configuration object provides these methods to transform requests and responses: + +- **`getEndpoint`**: Builds the API endpoint path (defaults to resource name) +- **`buildHeaders`**: Adds authentication tokens or content-type headers +- **`buildQueryParams`**: Adds query parameters to the request +- **`buildBodyParams`**: Transforms form data into your API's expected body format +- **`mapResponse`**: Extracts the created record from your API response +- **`transformError`**: Converts API errors into user-friendly form validation errors + +

Implementation Example

+ +```tsx +export const myDataProvider: CreateDataProviderOptions = { + create: { + // Build the endpoint for creating records + getEndpoint: ({ resource }) => resource, // "posts" → "/posts" + + // Add required headers for POST requests + buildHeaders: async ({ resource, variables }) => ({ + "Accept-Language": "en-US", + }), + + // Transform form data into API request body + buildBodyParams: async ({ resource, variables }) => { + // Refine provides: { title: "My Post", content: "Hello world" } + // API expects: { dto: { title: "My Post", content: "Hello world", status: "DRAFT" }} + return { + dto: { + ...variables, + status: "DRAFT", // Add default status + }, + }; + }, + + // Extract the created record from API response + mapResponse: async (response, params) => { + const json = await response.json(); + + // Your API wraps the created record in a "data" property + // API returns: { "data": { "id": 124, "title": "My Post" } } + // Refine needs: { "id": 124, "title": "My Post" } + return json.data; + }, + + // Handle API errors and convert to form validation errors + transformError: async (response) => { + const json = await response.json(); + + // API returns validation errors in different formats: + // { + // "error": "Validation failed", + // "field_errors": { + // "title": ["Title is required"], + // "email": ["Invalid format", "Already exists"], + // } + // } + // Refine expects: + // { + // message: 'Validation failed', + // statusCode: 422, + // errors: [ + // { title: ['Title is required'] }, + // { email: ['InvalidFormat', 'Already exists] } + // ] + // } + + return { + message: json.error || "Something went wrong", + statusCode: response.status, + errors: json.field_errors, + }; + }, + }, +}; +``` + +With this `create` implementation, here's what happens when a user submits a form: + +**Success scenario:** + +1. **User action**: User fills out a form and clicks "Save" or "Create" +2. **Refine processes**: Refine calls `create` with form data (`variables: { title: "My Post", content: "..." }`) +3. **Your transformation**: `buildBodyParams` adds default fields (status, timestamp) and formats the request body +4. **API call**: POST request goes to `https://example.com/posts` with the transformed data +5. **Response processing**: `mapResponse` extracts the created record with its new ID +6. **UI update**: Refine redirects to the new record or shows a success message + +**Error scenario:** + +1. **API returns error**: Server responds with 400/422 status and validation errors +2. **Error transformation**: `transformError` converts API errors into consistent format +3. **Form validation**: Refine displays field-specific errors under each input +4. **User feedback**: User sees exactly which fields need to be fixed + +This pattern ensures reliable record creation with proper data transformation and comprehensive error handling. + +### update + +The `update` method handles **updating existing records**. This powers your edit forms, inline editors, and any component that needs to modify existing data in your API. + +

Understanding the Data Flow

+ +The data flow for `update` involves sending modified form data to your API: + +**Refine Hooks → Your Data Provider → API → Your Data Provider → Refine** + +Refine provides the record ID and form variables, you build the endpoint and request body, send the data, and return the updated record. + +

What Refine Provides

+ +Refine calls `update` with these parameters: + +- `resource`: the collection name (e.g. `"posts"`) +- `id`: the unique identifier of the record to update +- `variables`: the form data with changes (e.g. `{ title: "Updated Title", content: "..." }`) +- `meta`: optional metadata for custom behavior + +

What Your API Expects

+ +Your API likely expects a PUT or PATCH request with the updated data: + +:::simple +**Example API Format:** + +- Endpoint: `https://example.com/posts/123` +- Method: `PUT` or `PATCH` +- Body: `{ dto: { "title": "Updated Title", "content": "Updated content" }}` +- Response: `{ "data": { "id": 123, "title": "Updated Title", "content": "Updated content" } }` + ::: + +

What Refine Expects Back

+ +Refine expects the updated record object reflecting all changes: + +API returns: + +```json +{ + "data": { "id": 123, "title": "Updated Title", "content": "Updated content" } +} +``` + +Refine expects: + +```json +{ "id": 123, "title": "Updated Title", "content": "Updated content" } +``` + +

Available Methods

+ +The `update` configuration object provides these methods to transform requests and responses: + +- **`getEndpoint`**: Builds the API endpoint path with the record ID +- **`getRequestMethod`**: Specifies request method, `patch` by default. +- **`buildHeaders`**: Adds authentication tokens or content-type headers +- **`buildQueryParams`**: Adds query parameters to the request +- **`buildBodyParams`**: Transforms form data into your API's expected body format +- **`mapResponse`**: Extracts the updated record from your API response +- **`transformError`**: Converts API errors into user-friendly form validation errors + +

Implementation Example

+ +```tsx +export const myDataProvider: CreateDataProviderOptions = { + update: { + // Build the endpoint with the record ID + getEndpoint: ({ resource, id }) => `${resource}/${id}`, // "posts/123" + + // Add required headers for put/patch requests + getRequestMethod: (params: UpdateParams) => 'put' + + buildHeaders: async ({ resource, id, variables }) => ({ + 'Accept-Language': 'en-US', + }), + + // Add query parameters if needed + buildQueryParams: async ({ resource, id, variables }) => { + const params: Record = {}; + + if (resource === 'posts') { + // Return updated record with author details + params.expand = 'author'; + } + + return params; + }, + + // Transform form data into API request body + buildBodyParams: async ({ resource, id, variables }) => { + // Refine provides: { title: "Updated Title", content: "Updated content" } + // API expects: { dto: { title: "Updated Title", content: "Updated content", updatedAt: "2025-09-24T..." }} + return { + dto: { + ...variables, + updatedAt: new Date().toISOString(), + } + }; + }, + + // Extract the updated record from API response + mapResponse: async (response, params) => { + const json = await response.json(); + + // Handle different response formats per resource + if (params.resource === 'categories') { + return json.result; + } + + // Your API wraps the updated record in a "data" property + // API returns: { "data": { "id": 123, "title": "Updated Title" } } + // Refine needs: { "id": 123, "title": "Updated Title" } + return json.data; + }, + + // Handle API errors and convert to form validation errors + transformError: async (response) => { + const json = await response.json(); + + // API returns validation errors: + // { + // "error": "Validation failed", + // "field_errors": { + // "title": ["Title cannot be empty"], + // "email": ["Invalid format"], + // } + // } + + return { + message: json.error || 'Update failed', + statusCode: response.status, + errors: json.field_errors, + }; + }, + }, +}; +``` + +With this `update` implementation, here's what happens when a user modifies a record: + +**Success scenario:** + +1. **User action**: User edits a form and clicks "Save" or "Update" +2. **Refine processes**: Refine calls `update` with the record ID and modified data (`id: 123`, `variables: { title: "Updated Title", content: "..." }`) +3. **Your transformation**: `buildBodyParams` adds metadata (updatedAt timestamp) and formats the request body +4. **API call**: PUT request goes to `https://example.com/posts/123?expand=author` with the transformed data +5. **Response processing**: `mapResponse` extracts the updated record with expanded author details +6. **UI update**: Refine refreshes the form or redirects with the updated data + +**Error scenario:** + +1. **API returns error**: Server responds with 400/422 status and validation errors +2. **Error transformation**: `transformError` converts API errors into consistent format +3. **Form validation**: Refine displays field-specific errors under each input +4. **User feedback**: User sees exactly which fields have validation issues + +This pattern ensures reliable record updates with proper data transformation and comprehensive error handling. + +### deleteOne + +The `deleteOne` method handles **deleting existing records**. This powers your delete buttons, bulk delete actions, and any component that needs to remove data from your API. + +

Understanding the Data Flow

+ +The data flow for `deleteOne` involves sending a delete request to your API: + +**Refine Hooks → Your Data Provider → API → Your Data Provider → Refine** + +Refine provides the record ID, you build the endpoint, send the delete request, and return the deleted record for confirmation. + +

What Refine Provides

+ +Refine calls `deleteOne` with these parameters: + +- `resource`: the collection name (e.g. `"posts"`) +- `id`: the unique identifier of the record to delete +- `variables`: optional data for soft deletes or additional context +- `meta`: optional metadata for custom behavior + +

What Your API Expects

+ +Your API likely expects a DELETE request with the record ID: + +:::simple +**Example API Format:** + +- Endpoint: `https://example.com/posts/123` +- Method: `DELETE` +- Body: Optional (for soft deletes or additional data) +- Response: `{ "data": { "id": 123, "title": "Deleted Post" } }` or `{ "success": true }` + ::: + +

What Refine Expects Back

+ +Refine expects the deleted record object for confirmation and optimistic updates: + +API returns: + +```json +{ "data": { "id": 123, "title": "Deleted Post" } } +``` + +Refine expects: + +```json +{ "id": 123, "title": "Deleted Post" } +``` + +

Available Methods

+ +The `deleteOne` configuration object provides these methods to transform requests and responses: + +- **`getEndpoint`**: Builds the API endpoint path with the record ID +- **`buildHeaders`**: Adds authentication tokens or custom headers +- **`buildQueryParams`**: Adds query parameters to the request +- **`buildBodyParams`**: Transforms variables into request body (for soft deletes) +- **`mapResponse`**: Extracts the deleted record from your API response +- **`transformError`**: Converts API errors into user-friendly error messages + +

Implementation Example

+ +```tsx +export const myDataProvider: CreateDataProviderOptions = { + deleteOne: { + // Build the endpoint with the record ID + getEndpoint: ({ resource, id }) => `${resource}/${id}`, // "posts/123" + + // Add required headers for DELETE requests + buildHeaders: async ({ resource, id, variables }) => ({ + "Accept-Language": "en-US", + }), + + // Add query parameters if needed + buildQueryParams: async ({ resource, id, variables }) => { + const params: Record = {}; + + if (resource === "posts") { + // Force hard delete instead of soft delete + params.force = true; + } + + return params; + }, + + // Transform variables into request body (for soft deletes) + buildBodyParams: async ({ resource, id, variables }) => { + // For soft deletes, send deletion reason or metadata + if (variables?.softDelete) { + return { + deletedAt: new Date().toISOString(), + deletionReason: variables.reason || "User deleted", + softDelete: true, + }; + } + + // Hard delete - no body needed + return undefined; + }, + + // Extract the deleted record from API response + mapResponse: async (response, params) => { + const json = await response.json(); + + // Handle different response formats + if (params.resource === "categories") { + return json.result; + } + + // Some APIs return just success confirmation + if (json.success && !json.data) { + // Return minimal record with just the ID for confirmation + return { id: params.id }; + } + + // Your API wraps the deleted record in a "data" property + // API returns: { "data": { "id": 123, "title": "Deleted Post" } } + // Refine needs: { "id": 123, "title": "Deleted Post" } + return json.data; + }, + + // Handle API errors and convert to user-friendly errors + transformError: async (response) => { + const json = await response.json(); + + // Handle specific delete errors + if (response.status === 409) { + return { + message: "Cannot delete: Record has dependencies", + statusCode: 409, + }; + } + + if (response.status === 403) { + return { + message: "Not authorized to delete this record", + statusCode: 403, + }; + } + + return { + message: json.error || "Delete failed", + statusCode: response.status, + }; + }, + }, +}; +``` + +With this `deleteOne` implementation, here's what happens when a user deletes a record: + +**Success scenario:** + +1. **User action**: User clicks delete button or confirms deletion in a modal +2. **Refine processes**: Refine calls `deleteOne` with the record ID (`id: 123`, optionally `variables: { softDelete: true, reason: "Outdated content" }`) +3. **Your transformation**: `buildBodyParams` formats the request body for soft delete, `buildQueryParams` adds force parameter if needed +4. **API call**: DELETE request goes to `https://example.com/posts/123?force=true` with deletion metadata +5. **Response processing**: `mapResponse` extracts the deleted record for confirmation +6. **UI update**: Refine removes the record from lists and shows success confirmation + +**Error scenario:** + +1. **API returns error**: Server responds with 409 (conflict) or 403 (forbidden) status +2. **Error transformation**: `transformError` converts specific HTTP codes into user-friendly messages +3. **User feedback**: Refine displays contextual error messages like "Cannot delete: Record has dependencies" +4. **UI state**: Record remains in the list, delete operation is cancelled + +**Soft delete scenario:** + +1. **User triggers soft delete**: Form includes deletion reason and soft delete flag +2. **Request body**: Contains `deletedAt` timestamp, reason, and soft delete flag +3. **API processing**: Server marks record as deleted without removing from database +4. **Response**: API returns the soft-deleted record with updated status +5. **UI update**: Record is filtered out of active lists but may appear in "deleted items" view + +This pattern ensures reliable record deletion with proper error handling and support for both hard and soft deletion strategies. + +### getMany + +The `getMany` method handles **fetching multiple records by their IDs**. This powers relationship fields, reference selectors, and any component that needs to load specific records by their identifiers. + +:::info Optional Method +The `getMany` method is optional. If you don't implement it, Refine will automatically fall back to making individual `getOne` requests for each ID. While this works, implementing `getMany` with batch requests is more efficient for performance. +::: + +

Understanding the Data Flow

+ +The data flow for `getMany` involves sending a request with multiple IDs to your API: + +**Refine Hooks → Your Data Provider → API → Your Data Provider → Refine** + +Refine provides an array of IDs, you build the endpoint and query parameters, make the request, and return the matching records. + +

What Refine Provides

+ +Refine calls `getMany` with these parameters: + +- `resource`: the collection name (e.g. `"posts"`) +- `ids`: array of unique identifiers to fetch (e.g. `[123, 456, 789]`) +- `meta`: optional metadata for custom behavior + +

What Your API Expects

+ +Your API might handle multiple IDs in different ways: + +:::simple +**Example API Formats:** + +**Option 1 - Query parameter with comma-separated IDs:** + +- Endpoint: `https://example.com/posts?ids=123,456,789` +- Method: `GET` + +**Option 2 - Query parameter with array format:** + +- Endpoint: `https://example.com/posts?id[]=123&id[]=456&id[]=789` +- Method: `GET` + +**Option 3 - Multiple separate requests (fallback):** + +- Endpoint: `https://example.com/posts/123`, `https://example.com/posts/456`, etc. +- Method: `GET` (multiple requests) +- Note: Less efficient but works when batch endpoints aren't available + +**Response:** `{ "data": [{ "id": 123, "title": "Post 1" }, { "id": 456, "title": "Post 2" }] }` +::: + +

What Refine Expects Back

+ +Refine expects an array of record objects matching the requested IDs: + +API returns: + +```json +{ + "data": [ + { "id": 123, "title": "Post 1" }, + { "id": 456, "title": "Post 2" }, + { "id": 789, "title": "Post 3" } + ] +} +``` + +Refine expects: + +```json +[ + { "id": 123, "title": "Post 1" }, + { "id": 456, "title": "Post 2" }, + { "id": 789, "title": "Post 3" } +] +``` + +

Available Methods

+ +The `getMany` configuration object provides these methods to transform requests and responses: + +- **`getEndpoint`**: Builds the API endpoint path (defaults to resource name) +- **`buildHeaders`**: Adds authentication tokens or custom headers +- **`buildQueryParams`**: Transforms ID array into your API's query format +- **`mapResponse`**: Extracts the record array from your API response + +

Implementation Example

+ +```tsx +export const myDataProvider: CreateDataProviderOptions = { + getMany: { + // Build the endpoint for batch requests + getEndpoint: ({ resource, ids }) => { + // Use different endpoints based on resource type + if (resource === "users") { + return `${resource}/batch`; + } + return resource; // "posts" + }, + + // Add required headers + buildHeaders: async ({ resource, ids }) => ({ + "Accept-Language": "en-US", + }), + + // Transform ID array into query parameters + buildQueryParams: async ({ resource, ids }) => { + const params: Record = {}; + + // Different query formats based on resource + if (resource === "posts") { + // Format: ?ids=123,456,789 + params.ids = ids.join(","); + } else if (resource === "categories") { + // Format: ?id[]=123&id[]=456&id[]=789 + params.id = ids; + } + + // Add expansion for related data + if (resource === "posts") { + params.expand = "author,category"; + } + + return params; + }, + + // Extract the record array from API response + mapResponse: async (response, params) => { + const json = await response.json(); + + // Handle different response formats per resource + if (params.resource === "categories") { + return json.results; + } + + // Your API wraps records in a "data" property + // API returns: { "data": [{ "id": 123 }, { "id": 456 }] } + // Refine needs: [{ "id": 123 }, { "id": 456 }] + return json.data; + }, + }, +}; +``` + +With this `getMany` implementation, here's what happens when Refine needs multiple records: + +**Success scenario:** + +1. **Refine needs records**: Reference field or relationship component requests multiple records (`ids: [123, 456, 789]`) +2. **Your transformation**: `buildQueryParams` formats IDs as comma-separated string (`?ids=123,456,789&expand=author,category`) +3. **API call**: GET request goes to `https://example.com/posts?ids=123,456,789&expand=author,category` +4. **Response processing**: `mapResponse` extracts the record array from wrapped response +5. **UI update**: Refine displays the records in select options, relationship fields, or reference components + +**Fallback behavior scenario:** + +1. **No getMany implemented**: You only implement `getOne` in your data provider +2. **Refine needs multiple records**: Component requests records with `ids: [123, 456, 789]` +3. **Automatic fallback**: Refine makes three separate `getOne` calls: `getOne({ resource: "posts", id: 123 })`, `getOne({ resource: "posts", id: 456 })`, `getOne({ resource: "posts", id: 789 })` +4. **Performance impact**: Three HTTP requests instead of one batch request +5. **UI behavior**: Same end result, but slower loading times + +**Large ID array scenario:** + +1. **Many IDs requested**: Component requests 100+ records at once +2. **Query parameter handling**: Your API must handle long query strings with many IDs +3. **API call**: GET request with all IDs in query parameters (URL length limits may apply) +4. **Response processing**: Same `mapResponse` logic handles the response +5. **Consideration**: If URL length becomes an issue, consider implementing a custom method using the `custom` data provider method with POST requests + +**Partial results scenario:** + +1. **Some IDs missing**: API returns records for IDs 123 and 456 but not 789 +2. **Response processing**: `mapResponse` returns available records `[{ id: 123 }, { id: 456 }]` +3. **UI handling**: Refine components gracefully handle missing records (show placeholder or skip) +4. **No error thrown**: Missing records are handled as normal behavior, not errors + +This pattern ensures reliable batch record fetching with support for different API designs, large datasets, and graceful error handling. + +### createMany + +The `createMany` method handles **creating multiple records in a single request**. This powers bulk creation features, import functionality, and any component that needs to efficiently create multiple records at once. + +:::info Optional Method +The `createMany` method is optional. If you don't implement it, Refine will automatically fall back to making individual `create` requests for each record. While this works, implementing `createMany` with batch requests is more efficient for performance and provides better transaction handling. +::: + +

Understanding the Data Flow

+ +The data flow for `createMany` involves sending multiple record data to your API in a single request: + +**Refine Hooks → Your Data Provider → API → Your Data Provider → Refine** + +Refine provides an array of record data, you build the endpoint and request body, send the batch data, and return the array of created records. + +

What Refine Provides

+ +Refine calls `createMany` with these parameters: + +- `resource`: the collection name (e.g. `"posts"`) +- `variables`: array of form data to be saved (e.g. `[{ title: "Post 1", content: "..." }, { title: "Post 2", content: "..." }]`) +- `meta`: optional metadata for custom behavior + +

What Your API Expects

+ +Your API likely expects a POST request with multiple records in the request body: + +:::simple +**Example API Format:** + +- Endpoint: `https://example.com/posts/batch` +- Method: `POST` +- Body: `{ "items": [{ "title": "Post 1", "content": "Hello" }, { "title": "Post 2", "content": "World" }] }` +- Response: `{ "data": [{ "id": 124, "title": "Post 1" }, { "id": 125, "title": "Post 2" }] }` + ::: + +

What Refine Expects Back

+ +Refine expects an array of newly created record objects with their assigned IDs: + +API returns: + +```json +{ + "data": [ + { "id": 124, "title": "Post 1", "content": "Hello" }, + { "id": 125, "title": "Post 2", "content": "World" } + ] +} +``` + +Refine expects: + +```json +[ + { "id": 124, "title": "Post 1", "content": "Hello" }, + { "id": 125, "title": "Post 2", "content": "World" } +] +``` + +

Available Methods

+ +The `createMany` configuration object provides these methods to transform requests and responses: + +- **`getEndpoint`**: Builds the API endpoint path (defaults to resource name) +- **`buildHeaders`**: Adds authentication tokens or content-type headers +- **`buildQueryParams`**: Adds query parameters to the request +- **`buildBodyParams`**: Transforms the array of form data into your API's expected body format +- **`mapResponse`**: Extracts the created records array from your API response +- **`transformError`**: Converts API errors into user-friendly form validation errors + +

Implementation Example

+ +```tsx +export const myDataProvider: CreateDataProviderOptions = { + createMany: { + // Build the endpoint for batch creation + getEndpoint: ({ resource }) => `${resource}/batch`, // "posts/batch" + + // Add required headers for POST requests + buildHeaders: async ({ resource, variables }) => ({ + "Accept-Language": "en-US", + }), + + // Add query parameters if needed + buildQueryParams: async ({ resource, variables }) => { + const params: Record = {}; + + if (resource === "posts") { + // Return created records with author details + params.expand = "author"; + } + + return params; + }, + + // Transform array of form data into API request body + buildBodyParams: async ({ resource, variables }) => { + // Refine provides: [{ title: "Post 1" }, { title: "Post 2" }] + // API expects: { items: [{ title: "Post 1", status: "DRAFT" }, { title: "Post 2", status: "DRAFT" }] } + const itemsWithDefaults = variables.map((item) => ({ + ...item, + status: "DRAFT", + createdAt: new Date().toISOString(), + })); + + return { + items: itemsWithDefaults, + }; + }, + + // Extract the created records array from API response + mapResponse: async (response, params) => { + const json = await response.json(); + + // Handle different response formats per resource + if (params.resource === "categories") { + return json.results; + } + + // Your API wraps the created records in a "data" property + // API returns: { "data": [{ "id": 124 }, { "id": 125 }] } + // Refine needs: [{ "id": 124 }, { "id": 125 }] + return json.data; + }, + + // Handle API errors and convert to form validation errors + transformError: async (response) => { + const json = await response.json(); + + // Handle batch creation errors + // API might return errors for individual items: + // { + // "error": "Some items failed validation", + // "item_errors": [ + // { "index": 0, "field_errors": { "title": ["Required"] } }, + // { "index": 2, "field_errors": { "email": ["Invalid"] } } + // ] + // } + + return { + message: json.error || "Batch creation failed", + statusCode: response.status, + errors: json.item_errors, + }; + }, + }, +}; +``` + +With this `createMany` implementation, here's what happens when multiple records need to be created: + +**Success scenario:** + +1. **Bulk creation triggered**: User imports CSV data or uses bulk creation form with multiple records +2. **Refine processes**: Refine calls `createMany` with array of form data (`variables: [{ title: "Post 1" }, { title: "Post 2" }]`) +3. **Your transformation**: `buildBodyParams` adds default fields to each item and formats the request body +4. **API call**: POST request goes to `https://example.com/posts/batch?expand=author` with the batch data +5. **Response processing**: `mapResponse` extracts the array of created records with their new IDs +6. **UI update**: Refine updates lists with all newly created records and shows success confirmation + +**Fallback behavior scenario:** + +1. **No createMany implemented**: You only implement `create` in your data provider +2. **Refine needs to create multiple records**: Component requests batch creation with `variables: [{ title: "Post 1" }, { title: "Post 2" }, { title: "Post 3" }]` +3. **Automatic fallback**: Refine makes three separate `create` calls: `create({ resource: "posts", variables: { title: "Post 1" } })`, etc. +4. **Performance impact**: Three HTTP requests instead of one batch request +5. **Transaction handling**: No atomicity - some records might succeed while others fail +6. **UI behavior**: Same end result, but slower and less reliable for large batches + +**Error scenario:** + +1. **API returns batch error**: Server responds with validation errors for specific items in the batch +2. **Error transformation**: `transformError` converts item-specific errors into structured format +3. **Partial success handling**: Some records might be created successfully while others fail +4. **User feedback**: Refine can display which specific items had validation issues + +**Large batch scenario:** + +1. **Many records requested**: User tries to import 1000+ records at once +2. **API limitations**: Server might have limits on batch size or request timeout +3. **Consideration**: You might want to implement chunking logic to split large batches into smaller requests +4. **Error handling**: Handle timeout and size limit errors gracefully + +This pattern ensures efficient batch record creation with proper transaction handling, performance benefits, and comprehensive error management for individual items within the batch. + +### updateMany + +The `updateMany` method handles **updating multiple records in a single request**. This powers bulk edit features, batch status changes, and any component that needs to efficiently modify multiple records at once. + +:::info Optional Method +The `updateMany` method is optional. If you don't implement it, Refine will automatically fall back to making individual `update` requests for each record. While this works, implementing `updateMany` with batch requests is more efficient for performance and provides better transaction handling. +::: + +

Understanding the Data Flow

+ +The data flow for `updateMany` involves sending multiple record updates to your API in a single request: + +**Refine Hooks → Your Data Provider → API → Your Data Provider → Refine** + +Refine provides an array of IDs and update data, you build the endpoint and request body, send the batch updates, and return the array of updated records. + +

What Refine Provides

+ +Refine calls `updateMany` with these parameters: + +- `resource`: the collection name (e.g. `"posts"`) +- `ids`: array of unique identifiers to update (e.g. `[123, 456, 789]`) +- `variables`: the form data with changes to apply to all records (e.g. `{ status: "published", updatedAt: "..." }`) +- `meta`: optional metadata for custom behavior + +

What Your API Expects

+ +Your API likely expects a PUT or PATCH request with multiple record updates: + +:::simple +**Example API Format:** + +- Endpoint: `https://example.com/posts/batch` +- Method: `PUT` or `PATCH` +- Body: `{ "ids": [123, 456, 789], "updates": { "status": "published", "updatedAt": "2025-09-24T..." } }` +- Response: `{ "data": [{ "id": 123, "status": "published" }, { "id": 456, "status": "published" }] }` + ::: + +

What Refine Expects Back

+ +Refine expects an array of updated record objects reflecting the changes: + +API returns: + +```json +{ + "data": [ + { "id": 123, "title": "Post 1", "status": "published" }, + { "id": 456, "title": "Post 2", "status": "published" }, + { "id": 789, "title": "Post 3", "status": "published" } + ] +} +``` + +Refine expects: + +```json +[ + { "id": 123, "title": "Post 1", "status": "published" }, + { "id": 456, "title": "Post 2", "status": "published" }, + { "id": 789, "title": "Post 3", "status": "published" } +] +``` + +

Available Methods

+ +The `updateMany` configuration object provides these methods to transform requests and responses: + +- **`getEndpoint`**: Builds the API endpoint path (defaults to resource name) +- **`getRequestMethod`**: Specifies request method, `patch` by default +- **`buildHeaders`**: Adds authentication tokens or content-type headers +- **`buildQueryParams`**: Adds query parameters to the request +- **`buildBodyParams`**: Transforms IDs and variables into your API's expected body format +- **`mapResponse`**: Extracts the updated records array from your API response +- **`transformError`**: Converts API errors into user-friendly error messages + +

Implementation Example

+ +```tsx +export const myDataProvider: CreateDataProviderOptions = { + updateMany: { + // Build the endpoint for batch updates + getEndpoint: ({ resource }) => `${resource}/batch`, // "posts/batch" + + // Specify request method + getRequestMethod: ({ resource, ids, variables }) => "put", + + // Add required headers for PUT/PATCH requests + buildHeaders: async ({ resource, ids, variables }) => ({ + "Accept-Language": "en-US", + }), + + // Add query parameters if needed + buildQueryParams: async ({ resource, ids, variables }) => { + const params: Record = {}; + + if (resource === "posts") { + // Return updated records with author details + params.expand = "author"; + } + + return params; + }, + + // Transform IDs and variables into API request body + buildBodyParams: async ({ resource, ids, variables }) => { + // Refine provides: ids: [123, 456], variables: { status: "published" } + // API expects: { ids: [123, 456], updates: { status: "published", updatedAt: "..." } } + return { + ids: ids, + updates: { + ...variables, + updatedAt: new Date().toISOString(), + }, + }; + }, + + // Extract the updated records array from API response + mapResponse: async (response, params) => { + const json = await response.json(); + + // Handle different response formats per resource + if (params.resource === "categories") { + return json.results; + } + + // Your API wraps the updated records in a "data" property + // API returns: { "data": [{ "id": 123 }, { "id": 456 }] } + // Refine needs: [{ "id": 123 }, { "id": 456 }] + return json.data; + }, + + // Handle API errors and convert to user-friendly errors + transformError: async (response) => { + const json = await response.json(); + + // Handle batch update errors + if (response.status === 409) { + return { + message: "Some records could not be updated due to conflicts", + statusCode: 409, + }; + } + + if (response.status === 403) { + return { + message: "Not authorized to update some records", + statusCode: 403, + }; + } + + return { + message: json.error || "Batch update failed", + statusCode: response.status, + }; + }, + }, +}; +``` + +With this `updateMany` implementation, here's what happens when multiple records need to be updated: + +**Success scenario:** + +1. **Bulk update triggered**: User selects multiple records and changes their status to "published" +2. **Refine processes**: Refine calls `updateMany` with IDs and update data (`ids: [123, 456, 789]`, `variables: { status: "published" }`) +3. **Your transformation**: `buildBodyParams` adds metadata (updatedAt) and formats the request body with IDs and updates +4. **API call**: PUT request goes to `https://example.com/posts/batch?expand=author` with the batch data +5. **Response processing**: `mapResponse` extracts the array of updated records +6. **UI update**: Refine refreshes the list view with all updated records showing the new status + +**Fallback behavior scenario:** + +1. **No updateMany implemented**: You only implement `update` in your data provider +2. **Refine needs to update multiple records**: Component requests batch update with `ids: [123, 456, 789]`, `variables: { status: "published" }` +3. **Automatic fallback**: Refine makes three separate `update` calls: `update({ resource: "posts", id: 123, variables: { status: "published" } })`, etc. +4. **Performance impact**: Three HTTP requests instead of one batch request +5. **Transaction handling**: No atomicity - some records might update while others fail +6. **UI behavior**: Same end result, but slower and less reliable for large batches + +**Partial success scenario:** + +1. **Some records cannot be updated**: API successfully updates records 123 and 456, but record 789 has validation errors +2. **Response handling**: API returns partial success with updated records and error details +3. **Error transformation**: `transformError` processes mixed success/failure responses +4. **User feedback**: Refine shows which records were updated successfully and which failed + +**Large batch scenario:** + +1. **Many records selected**: User tries to update 500+ records at once +2. **API limitations**: Server might have limits on batch size or processing time +3. **Performance consideration**: Large batches might need chunking or background processing +4. **Error handling**: Handle timeout errors and suggest smaller batch sizes + +This pattern ensures efficient batch record updates with proper transaction handling, performance benefits, and comprehensive error management for bulk operations. + +### custom + +The `custom` method handles **any special operations** that don't fit into the standard CRUD pattern. This powers search endpoints, export functionality, analytics queries, file uploads, and any unique API operations your application needs. + +:::info Required Method +Unlike other data provider methods, the `custom` method is **required** when you need to perform operations beyond standard CRUD. There's no fallback behavior - if you need custom functionality, you must implement this method. +::: + +

Understanding the Data Flow

+ +The data flow for `custom` is flexible since it handles any type of operation: + +**Refine Hooks → Your Data Provider → API → Your Data Provider → Refine** + +Refine provides the operation parameters, you build the appropriate request, send it to your custom endpoint, and return the response data. + +

What Refine Provides

+ +Refine calls `custom` with these parameters: + +- `url`: the custom endpoint URL (e.g. `"/posts/search"` or `"/analytics/dashboard"`) +- `method`: HTTP method (e.g. `"get"`, `"post"`, `"put"`, `"delete"`) +- `payload`: optional request data for POST/PUT operations +- `query`: optional query parameters +- `headers`: optional custom headers +- `meta`: optional metadata for additional context + +

What Your API Expects

+ +Your API endpoints can have any format since `custom` handles specialized operations: + +:::simple +**Example API Formats:** + +**Search endpoint:** + +- Endpoint: `https://example.com/posts/search` +- Method: `POST` +- Body: `{ "query": "react hooks", "filters": { "category": "tutorial" } }` + +**Export endpoint:** + +- Endpoint: `https://example.com/posts/export?format=csv` +- Method: `GET` + +**Analytics endpoint:** + +- Endpoint: `https://example.com/analytics/dashboard` +- Method: `GET` +- Response: `{ "metrics": { "totalPosts": 150, "publishedToday": 5 } }` + ::: + +

What Refine Expects Back

+ +Refine expects the raw response data from your custom endpoint: + +API returns: + +```json +{ "results": [...], "facets": {...}, "total": 42 } +``` + +Refine expects: + +```json +{ "results": [...], "facets": {...}, "total": 42 } +``` + +The `custom` method passes through the exact response, allowing complete flexibility. + +

Available Methods

+ +The `custom` configuration object provides these methods to transform requests and responses: + +- **`buildHeaders`**: Adds authentication tokens or custom headers +- **`buildQueryParams`**: Transforms query parameters for the request +- **`buildBodyParams`**: Transforms payload data into your API's expected body format +- **`mapResponse`**: Transforms your API response into the format your components expect + +

Implementation Example

+ +```tsx +export const myDataProvider: CreateDataProviderOptions = { + custom: { + // Add required headers for custom requests + buildHeaders: async ({ url, method, payload, query, headers, meta }) => { + const customHeaders: Record = { + "Accept-Language": "en-US", + }; + + // Add specific headers based on the custom operation + if (url.includes("/search")) { + customHeaders["X-Search-Engine"] = "elasticsearch"; + } + + if (url.includes("/export")) { + customHeaders["Accept"] = "text/csv"; + } + + return customHeaders; + }, + + // Transform query parameters for custom endpoints + buildQueryParams: async ({ + url, + method, + payload, + query, + headers, + meta, + }) => { + const params: Record = { ...query }; + + // Add default parameters for search endpoints + if (url.includes("/search")) { + params.highlight = true; + params.spell_check = true; + } + + // Add format parameter for export endpoints + if (url.includes("/export")) { + params.format = params.format || "csv"; + params.timestamp = new Date().toISOString(); + } + + return params; + }, + + // Transform payload data for custom endpoints + buildBodyParams: async ({ url, method, payload, query, headers, meta }) => { + // Search endpoint expects specific body format + if (url.includes("/search")) { + return { + searchQuery: payload?.query || "", + filters: payload?.filters || {}, + pagination: { + page: payload?.page || 1, + size: payload?.size || 20, + }, + sort: payload?.sort || "relevance", + }; + } + + // Analytics endpoint might need date ranges + if (url.includes("/analytics")) { + return { + ...payload, + dateRange: payload?.dateRange || { + from: new Date(Date.now() - 30 * 24 * 60 * 60 * 1000).toISOString(), + to: new Date().toISOString(), + }, + }; + } + + // Default: pass payload as-is + return payload; + }, + + // Transform response data from custom endpoints + mapResponse: async ( + response, + { url, method, payload, query, headers, meta }, + ) => { + const json = await response.json(); + + // Search endpoint returns results in specific format + if (url.includes("/search")) { + return { + data: json.hits || [], + total: json.total_count || 0, + facets: json.aggregations || {}, + suggestions: json.suggestions || [], + }; + } + + // Export endpoint might return file metadata + if (url.includes("/export")) { + return { + downloadUrl: json.file_url, + filename: json.filename, + size: json.file_size, + expiresAt: json.expires_at, + }; + } + + // Analytics endpoint returns metrics + if (url.includes("/analytics")) { + return { + metrics: json.data || {}, + period: json.period, + updatedAt: json.last_updated, + }; + } + + // Default: return response as-is + return json; + }, + }, +}; +``` + +With this `custom` implementation, here's what happens for different custom operations: + +**Search scenario:** + +1. **User performs search**: Search component calls `custom` with `url: "/posts/search"`, `method: "post"`, `payload: { query: "react hooks", filters: { category: "tutorial" } }` +2. **Your transformation**: `buildBodyParams` formats search parameters, `buildHeaders` adds search engine header +3. **API call**: POST request goes to `https://example.com/posts/search?highlight=true&spell_check=true` +4. **Response processing**: `mapResponse` transforms search results into consistent format with data, total, facets, and suggestions +5. **UI update**: Search components display results with highlighting, faceted navigation, and spelling suggestions + +**Export scenario:** + +1. **User requests export**: Export component calls `custom` with `url: "/posts/export"`, `method: "get"`, `query: { format: "xlsx" }` +2. **Your transformation**: `buildQueryParams` adds format and timestamp, `buildHeaders` sets appropriate Accept header +3. **API call**: GET request goes to `https://example.com/posts/export?format=xlsx×tamp=2025-09-24T...` +4. **Response processing**: `mapResponse` extracts download URL and file metadata +5. **UI update**: Component provides download link or triggers automatic download + +**Analytics scenario:** + +1. **Dashboard loads**: Analytics component calls `custom` with `url: "/analytics/dashboard"`, `method: "get"` +2. **Your transformation**: `buildBodyParams` adds default date range for last 30 days +3. **API call**: GET request goes to `https://example.com/analytics/dashboard` +4. **Response processing**: `mapResponse` structures metrics data with period information +5. **UI update**: Dashboard displays charts and metrics with last updated timestamp + +**File upload scenario:** + +1. **User uploads file**: Upload component calls `custom` with `url: "/files/upload"`, `method: "post"`, `payload: formData` +2. **Your transformation**: `buildHeaders` adds multipart content type, `buildBodyParams` passes FormData through +3. **API call**: POST request goes to `https://example.com/files/upload` with file data +4. **Response processing**: `mapResponse` extracts file ID and metadata +5. **UI update**: Component shows upload success with file details + +The `custom` method provides complete flexibility for any specialized API operations while maintaining the consistent transformation pattern used throughout the data provider. + +## Hooks + +The `@refinedev/rest` data provider uses KY as its HTTP client, which provides powerful hooks for intercepting and modifying requests and responses. We provide several pre-built hooks for common use cases, and you can also create custom hooks or swizzle existing ones to match your specific needs. + +:::info KY Hooks +These are KY hooks, not Refine hooks. They operate at the HTTP request/response level and run for every API call made by the data provider. For more information about KY hooks, see the [KY documentation](https://github.com/sindresorhus/ky#hooks). +::: + +

Using Hooks

+ +Hooks are passed as the third parameter to `createDataProvider` in the KY options: + +```tsx +import { + createDataProvider, + authHeaderBeforeRequestHook, +} from "@refinedev/rest"; + +const dataProvider = createDataProvider( + "https://api.example.com", + {}, // Data provider options + { + // KY options + hooks: { + beforeRequest: [ + authHeaderBeforeRequestHook({ ACCESS_TOKEN_KEY: "accessToken" }), + // Add more beforeRequest hooks here + ], + afterResponse: [ + // Add afterResponse hooks here + ], + beforeError: [ + // Add beforeError hooks here + ], + }, + }, +); +``` + +### Available Hooks + +KY provides several hook types for different stages of the request lifecycle: + +- **`beforeRequest`**: Modify the request before it's sent +- **`beforeRetry`**: Handle retry logic for failed requests +- **`afterResponse`**: Process the response after it's received +- **`beforeError`**: Transform errors before they're thrown + +#### Auth Header Hook + +:::simple Swizzle +You can swizzle this hook to customize it with the [**refine CLI**](/docs/3.xx.xx/packages/documentation/cli) +::: + +Automatically adds Bearer token authentication to all requests: + +```tsx +import { authHeaderBeforeRequestHook } from "@refinedev/rest"; + +const dataProvider = createDataProvider( + "https://api.example.com", + {}, + { + hooks: { + beforeRequest: [ + authHeaderBeforeRequestHook({ ACCESS_TOKEN_KEY: "accessToken" }), + ], + }, + }, +); +``` + +**Parameters:** + +- `ACCESS_TOKEN_KEY`: The localStorage key where your access token is stored + +**Behavior:** + +- Retrieves the token from `localStorage.getItem(ACCESS_TOKEN_KEY)` +- Adds `Authorization: Bearer ` header to all requests +- Silently skips if no token is found + +#### Refresh Token Hook + +:::simple Swizzle +You can swizzle this hook to customize it with the [**refine CLI**](/docs/3.xx.xx/packages/documentation/cli) +::: + +Automatically handles token refresh when receiving 401 responses: + +```tsx +import { refreshTokenAfterResponseHook } from "@refinedev/rest"; + +const dataProvider = createDataProvider( + "https://api.example.com", + {}, + { + hooks: { + afterResponse: [ + refreshTokenAfterResponseHook({ + ACCESS_TOKEN_KEY: "accessToken", + REFRESH_TOKEN_KEY: "refreshToken", + REFRESH_TOKEN_URL: "https://api.example.com/refresh-token", + }), + ], + }, + }, +); +``` + +**Parameters:** + +- `ACCESS_TOKEN_KEY`: The localStorage key where your access token is stored +- `REFRESH_TOKEN_KEY`: The localStorage key where your refresh token is stored +- `REFRESH_TOKEN_URL`: The endpoint URL for refreshing tokens + +**Behavior:** + +- Intercepts 401 responses and attempts to refresh the access token +- Sends POST request to refresh endpoint with current refresh token in body +- Updates localStorage with new access and refresh tokens +- Retries the original request with the new access token +- Returns original 401 response if token refresh fails + +### Creating Custom Hooks + +You can create custom hooks to handle your specific authentication, logging, or request modification needs: + +```tsx +import type { Hooks } from "ky"; + +// Custom beforeRequest hook for API versioning +const apiVersionHook: NonNullable[number] = async ( + request, +) => { + request.headers.set("API-Version", "2.0"); + request.headers.set("X-Client", "refine-app"); +}; + +// Custom afterResponse hook for response logging +const responseLoggerHook: NonNullable[number] = async ( + request, + options, + response, +) => { + console.log(`${request.method} ${request.url} - ${response.status}`); + return response; +}; + +// Custom beforeError hook for error transformation +const errorTransformHook: NonNullable[number] = async ( + error, +) => { + if (error.response?.status === 401) { + // Redirect to login or refresh token + window.location.href = "/login"; + } + return error; +}; + +const dataProvider = createDataProvider( + "https://api.example.com", + {}, + { + hooks: { + beforeRequest: [apiVersionHook], + afterResponse: [responseLoggerHook], + beforeError: [errorTransformHook], + }, + }, +); +``` + +### Swizzling Existing Hooks + +You can swizzle (copy and modify) our pre-built hooks to customize their behavior. Use the Refine CLI to swizzle hooks: + +```bash +npm run refine swizzle +``` + +Then select the hook you want to customize. This will copy the hook to your project where you can modify it: + +```tsx +import type { Hooks } from "ky"; + +// Swizzled version of authHeaderBeforeRequestHook with custom logic +const customAuthHeaderHook = + (options: { + ACCESS_TOKEN_KEY: string; + }): NonNullable[number] => + async (request) => { + const token = localStorage.getItem(options.ACCESS_TOKEN_KEY); + + if (token) { + // Custom: Add both Bearer token and API key + request.headers.set("Authorization", `Bearer ${token}`); + request.headers.set("X-API-Key", "your-api-key"); + } else { + // Custom: Redirect to login if no token + window.location.href = "/login"; + throw new Error("No authentication token found"); + } + }; + +const dataProvider = createDataProvider( + "https://api.example.com", + {}, + { + hooks: { + beforeRequest: [ + customAuthHeaderHook({ ACCESS_TOKEN_KEY: "accessToken" }), + ], + }, + }, +); +``` + +### Common Hook Patterns + +

Request/Response Logging

+ +```tsx +const requestLoggerHook: NonNullable[number] = async ( + request, +) => { + console.log(`Making ${request.method} request to ${request.url}`); +}; + +const responseLoggerHook: NonNullable[number] = async ( + request, + options, + response, +) => { + console.log(`Response ${response.status} from ${request.url}`); + return response; +}; +``` + +

Request Timeout

+ +```tsx +const timeoutHook: NonNullable[number] = async ( + request, + options, +) => { + // Set 30-second timeout for all requests + options.timeout = 30000; +}; +``` + +

Retry Logic with Custom Conditions

+ +```tsx +const customRetryHook: NonNullable[number] = async ({ + request, + options, + error, + retryCount, +}) => { + // Only retry for specific error codes + if (error.response?.status === 503 && retryCount < 3) { + console.log( + `Retrying request to ${request.url} (attempt ${retryCount + 1})`, + ); + // Add exponential backoff + await new Promise((resolve) => + setTimeout(resolve, Math.pow(2, retryCount) * 1000), + ); + } +}; +``` + +

Global Error Handling

+ +```tsx +const globalErrorHook: NonNullable[number] = async ( + error, +) => { + if (error.response?.status === 401) { + // Clear auth and redirect + localStorage.removeItem("accessToken"); + window.location.href = "/login"; + } else if (error.response?.status >= 500) { + // Show global error notification + console.error("Server error:", error.message); + } + return error; +}; +``` + +

Hook Execution Order

+ +Hooks execute in the order they're defined in the array: + +```tsx +const dataProvider = createDataProvider( + "https://api.example.com", + {}, + { + hooks: { + beforeRequest: [ + firstHook, // Runs first + secondHook, // Runs second + thirdHook, // Runs third + ], + }, + }, +); +``` + +This allows you to compose multiple hooks and control their execution sequence for complex request/response processing. diff --git a/documentation/sidebars.js b/documentation/sidebars.js index cf8b4ced041b..682c97bce60d 100644 --- a/documentation/sidebars.js +++ b/documentation/sidebars.js @@ -179,6 +179,7 @@ module.exports = { type: "category", label: "Packages", items: [ + "data/packages/rest-data-provider/index", "data/packages/airtable/index", "data/packages/appwrite/index", "data/packages/graphql/index", diff --git a/examples/data-provider-strapi-v4/package.json b/examples/data-provider-strapi-v4/package.json index d442464346f1..770cdec14ef0 100644 --- a/examples/data-provider-strapi-v4/package.json +++ b/examples/data-provider-strapi-v4/package.json @@ -27,10 +27,12 @@ "@refinedev/cli": "^2.16.49", "@refinedev/core": "^5.0.2", "@refinedev/react-router": "^2.0.1", + "@refinedev/rest": "^2.0.0", "@refinedev/strapi-v4": "^7.0.0", "@uiw/react-md-editor": "^4.0.8", "antd": "^5.23.0", "axios": "^1.11.0", + "ky": "^1.10.0", "react": "^19.1.0", "react-dom": "^19.1.0", "react-router": "^7.0.2" diff --git a/examples/data-provider-strapi-v4/src/App.tsx b/examples/data-provider-strapi-v4/src/App.tsx index 585fd322642b..df26112a273c 100644 --- a/examples/data-provider-strapi-v4/src/App.tsx +++ b/examples/data-provider-strapi-v4/src/App.tsx @@ -1,9 +1,4 @@ -import { - GitHubBanner, - Refine, - type AuthProvider, - Authenticated, -} from "@refinedev/core"; +import { GitHubBanner, Refine, Authenticated } from "@refinedev/core"; import { useNotificationProvider, ThemedLayout, @@ -11,14 +6,12 @@ import { AuthPage, RefineThemes, } from "@refinedev/antd"; -import { DataProvider, AuthHelper } from "@refinedev/strapi-v4"; import routerProvider, { CatchAllNavigate, NavigateToResource, UnsavedChangesNotifier, DocumentTitleHandler, } from "@refinedev/react-router"; -import axios from "axios"; import { BrowserRouter, Routes, Route, Outlet } from "react-router"; import "@ant-design/v5-patch-for-react-19"; @@ -32,105 +25,11 @@ import { CategoryEdit, } from "../src/pages/categories"; -import { TOKEN_KEY, API_URL } from "./constants"; import { ConfigProvider, App as AntdApp } from "antd"; +import { authProvider } from "./providers/auth"; +import { dataProvider } from "./providers/data"; const App: React.FC = () => { - const axiosInstance = axios.create(); - const strapiAuthHelper = AuthHelper(`${API_URL}/api`); - - const authProvider: AuthProvider = { - login: async ({ email, password }) => { - try { - const { data, status } = await strapiAuthHelper.login(email, password); - if (status === 200) { - localStorage.setItem(TOKEN_KEY, data.jwt); - - // set header axios instance - axiosInstance.defaults.headers.common["Authorization"] = - `Bearer ${data.jwt}`; - - return { - success: true, - redirectTo: "/", - }; - } - } catch (error: any) { - const errorObj = error?.response?.data?.message?.[0]?.messages?.[0]; - return { - success: false, - error: { - message: errorObj?.message || "Login failed", - name: errorObj?.id || "Invalid email or password", - }, - }; - } - - return { - success: false, - error: { - message: "Login failed", - name: "Invalid email or password", - }, - }; - }, - logout: async () => { - localStorage.removeItem(TOKEN_KEY); - return { - success: true, - redirectTo: "/login", - }; - }, - onError: async (error) => { - if (error.response?.status === 401) { - return { - logout: true, - }; - } - - return { error }; - }, - check: async () => { - const token = localStorage.getItem(TOKEN_KEY); - if (token) { - axiosInstance.defaults.headers.common["Authorization"] = - `Bearer ${token}`; - return { - authenticated: true, - }; - } - - return { - authenticated: false, - error: { - message: "Authentication failed", - name: "Token not found", - }, - logout: true, - redirectTo: "/login", - }; - }, - getPermissions: async () => null, - getIdentity: async () => { - const token = localStorage.getItem(TOKEN_KEY); - if (!token) { - return null; - } - - const { data, status } = await strapiAuthHelper.me(token); - if (status === 200) { - const { id, username, email } = data; - return { - id, - username, - email, - }; - } - - return null; - }, - }; - return ( @@ -138,7 +37,7 @@ const App: React.FC = () => { { { }, }, ]} - accessControlProvider={accessControlProvider} options={{ reactQuery: { clientConfig: queryClient, diff --git a/examples/refine-hr-ce/src/providers/auth-provider/index.tsx b/examples/refine-hr-ce/src/providers/auth-provider/index.tsx index a57472e5539b..e62904bb829e 100644 --- a/examples/refine-hr-ce/src/providers/auth-provider/index.tsx +++ b/examples/refine-hr-ce/src/providers/auth-provider/index.tsx @@ -1,19 +1,20 @@ import type { AuthProvider } from "@refinedev/core"; -import { axiosInstance } from "@/utilities/axios"; import { Role, type Employee, type ResponseLogin } from "@/types"; import { ACCESS_TOKEN_KEY, REFRESH_TOKEN_KEY } from "@/utilities/constants"; +import { kyInstance } from "../data"; export const authProvider: AuthProvider = { login: async ({ email, redirectTo }) => { try { - const response = await axiosInstance.post("/login", { - email, - }); - const data = response.data; + const { accessToken, refreshToken, user } = + await kyInstance("login", { + method: "post", + body: JSON.stringify({ email }), + }).json(); - localStorage.setItem(ACCESS_TOKEN_KEY, data.accessToken); - localStorage.setItem(REFRESH_TOKEN_KEY, data.refreshToken); - localStorage.setItem("user", JSON.stringify(data.user)); + localStorage.setItem(ACCESS_TOKEN_KEY, accessToken); + localStorage.setItem(REFRESH_TOKEN_KEY, refreshToken); + localStorage.setItem("user", JSON.stringify(user)); return { success: true, @@ -81,9 +82,8 @@ export const authProvider: AuthProvider = { }; }, getIdentity: async () => { - const response = await axiosInstance.get("/me"); - const data = response?.data; + const user = await kyInstance("me").json(); - return data; + return user; }, }; diff --git a/examples/refine-hr-ce/src/providers/data.ts b/examples/refine-hr-ce/src/providers/data.ts new file mode 100644 index 000000000000..9251c434caae --- /dev/null +++ b/examples/refine-hr-ce/src/providers/data.ts @@ -0,0 +1,28 @@ +import { + authHeaderBeforeRequestHook, + refreshTokenAfterResponseHook, +} from "@refinedev/rest"; +import { createNestjsxCrudDataProvider } from "@refinedev/rest/nestjsx-crud"; + +import { + ACCESS_TOKEN_KEY, + BASE_URL, + REFRESH_TOKEN_KEY, + REFRESH_TOKEN_URL, +} from "@/utilities/constants"; + +export const { dataProvider, kyInstance } = createNestjsxCrudDataProvider({ + apiURL: BASE_URL, + kyOptions: { + hooks: { + beforeRequest: [authHeaderBeforeRequestHook({ ACCESS_TOKEN_KEY })], + afterResponse: [ + refreshTokenAfterResponseHook({ + ACCESS_TOKEN_KEY, + REFRESH_TOKEN_KEY, + REFRESH_TOKEN_URL, + }), + ], + }, + }, +}); diff --git a/examples/refine-hr-ce/src/utilities/axios.ts b/examples/refine-hr-ce/src/utilities/axios.ts deleted file mode 100644 index 5fc66fd13f43..000000000000 --- a/examples/refine-hr-ce/src/utilities/axios.ts +++ /dev/null @@ -1,73 +0,0 @@ -import axios, { type AxiosRequestConfig, type AxiosError } from "axios"; -import { - ACCESS_TOKEN_KEY, - BASE_URL, - REFRESH_TOKEN_KEY, -} from "@/utilities/constants"; -import type { ResponseLogin } from "@/types"; - -export const axiosInstance = axios.create({ - baseURL: BASE_URL, - headers: { - "Content-Type": "application/json", - }, -}); - -axiosInstance.interceptors.request.use( - async (config) => { - const accessToken = localStorage.getItem(ACCESS_TOKEN_KEY); - if (accessToken && config?.headers) { - config.headers.Authorization = `Bearer ${accessToken}`; - } - return config; - }, - (error) => { - return Promise.reject(error); - }, -); - -axiosInstance.interceptors.response.use( - async (response) => { - return response; - }, - async (error: AxiosError) => { - const originalRequest = error.config as AxiosRequestConfig & { - _retry: boolean; - }; - - if (error.status === 401 && !originalRequest?._retry) { - const tokens = await refreshTokens(); - if (!tokens) { - return Promise.reject(error); - } - - originalRequest._retry = true; - return axiosInstance(originalRequest); - } - - return Promise.reject(error); - }, -); - -export const refreshTokens = async () => { - const currentRefreshToken = localStorage.getItem(REFRESH_TOKEN_KEY); - if (!currentRefreshToken) return null; - - try { - const response = await axiosInstance.post("/refresh-token", { - refreshToken: currentRefreshToken, - }); - const data = response.data; - - localStorage.setItem(ACCESS_TOKEN_KEY, data.accessToken); - localStorage.setItem(REFRESH_TOKEN_KEY, data.refreshToken); - localStorage.setItem("user", JSON.stringify(data.user)); - - return data; - } catch (error) { - localStorage.removeItem(ACCESS_TOKEN_KEY); - localStorage.removeItem(REFRESH_TOKEN_KEY); - localStorage.removeItem("user"); - return null; - } -}; diff --git a/examples/refine-hr-ce/src/utilities/constants.ts b/examples/refine-hr-ce/src/utilities/constants.ts index 5492aacfed0e..60c8a6bf4ef3 100644 --- a/examples/refine-hr-ce/src/utilities/constants.ts +++ b/examples/refine-hr-ce/src/utilities/constants.ts @@ -1,5 +1,7 @@ export const BASE_URL = "https://api.hr.refine.dev"; +export const REFRESH_TOKEN_URL = `${BASE_URL}/refresh-token`; + export const ACCESS_TOKEN_KEY = "accessToken"; export const REFRESH_TOKEN_KEY = "refreshToken"; diff --git a/packages/rest/.npmignore b/packages/rest/.npmignore new file mode 100644 index 000000000000..3321cad098b2 --- /dev/null +++ b/packages/rest/.npmignore @@ -0,0 +1,11 @@ +node_modules +.DS_Store +test +jest.config.js +**/*.spec.ts +**/*.spec.tsx +**/*.test.ts +**/*.test.tsx +tsup.config.ts +tsconfig.test.json +tsconfig.declarations.json \ No newline at end of file diff --git a/packages/rest/CHANGELOG.md b/packages/rest/CHANGELOG.md new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/packages/rest/LICENSE b/packages/rest/LICENSE new file mode 100644 index 000000000000..1028bea7bced --- /dev/null +++ b/packages/rest/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2022 Refine Development Inc. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. \ No newline at end of file diff --git a/packages/rest/README.md b/packages/rest/README.md new file mode 100644 index 000000000000..38352c262998 --- /dev/null +++ b/packages/rest/README.md @@ -0,0 +1,53 @@ + + +
+ +
+ Home Page | + Discord | + Examples | + Blog | + Documentation + +
+
+ +[![Discord](https://img.shields.io/discord/837692625737613362.svg?label=&logo=discord&logoColor=ffffff&color=7389D8&labelColor=6A7EC2)](https://discord.gg/refine) +[![Twitter Follow](https://img.shields.io/twitter/follow/refine_dev?style=social)](https://twitter.com/refine_dev) + +refine - 100% open source React framework to build web apps 3x faster | Product Hunt + +
+ +
+ +
refine is an open-source, headless React framework for developers building enterprise internal tools, admin panels, dashboards, B2B applications. + +
+ +It eliminates repetitive tasks in CRUD operations and provides industry-standard solutions for critical project components like **authentication**, **access control**, **routing**, **networking**, **state management**, and **i18n**. + +
+ +# Refine REST data provider + +## Installation & Usage + +``` +npm install @refinedev/rest +``` + +```tsx + +``` + +## Documentation + +- For more detailed information and usage, refer to the [refine data provider documentation](https://refine.dev/docs/core/providers/data-provider). +- [Refer to Refine rest docs.](https://refine.dev/docs/packages/documentation/data-providers/rest/). +- [Refer to documentation for more info about refine](https://refine.dev/docs/). +- [Step up to refine tutorials](https://refine.dev/docs/tutorial/introduction/index/). diff --git a/packages/rest/package.json b/packages/rest/package.json new file mode 100644 index 000000000000..0e4e1f8fa104 --- /dev/null +++ b/packages/rest/package.json @@ -0,0 +1,114 @@ +{ + "name": "@refinedev/rest", + "version": "2.0.0", + "private": false, + "description": "REST Data Provider for Refine, enabling seamless REST API interactions.", + "repository": { + "type": "git", + "url": "https://github.com/refinedev/refine.git", + "directory": "packages/rest" + }, + "license": "MIT", + "author": "refine", + "exports": { + ".": { + "import": { + "types": "./dist/index.d.mts", + "default": "./dist/index.mjs" + }, + "require": { + "types": "./dist/index.d.cts", + "default": "./dist/index.cjs" + } + }, + "./nestjsx-crud": { + "import": { + "types": "./dist/data-providers/nestjsx-crud/index.d.mts", + "default": "./dist/nestjsx-crud.mjs" + }, + "require": { + "types": "./dist/data-providers/nestjsx-crud/index.d.cts", + "default": "./dist/nestjsx-crud.cjs" + } + }, + "./simple-rest": { + "import": { + "types": "./dist/data-providers/simple-rest/index.d.mts", + "default": "./dist/simple-rest.mjs" + }, + "require": { + "types": "./dist/data-providers/simple-rest/index.d.cts", + "default": "./dist/simple-rest.cjs" + } + }, + "./strapi-v4": { + "import": { + "types": "./dist/data-providers/strapi-v4/index.d.mts", + "default": "./dist/strapi-v4.mjs" + }, + "require": { + "types": "./dist/data-providers/strapi-v4/index.d.cts", + "default": "./dist/strapi-v4.cjs" + } + } + }, + "main": "dist/index.cjs", + "module": "dist/index.mjs", + "typesVersions": { + "*": { + ".": [ + "dist/index.d.ts" + ], + "nestjsx-crud": [ + "dist/data-providers/nestjsx-crud/index.d.ts" + ], + "simple-rest": [ + "dist/data-providers/simple-rest/index.d.ts" + ], + "strapi-v4": [ + "dist/data-providers/strapi-v4/index.d.ts" + ] + } + }, + "typings": "dist/index.d.ts", + "scripts": { + "attw": "attw --pack .", + "build": "tsup && node ../shared/generate-declarations.js", + "dev": "tsup --watch", + "prepare": "pnpm build", + "publint": "publint --strict=true --level=suggestion", + "test": "vitest run", + "test:ui": "vitest --ui", + "test:watch": "vitest", + "types": "node ../shared/generate-declarations.js" + }, + "dependencies": { + "deepmerge": "^4.3.1", + "ky": "^1.10.0", + "qs": "^6.10.1" + }, + "devDependencies": { + "@esbuild-plugins/node-resolve": "^0.1.4", + "@types/node": "^20", + "@vitest/ui": "^2.1.8", + "nock": "^14.0.5", + "tsup": "^6.7.0", + "typescript": "^5.8.3", + "vitest": "^2.1.8" + }, + "peerDependencies": { + "@nestjsx/crud-request": "^5.0.0-alpha.3", + "@refinedev/core": "^5.0.0" + }, + "peerDependenciesMeta": { + "@nestjsx/crud-request": { + "optional": true + } + }, + "engines": { + "node": ">=20" + }, + "publishConfig": { + "access": "public" + } +} diff --git a/packages/rest/refine.config.js b/packages/rest/refine.config.js new file mode 100644 index 000000000000..d09a54867ce2 --- /dev/null +++ b/packages/rest/refine.config.js @@ -0,0 +1,111 @@ +/** @type {import('@refinedev/cli').RefineConfig} */ +module.exports = { + group: "Rest Data Provider", + swizzle: { + items: [ + { + group: "Default Options", + label: "Data Provider Default Options", + requiredPackages: ["deepmerge@4.3.1", "ky@1.10.0", "qs@6.10.1"], + files: [ + { + src: "./src/default.options.ts", + dest: "./providers/default.options.ts", + }, + ], + message: ` + **\`Usage\`** + + \`\`\` + // title: providers/data.ts + import { createDataProvider } from "@refinedev/rest"; + import { defaultOptions } from "./default.options"; + + export const MyDataProvider = createDataProvider( + 'https://example.com', + defaultOptions, + ); + \`\`\` + `, + }, + { + group: "Hooks", + label: "Auth Header Hook", + requiredPackages: ["deepmerge@4.3.1", "ky@1.10.0", "qs@6.10.1"], + files: [ + { + src: "./src/hooks/auth-header.before-request.hook.ts", + dest: "./providers/hooks/auth-header.before-request.hook.ts", + }, + ], + message: ` + **\`Usage\`** + + \`\`\` + // title: providers/data.ts + import { createDataProvider } from "@refinedev/rest"; + import { defaultOptions } from "./default.options"; + import { authHeaderBeforeRequestHook } from "./hooks/auth-header.before-request.hook.ts + + const ACCESS_TOKEN_KEY = 'access-token' + + export const MyDataProvider = createDataProvider( + 'https://example.com', + defaultOptions, + { + hooks: { + beforeRequest: [ + authHeaderBeforeRequestHook({ ACCESS_TOKEN_KEY }) + ] + } + } + ); + \`\`\` + `, + }, + { + group: "Hooks", + label: "Refresh Token Hook", + requiredPackages: ["deepmerge@4.3.1", "ky@1.10.0", "qs@6.10.1"], + files: [ + { + src: "./src/hooks/refresh-token.after-response.hook.ts", + dest: "./providers/hooks/refresh-token.after-response.hook.ts", + }, + ], + message: ` + **\`Usage\`** + + \`\`\` + // title: providers/data.ts + import { createDataProvider } from "@refinedev/rest"; + import { defaultOptions } from "./default.options"; + import { refreshTokenAfterResponseHook } from "./hooks/auth-header.before-request.hook.ts + + const ACCESS_TOKEN_KEY = 'access-token' + const REFRESH_TOKEN_KEY = 'refresh-token' + const REFRESH_TOKEN_URL = 'https://example.com/refresh-token' + + export const MyDataProvider = createDataProvider( + 'https://example.com', + defaultOptions, + { + hooks: { + afterResponse: [ + refreshTokenAfterResponseHook( + { + ACCESS_TOKEN_KEY, + REFRESH_TOKEN_KEY, + REFRESH_TOKEN_URL, + } + ) + ] + } + } + ); + \`\`\` + `, + }, + ], + }, +}; diff --git a/packages/rest/src/create-data-provider.ts b/packages/rest/src/create-data-provider.ts new file mode 100644 index 000000000000..f6be49673c1d --- /dev/null +++ b/packages/rest/src/create-data-provider.ts @@ -0,0 +1,323 @@ +import type { + CreateManyResponse, + CreateResponse, + CustomResponse, + DataProvider, + DeleteOneResponse, + GetOneResponse, + UpdateManyResponse, + UpdateResponse, +} from "@refinedev/core"; +import dm from "deepmerge"; +import kyBase, { type Options as KyOptions } from "ky"; +import qs from "qs"; + +import type { AnyObject, CreateDataProviderOptions } from "./types"; +import { defaultCreateDataProviderOptions } from "./default.options"; + +type CreateDataProvider = { + kyInstance: typeof kyBase; + dataProvider: DataProvider; +}; + +export const createDataProvider = ( + apiURL: string, + baseOptions: CreateDataProviderOptions = defaultCreateDataProviderOptions, + kyOptions: KyOptions = {}, +): CreateDataProvider => { + const options = dm(defaultCreateDataProviderOptions, baseOptions); + + const ky = kyBase.create({ + prefixUrl: apiURL, + ...kyOptions, + headers: { + "Content-Type": "application/json", + Accept: "application/json", + ...kyOptions.headers, + }, + throwHttpErrors: false, + }); + + return { + kyInstance: ky, + dataProvider: { + getList: async (params) => { + const endpoint = options.getList.getEndpoint(params); + + const headers = await options.getList.buildHeaders(params); + + const query = await options.getList.buildQueryParams(params); + + const response = await ky(endpoint, { + headers, + searchParams: qs.stringify(query, { encodeValuesOnly: true }), + }); + + const data = await options.getList.mapResponse( + response.clone(), + params, + ); + + const total = await options.getList.getTotalCount( + response.clone(), + params, + ); + + return { data, total }; + }, + getOne: async (params): Promise> => { + const endpoint = options.getOne.getEndpoint(params); + + const headers = await options.getOne.buildHeaders(params); + + const query = await options.getOne.buildQueryParams(params); + + const response = await ky(endpoint, { + headers, + searchParams: qs.stringify(query, { encodeValuesOnly: true }), + }); + + const data = await options.getOne.mapResponse(response, params); + + return { data }; + }, + async getMany(params) { + const endpoint = options.getMany.getEndpoint(params); + + const headers = await options.getMany.buildHeaders(params); + + const query = await options.getMany.buildQueryParams(params); + + const response = await ky(endpoint, { + headers, + searchParams: qs.stringify(query, { encodeValuesOnly: true }), + }); + + const data = await options.getMany.mapResponse(response, params); + + return { data }; + }, + create: async (params): Promise> => { + const endpoint = options.create.getEndpoint(params); + + const headers = await options.create.buildHeaders(params); + + const query = await options.create.buildQueryParams(params); + + const body = await options.create.buildBodyParams(params); + + const response = await ky(endpoint, { + method: "post", + headers, + searchParams: qs.stringify(query, { encodeValuesOnly: true }), + body: JSON.stringify(body), + }); + + if (response.ok) { + const data = await options.create.mapResponse(response, params); + + return { data }; + } + + const error = await options.create.transformError(response, params); + + throw error; + }, + createMany: options.createMany + ? async (params): Promise> => { + const endpoint = + options.createMany.getEndpoint?.(params) ?? params.resource; + + const headers = + (await options.createMany.buildHeaders?.(params)) ?? {}; + + const query = + (await options.createMany.buildQueryParams?.(params)) ?? {}; + + const body = await options.createMany.buildBodyParams(params); + + const response = await ky(endpoint, { + method: "post", + headers, + searchParams: qs.stringify(query, { encodeValuesOnly: true }), + body: JSON.stringify(body), + }); + + if (response.ok) { + const data = await options.createMany.mapResponse( + response, + params, + ); + + return { data }; + } + + let error; + + if (options.createMany.transformError) { + error = await options.createMany?.transformError( + response, + params, + ); + } else { + error = await response.json(); + } + + throw error; + } + : undefined, + update: async (params): Promise> => { + const endpoint = options.update.getEndpoint(params); + + // TODO: Validate method name. + const method = options.update.getRequestMethod(params); + + const headers = await options.update.buildHeaders(params); + + const query = await options.update.buildQueryParams(params); + + const body = await options.update.buildBodyParams(params); + + const response = await ky(endpoint, { + method, + headers, + searchParams: qs.stringify(query, { encodeValuesOnly: true }), + body: JSON.stringify(body), + }); + + if (response.ok) { + const data = await options.update.mapResponse(response, params); + + return { data }; + } + + const error = await options.update.transformError(response, params); + + throw error; + }, + updateMany: options.updateMany + ? async (params): Promise> => { + const endpoint = options.updateMany.getEndpoint(params); + + const method = + options.updateMany.getRequestMethod?.(params) ?? "patch"; + + const headers = + (await options.updateMany.buildHeaders?.(params)) ?? {}; + + const query = + (await options.updateMany.buildQueryParams?.(params)) ?? {}; + + const body = await options.updateMany.buildBodyParams(params); + + const response = await ky(endpoint, { + method, + headers, + searchParams: qs.stringify(query, { encodeValuesOnly: true }), + body: JSON.stringify(body), + }); + + if (response.ok) { + const data = await options.updateMany.mapResponse( + response, + params, + ); + + return { data }; + } + + let error; + + if (options.updateMany.transformError) { + error = await options.updateMany.transformError(response, params); + } else { + error = await response.json(); + } + + throw error; + } + : undefined, + deleteOne: async (params): Promise> => { + const endpoint = options.deleteOne.getEndpoint(params); + + const headers = await options.deleteOne.buildHeaders(params); + + const query = await options.deleteOne.buildQueryParams(params); + + const response = await ky(endpoint, { + method: "delete", + headers, + searchParams: qs.stringify(query, { encodeValuesOnly: true }), + }); + + const data = await options.deleteOne.mapResponse(response, params); + + return { data }; + }, + deleteMany: options.deleteMany + ? async (params): Promise> => { + const endpoint = + options.deleteMany.getEndpoint?.(params) ?? params.resource; + + const headers = + (await options.deleteMany.buildHeaders?.(params)) ?? {}; + + const query = + (await options.deleteMany.buildQueryParams?.(params)) ?? {}; + + const response = await ky(endpoint, { + method: "delete", + headers, + searchParams: qs.stringify(query, { encodeValuesOnly: true }), + }); + + if (options.deleteMany.mapResponse) { + const data = await options.deleteMany.mapResponse( + response, + params, + ); + + return { data }; + } + + return { data: undefined }; + } + : undefined, + custom: async (params): Promise> => { + const { method, url } = params; + + let client = kyBase.create({ + method, + ...kyOptions, + }); + + const headers = await options.custom.buildHeaders(params); + if (headers) { + client = client.extend({ headers }); + } + + const query = await options.custom.buildQueryParams(params); + if (query) { + client = client.extend({ + searchParams: qs.stringify(query, { encodeValuesOnly: true }), + }); + } + + if (["post", "put", "patch"].includes(method)) { + const body = await options.custom.buildBodyParams(params); + + if (body) { + client = client.extend({ body: JSON.stringify(body) }); + } + } + + const response = await client(url); + + const data = await options.custom.mapResponse(response, params); + + return { data }; + }, + getApiUrl: () => apiURL, + }, + }; +}; diff --git a/packages/rest/src/data-providers/index.ts b/packages/rest/src/data-providers/index.ts new file mode 100644 index 000000000000..65bd7b46c06b --- /dev/null +++ b/packages/rest/src/data-providers/index.ts @@ -0,0 +1,3 @@ +export { createNestjsxCrudDataProvider } from "./nestjsx-crud/index.js"; +export { createSimpleRestDataProvider } from "./simple-rest/index.js"; +export { createStrapiV4DataProvider } from "./strapi-v4/index.js"; diff --git a/packages/rest/src/data-providers/nestjsx-crud/index.ts b/packages/rest/src/data-providers/nestjsx-crud/index.ts new file mode 100644 index 000000000000..d86631a47112 --- /dev/null +++ b/packages/rest/src/data-providers/nestjsx-crud/index.ts @@ -0,0 +1,17 @@ +import type { Options as KyOptions } from "ky"; + +import { createDataProvider } from "../../create-data-provider"; +import { nestjsxCrudDataProviderOptions } from "./nestjsx-crud.options"; + +type CreateNestjsxCrudDataProviderParams = { + apiURL: string; + kyOptions?: KyOptions; +}; + +export const createNestjsxCrudDataProvider = ( + params: CreateNestjsxCrudDataProviderParams, +) => { + const { apiURL, kyOptions } = params; + + return createDataProvider(apiURL, nestjsxCrudDataProviderOptions, kyOptions); +}; diff --git a/packages/rest/src/data-providers/nestjsx-crud/nestjsx-crud.options.ts b/packages/rest/src/data-providers/nestjsx-crud/nestjsx-crud.options.ts new file mode 100644 index 000000000000..f56c15421000 --- /dev/null +++ b/packages/rest/src/data-providers/nestjsx-crud/nestjsx-crud.options.ts @@ -0,0 +1,115 @@ +import { CondOperator, RequestQueryBuilder } from "@nestjsx/crud-request"; +import type { BaseRecord } from "@refinedev/core"; + +import { + handleFilter, + handleJoin, + handlePagination, + handleSort, + transformHttpError, +} from "./utils"; +import type { CreateDataProviderOptions } from "../../types"; + +export const nestjsxCrudDataProviderOptions: CreateDataProviderOptions = { + getList: { + getEndpoint: (params) => params.resource, + buildQueryParams: async (params) => { + let query = RequestQueryBuilder.create(); + + query = handleFilter(query, params.filters); + query = handleJoin(query, params.meta?.join); + query = handlePagination(query, params.pagination); + query = handleSort(query, params.sorters); + + return query.queryObject; + }, + mapResponse: async (response, params) => { + const body = await response.json(); + + if (Array.isArray(body)) { + return body; + } + + return body.data; + }, + getTotalCount: async (response, params) => { + const body = await response.json(); + + if (Array.isArray(body)) { + return body.length; + } + + return body.total; + }, + }, + getMany: { + getEndpoint: (params) => params.resource, + buildQueryParams: async ({ ids, meta }) => { + let query = RequestQueryBuilder.create().setFilter({ + field: "id", + operator: CondOperator.IN, + value: ids, + }); + + query = handleJoin(query, meta?.join); + + return query.queryObject; + }, + mapResponse: async (response, _params) => await response.json(), + }, + create: { + getEndpoint: (params) => params.resource, + buildBodyParams: ({ variables }) => variables, + mapResponse: async (response) => await response.json(), + transformError: async (response, _params) => { + const error = await response.json(); + + return transformHttpError(error); + }, + }, + update: { + getEndpoint: ({ resource, id }) => `${resource}/${id}`, + buildBodyParams: ({ variables }) => variables, + mapResponse: async (response) => await response.json(), + transformError: async (response, _params) => { + const error = await response.json(); + + return transformHttpError(error); + }, + }, + getOne: { + getEndpoint: ({ resource, id }) => `${resource}/${id}`, + buildQueryParams: async ({ meta }) => { + let query = RequestQueryBuilder.create(); + + query = handleJoin(query, meta?.join); + + return query.queryObject; + }, + mapResponse: async (response, _params) => await response.json(), + }, + deleteOne: { + getEndpoint: ({ resource, id }) => `${resource}/${id}`, + mapResponse: async (_response, _params) => undefined, + }, + custom: { + buildQueryParams: async (params) => { + let query = RequestQueryBuilder.create(); + + query = handleFilter(query, params.filters); + query = handleJoin(query, params.meta?.join); + query = handleSort(query, params.sorters); + + return query.queryObject; + }, + buildHeaders: async (params) => { + return params.meta?.headers ?? {}; + }, + buildBodyParams: async (params) => { + return params.payload ?? {}; + }, + mapResponse: async (response, _params) => { + return await response.json(); + }, + }, +}; diff --git a/packages/rest/src/data-providers/nestjsx-crud/specs/create/index.mock.ts b/packages/rest/src/data-providers/nestjsx-crud/specs/create/index.mock.ts new file mode 100644 index 000000000000..33fe5ea6ffd9 --- /dev/null +++ b/packages/rest/src/data-providers/nestjsx-crud/specs/create/index.mock.ts @@ -0,0 +1,54 @@ +import nock from "nock"; + +nock("https://api.nestjsx-crud.refine.dev:443", { encodedQueryParams: true }) + .post("/posts", { + title: "foo", + content: "bar", + status: "active", + user: { id: "6b67da31-2f7e-44ac-936e-18f5766252fc" }, + category: { id: "7f198377-e367-43ce-9932-d2e2572767e5" }, + }) + .reply( + 201, + { + id: "fea25f72-91c9-4354-9985-e1d2140c3818", + title: "foo", + content: "bar", + slug: "foo", + status: "active", + images: null, + createdAt: "2021-04-06T07:11:51.200Z", + updatedAt: "2021-04-06T07:11:51.200Z", + category: { + id: "7f198377-e367-43ce-9932-d2e2572767e5", + title: "Indexing Bypassing Licensed Soft Soap", + createdAt: "2021-04-05T17:21:02.712Z", + updatedAt: "2021-04-05T17:21:02.712Z", + }, + user: { + id: "6b67da31-2f7e-44ac-936e-18f5766252fc", + firstName: "Jamey", + lastName: "Legros", + email: "jamey.Legros@gmail.com", + status: true, + createdAt: "2021-04-05T17:21:02.630Z", + updatedAt: "2021-04-05T17:21:02.630Z", + }, + }, + [ + "Server", + "nginx/1.17.10", + "Date", + "Tue, 06 Apr 2021 07:11:51 GMT", + "Content-Type", + "application/json; charset=utf-8", + "Content-Length", + "599", + "Connection", + "close", + "X-Powered-By", + "Express", + "ETag", + 'W/"257-8HUvc54NAFm1ZAPjDuWf2qwt37I"', + ], + ); diff --git a/packages/rest/src/data-providers/nestjsx-crud/specs/create/index.spec.ts b/packages/rest/src/data-providers/nestjsx-crud/specs/create/index.spec.ts new file mode 100644 index 000000000000..c1bcf24bf1ea --- /dev/null +++ b/packages/rest/src/data-providers/nestjsx-crud/specs/create/index.spec.ts @@ -0,0 +1,26 @@ +import "./index.mock"; +import { nestjsxCrudDataProvider } from ".."; + +describe("create", () => { + it("correct response", async () => { + const { data } = await nestjsxCrudDataProvider.create({ + resource: "posts", + variables: { + title: "foo", + content: "bar", + status: "active", + user: { + id: "6b67da31-2f7e-44ac-936e-18f5766252fc", + }, + category: { + id: "7f198377-e367-43ce-9932-d2e2572767e5", + }, + }, + }); + + expect(data["title"]).toBe("foo"); + expect(data["content"]).toBe("bar"); + expect(data["user"]["id"]).toBe("6b67da31-2f7e-44ac-936e-18f5766252fc"); + expect(data["category"]["id"]).toBe("7f198377-e367-43ce-9932-d2e2572767e5"); + }); +}); diff --git a/packages/rest/src/data-providers/nestjsx-crud/specs/custom/index.mock.ts b/packages/rest/src/data-providers/nestjsx-crud/specs/custom/index.mock.ts new file mode 100644 index 000000000000..968e713483ec --- /dev/null +++ b/packages/rest/src/data-providers/nestjsx-crud/specs/custom/index.mock.ts @@ -0,0 +1,369 @@ +import nock from "nock"; + +nock("https://api.nestjsx-crud.refine.dev:443", { encodedQueryParams: true }) + .get("/users") + .query({}) + .reply( + 200, + [ + { + id: "35a97005-ffe9-4867-9108-58c00d8ebfa8", + firstName: "Nolan", + lastName: "Gottlieb", + email: "nolan85@hotmail.com", + status: true, + createdAt: "2021-06-21T12:16:36.033Z", + updatedAt: "2021-06-21T12:16:36.033Z", + }, + { + id: "fbb0df89-4474-46f4-97ae-ba97b1ac549a", + firstName: "Bart", + lastName: "Roberts", + email: "bart_Roberts58@hotmail.com", + status: true, + createdAt: "2021-06-21T12:16:36.098Z", + updatedAt: "2021-06-21T12:16:36.098Z", + }, + { + id: "5d12e4fc-2fc9-4323-ac93-f93b3f308b9a", + firstName: "Bobbie", + lastName: "Crooks", + email: "bobbie.Crooks32@yahoo.com", + status: true, + createdAt: "2021-06-21T12:16:36.205Z", + updatedAt: "2021-06-21T12:16:36.205Z", + }, + { + id: "0cad0ad8-e0d0-4bb0-b94c-3179e0ea7a4e", + firstName: "Aaron", + lastName: "Koss", + email: "aaron64@hotmail.com", + status: true, + createdAt: "2021-06-21T12:16:36.329Z", + updatedAt: "2021-06-21T12:16:36.329Z", + }, + { + id: "af414ac3-51d9-42b6-8ee7-040df85aba41", + firstName: "Hardy", + lastName: "Wolf", + email: "hardy.Wolf23@yahoo.com", + status: true, + createdAt: "2021-06-21T12:16:36.495Z", + updatedAt: "2021-06-21T12:16:36.495Z", + }, + { + id: "98677991-daba-40ee-91c8-2369942e415f", + firstName: "Katlynn", + lastName: "Casper", + email: "katlynn_Casper5@gmail.com", + status: true, + createdAt: "2021-06-21T12:16:36.723Z", + updatedAt: "2021-06-21T12:16:36.723Z", + }, + { + id: "20068b8c-4227-41d1-a8bf-d53e2d7010b4", + firstName: "Paula", + lastName: "Barton", + email: "paula_Barton@yahoo.com", + status: true, + createdAt: "2021-06-21T12:16:36.982Z", + updatedAt: "2021-06-21T12:16:36.982Z", + }, + { + id: "17cd0be0-84ce-467f-8cb4-5fcd51b34f93", + firstName: "Bertram", + lastName: "Littel", + email: "bertram.Littel12@yahoo.com", + status: true, + createdAt: "2021-06-21T12:16:37.327Z", + updatedAt: "2021-06-21T12:16:37.327Z", + }, + { + id: "bb731ec1-18bd-49e4-9193-57717fb3f102", + firstName: "Austyn", + lastName: "Lynch", + email: "austyn.Lynch96@gmail.com", + status: true, + createdAt: "2021-06-21T12:16:37.840Z", + updatedAt: "2021-06-21T12:16:37.840Z", + }, + { + id: "8864302b-172b-4d49-8dab-b04b3cba119d", + firstName: "Fern", + lastName: "Farrell", + email: "fern_Farrell@gmail.com", + status: true, + createdAt: "2021-06-21T12:16:38.312Z", + updatedAt: "2021-06-21T12:16:38.312Z", + }, + { + id: "0396774e-c41f-41a7-98db-39e0a709ef72", + firstName: "Margot", + lastName: "Ritchie", + email: "margot.Ritchie@yahoo.com", + status: false, + createdAt: "2021-06-21T12:16:38.974Z", + updatedAt: "2021-06-21T12:16:38.974Z", + }, + { + id: "420c5ea4-e493-4c1c-b0e3-ed4c8880bb7c", + firstName: "Makenna", + lastName: "Gislason", + email: "makenna41@gmail.com", + status: false, + createdAt: "2021-06-21T12:16:38.979Z", + updatedAt: "2021-06-21T12:16:38.979Z", + }, + { + id: "b83f5d5a-a0ac-4178-943c-35e654841ffb", + firstName: "Christopher", + lastName: "Schoen", + email: "christopher.Schoen56@hotmail.com", + status: false, + createdAt: "2021-06-21T12:16:38.990Z", + updatedAt: "2021-06-21T12:16:38.990Z", + }, + ], + [ + "Server", + "nginx/1.17.10", + "Date", + "Mon, 21 Jun 2021 12:17:11 GMT", + "Content-Type", + "application/json; charset=utf-8", + "Content-Length", + "2781", + "Connection", + "close", + "Vary", + "Accept-Encoding", + "X-Powered-By", + "Express", + "Access-Control-Allow-Origin", + "*", + "ETag", + 'W/"add-DvD0TqRb+HHPTGvK1zGsceqqHJw"', + ], + ); + +nock("https://api.nestjsx-crud.refine.dev:443", { encodedQueryParams: true }) + .get("/users") + .query({ + s: "%7B%22%24and%22%3A%5B%7B%22email%22%3A%7B%22%24eq%22%3A%22nolan85%40hotmail.com%22%7D%7D%5D%7D", + }) + .reply( + 200, + [ + { + id: "35a97005-ffe9-4867-9108-58c00d8ebfa8", + firstName: "Nolan", + lastName: "Gottlieb", + email: "nolan85@hotmail.com", + status: true, + createdAt: "2021-06-21T12:16:36.033Z", + updatedAt: "2021-06-21T12:16:36.033Z", + }, + ], + [ + "Server", + "nginx/1.17.10", + "Date", + "Mon, 21 Jun 2021 12:18:12 GMT", + "Content-Type", + "application/json; charset=utf-8", + "Content-Length", + "211", + "Connection", + "close", + "X-Powered-By", + "Express", + "Access-Control-Allow-Origin", + "*", + "ETag", + 'W/"d3-/cn/pgneUnCIQNP0lCSdBnxNS7c"', + ], + ); + +nock("https://api.nestjsx-crud.refine.dev:443", { encodedQueryParams: true }) + .get("/users") + .query({ "sort%5B0%5D": "id%2CASC" }) + .reply( + 200, + [ + { + id: "0396774e-c41f-41a7-98db-39e0a709ef72", + firstName: "Margot", + lastName: "Ritchie", + email: "margot.Ritchie@yahoo.com", + status: false, + createdAt: "2021-06-21T12:16:38.974Z", + updatedAt: "2021-06-21T12:16:38.974Z", + }, + { + id: "0cad0ad8-e0d0-4bb0-b94c-3179e0ea7a4e", + firstName: "Aaron", + lastName: "Koss", + email: "aaron64@hotmail.com", + status: true, + createdAt: "2021-06-21T12:16:36.329Z", + updatedAt: "2021-06-21T12:16:36.329Z", + }, + { + id: "17cd0be0-84ce-467f-8cb4-5fcd51b34f93", + firstName: "Bertram", + lastName: "Littel", + email: "bertram.Littel12@yahoo.com", + status: true, + createdAt: "2021-06-21T12:16:37.327Z", + updatedAt: "2021-06-21T12:16:37.327Z", + }, + { + id: "20068b8c-4227-41d1-a8bf-d53e2d7010b4", + firstName: "Paula", + lastName: "Barton", + email: "paula_Barton@yahoo.com", + status: true, + createdAt: "2021-06-21T12:16:36.982Z", + updatedAt: "2021-06-21T12:16:36.982Z", + }, + { + id: "35a97005-ffe9-4867-9108-58c00d8ebfa8", + firstName: "Nolan", + lastName: "Gottlieb", + email: "nolan85@hotmail.com", + status: true, + createdAt: "2021-06-21T12:16:36.033Z", + updatedAt: "2021-06-21T12:16:36.033Z", + }, + { + id: "420c5ea4-e493-4c1c-b0e3-ed4c8880bb7c", + firstName: "Makenna", + lastName: "Gislason", + email: "makenna41@gmail.com", + status: false, + createdAt: "2021-06-21T12:16:38.979Z", + updatedAt: "2021-06-21T12:16:38.979Z", + }, + { + id: "5d12e4fc-2fc9-4323-ac93-f93b3f308b9a", + firstName: "Bobbie", + lastName: "Crooks", + email: "bobbie.Crooks32@yahoo.com", + status: true, + createdAt: "2021-06-21T12:16:36.205Z", + updatedAt: "2021-06-21T12:16:36.205Z", + }, + { + id: "8864302b-172b-4d49-8dab-b04b3cba119d", + firstName: "Fern", + lastName: "Farrell", + email: "fern_Farrell@gmail.com", + status: true, + createdAt: "2021-06-21T12:16:38.312Z", + updatedAt: "2021-06-21T12:16:38.312Z", + }, + { + id: "98677991-daba-40ee-91c8-2369942e415f", + firstName: "Katlynn", + lastName: "Casper", + email: "katlynn_Casper5@gmail.com", + status: true, + createdAt: "2021-06-21T12:16:36.723Z", + updatedAt: "2021-06-21T12:16:36.723Z", + }, + { + id: "af414ac3-51d9-42b6-8ee7-040df85aba41", + firstName: "Hardy", + lastName: "Wolf", + email: "hardy.Wolf23@yahoo.com", + status: true, + createdAt: "2021-06-21T12:16:36.495Z", + updatedAt: "2021-06-21T12:16:36.495Z", + }, + { + id: "b83f5d5a-a0ac-4178-943c-35e654841ffb", + firstName: "Christopher", + lastName: "Schoen", + email: "christopher.Schoen56@hotmail.com", + status: false, + createdAt: "2021-06-21T12:16:38.990Z", + updatedAt: "2021-06-21T12:16:38.990Z", + }, + { + id: "bb731ec1-18bd-49e4-9193-57717fb3f102", + firstName: "Austyn", + lastName: "Lynch", + email: "austyn.Lynch96@gmail.com", + status: true, + createdAt: "2021-06-21T12:16:37.840Z", + updatedAt: "2021-06-21T12:16:37.840Z", + }, + { + id: "fbb0df89-4474-46f4-97ae-ba97b1ac549a", + firstName: "Bart", + lastName: "Roberts", + email: "bart_Roberts58@hotmail.com", + status: true, + createdAt: "2021-06-21T12:16:36.098Z", + updatedAt: "2021-06-21T12:16:36.098Z", + }, + ], + [ + "Server", + "nginx/1.17.10", + "Date", + "Mon, 21 Jun 2021 12:19:07 GMT", + "Content-Type", + "application/json; charset=utf-8", + "Content-Length", + "2781", + "Connection", + "close", + "Vary", + "Accept-Encoding", + "X-Powered-By", + "Express", + "Access-Control-Allow-Origin", + "*", + "ETag", + 'W/"add-KYjyf3xNiYvtZqYvamM9UkdSUtA"', + ], + ); + +nock("https://api.nestjsx-crud.refine.dev:443", { encodedQueryParams: true }) + .post("/users", { + firstName: "test", + lastName: "test", + email: "test@mail.com", + status: true, + }) + .reply( + 201, + { + id: "44e3c8e9-d95a-4423-bf9a-c40407e059af", + firstName: "test", + lastName: "test", + email: "test@mail.com", + status: true, + createdAt: "2021-06-21T12:20:05.530Z", + updatedAt: "2021-06-21T12:20:05.530Z", + }, + [ + "Server", + "nginx/1.17.10", + "Date", + "Mon, 21 Jun 2021 12:20:05 GMT", + "Content-Type", + "application/json; charset=utf-8", + "Content-Length", + "198", + "Connection", + "close", + "X-Powered-By", + "Express", + "Access-Control-Allow-Origin", + "*", + "ETag", + 'W/"c6-4unCtz5BG8f5pEq8IfSwJznEo+w"', + ], + ); diff --git a/packages/rest/src/data-providers/nestjsx-crud/specs/custom/index.spec.ts b/packages/rest/src/data-providers/nestjsx-crud/specs/custom/index.spec.ts new file mode 100644 index 000000000000..315a9a537d7e --- /dev/null +++ b/packages/rest/src/data-providers/nestjsx-crud/specs/custom/index.spec.ts @@ -0,0 +1,73 @@ +import { nestjsxCrudDataProvider } from ".."; +import "./index.mock"; + +describe("custom", () => { + const API_URL = "https://api.nestjsx-crud.refine.dev"; + + it("correct get response", async () => { + const response = await nestjsxCrudDataProvider.custom!({ + url: `${API_URL}/users`, + method: "get", + }); + + expect(response.data[0]["id"]).toBe("35a97005-ffe9-4867-9108-58c00d8ebfa8"); + expect(response.data[0]["email"]).toBe("nolan85@hotmail.com"); + }); + + it("correct filter response", async () => { + const response = await nestjsxCrudDataProvider.custom!({ + url: `${API_URL}/users`, + method: "get", + filters: [ + { + field: "email", + operator: "eq", + value: "nolan85@hotmail.com", + }, + ], + }); + + expect(response.data[0]["id"]).toBe("35a97005-ffe9-4867-9108-58c00d8ebfa8"); + expect(response.data[0]["email"]).toBe("nolan85@hotmail.com"); + }); + + it("correct sort response", async () => { + const response = await nestjsxCrudDataProvider.custom!({ + url: `${API_URL}/users`, + method: "get", + sorters: [ + { + field: "id", + order: "asc", + }, + ], + }); + + expect(response.data[0]["id"]).toBe("0396774e-c41f-41a7-98db-39e0a709ef72"); + + expect(response.data[0]["email"]).toBe("margot.Ritchie@yahoo.com"); + }); + + it("correct post request", async () => { + const response = await nestjsxCrudDataProvider.custom!({ + url: `${API_URL}/users`, + method: "post", + payload: { + firstName: "test", + lastName: "test", + email: "test@mail.com", + status: true, + }, + }); + + expect(response.data).toEqual({ + id: "44e3c8e9-d95a-4423-bf9a-c40407e059af", + firstName: "test", + lastName: "test", + email: "test@mail.com", + status: true, + createdAt: "2021-06-21T12:20:05.530Z", + updatedAt: "2021-06-21T12:20:05.530Z", + }); + }); +}); diff --git a/packages/rest/src/data-providers/nestjsx-crud/specs/deleteOne/index.mock.ts b/packages/rest/src/data-providers/nestjsx-crud/specs/deleteOne/index.mock.ts new file mode 100644 index 000000000000..0cc48741f61c --- /dev/null +++ b/packages/rest/src/data-providers/nestjsx-crud/specs/deleteOne/index.mock.ts @@ -0,0 +1,16 @@ +import nock from "nock"; + +nock("https://api.nestjsx-crud.refine.dev:443", { encodedQueryParams: true }) + .delete("/posts/99d8ae54-432c-48d4-a385-f0ff4665e448") + .reply(200, "", [ + "Server", + "nginx/1.17.10", + "Date", + "Tue, 06 Apr 2021 07:16:04 GMT", + "Content-Length", + "0", + "Connection", + "close", + "X-Powered-By", + "Express", + ]); diff --git a/packages/rest/src/data-providers/nestjsx-crud/specs/deleteOne/index.spec.ts b/packages/rest/src/data-providers/nestjsx-crud/specs/deleteOne/index.spec.ts new file mode 100644 index 000000000000..8b0777c3b46d --- /dev/null +++ b/packages/rest/src/data-providers/nestjsx-crud/specs/deleteOne/index.spec.ts @@ -0,0 +1,13 @@ +import "./index.mock"; +import { nestjsxCrudDataProvider } from ".."; + +describe("deleteOne", () => { + it("correct response", async () => { + const { data } = await nestjsxCrudDataProvider.deleteOne({ + resource: "posts", + id: "99d8ae54-432c-48d4-a385-f0ff4665e448", + }); + + expect(data).toEqual(undefined); + }); +}); diff --git a/packages/rest/src/data-providers/nestjsx-crud/specs/getList/index.mock.ts b/packages/rest/src/data-providers/nestjsx-crud/specs/getList/index.mock.ts new file mode 100644 index 000000000000..6a135a79628f --- /dev/null +++ b/packages/rest/src/data-providers/nestjsx-crud/specs/getList/index.mock.ts @@ -0,0 +1,2543 @@ +import nock from "nock"; + +nock("https://api.nestjsx-crud.refine.dev:443", { encodedQueryParams: true }) + .get("/posts") + .query({ limit: "10", page: "1", offset: "0" }) + .reply( + 200, + { + data: [ + { + status: "published", + id: "1b175cdc-4407-49d9-82cd-35e9f31afec2", + title: "User-friendly New Mexico Bedfordshire", + content: + "Nobis autem asperiores ut ea architecto dignissimos. Velit id magnam quod corrupti adipisci. Ratione ut saepe rerum omnis dolores perspiciatis sed eos. Recusandae quia animi sint perferendis vero eius sunt commodi ut.\n \rPraesentium ut pariatur voluptatem minima repellendus. Dolor deleniti non cum nostrum accusantium. A deleniti est eveniet cupiditate quo praesentium. Quia sed illo aut voluptas.\n \rIpsum in et voluptatem. Neque qui rerum et quasi sint quis voluptates. Nobis eum dolores ut vel enim officiis. Adipisci accusantium non voluptas eveniet odio eius.\n \rDolore provident pariatur et dignissimos molestiae aut id nisi. Voluptas praesentium aliquid debitis natus sapiente sunt laudantium perferendis. Architecto possimus aut laudantium explicabo quia expedita quasi atque. Ut ut occaecati vel voluptas assumenda incidunt cumque totam.\n \rMollitia facere id. Delectus quo cum et eligendi. Qui non distinctio praesentium nihil tempora ea.\n \rUt accusantium autem occaecati. Eos quae minus autem neque et quis voluptates earum eos. Excepturi veniam dolores laborum porro dolorem dolores omnis ducimus velit. Nobis earum molestias similique. Dolorem sint recusandae ea nihil voluptatem nihil rerum. Autem a fugiat eligendi tempora ut ipsa.\n \rHarum soluta fuga. Esse non praesentium quo rerum velit labore. Et in officia veritatis ipsam qui distinctio. Culpa aut quia explicabo eum et dicta sed quia. In adipisci neque consequatur at.\n \rRerum sed aut nisi et enim ut. Qui at quis dicta omnis quia beatae id. Fugiat ducimus molestiae. Nisi ratione provident. Ipsam tempora cum vel odit assumenda quibusdam debitis.\n \rDoloremque repellendus voluptatem quis. Quo et eos eligendi libero quia tempora illum rerum. Quas eum et accusamus tenetur esse in eum rerum qui. Ratione vero perspiciatis aut. Aut aliquid cum saepe. Voluptatem quo molestiae sapiente voluptas.\n \rEt ut et velit officia sequi omnis placeat. Quia dignissimos a et deleniti tenetur ea. Asperiores et magnam earum quasi. Neque explicabo autem voluptate quasi ut. Similique repellendus optio non accusantium aut assumenda et quas.", + slug: "user-friendly-new-mexico-bedfordshire", + images: [ + { + uid: "rc-upload-an6pptxt4k", + name: "random-image.jpg", + url: "https://picsum.photos/800", + type: "image/jpeg", + size: 141940, + }, + ], + createdAt: "2021-06-21T11:13:32.165Z", + updatedAt: "2021-06-21T11:13:32.249Z", + category: { + id: "07f14be2-72b4-495e-8193-2e4ce70d9be9", + title: "Libyan Dinar Relationships Mexico", + createdAt: "2021-06-21T11:13:32.249Z", + updatedAt: "2021-06-21T11:13:32.249Z", + }, + user: { + id: "ebf27bb0-4e2c-4ea4-9081-1bfb56a966a0", + firstName: "Rosemarie", + lastName: "Schmitt", + email: "rosemarie_Schmitt63@hotmail.com", + status: true, + createdAt: "2021-06-21T11:13:32.073Z", + updatedAt: "2021-06-21T11:13:32.073Z", + }, + tags: [ + { + id: "244d1b9a-450c-44fc-914e-2e00c2493171", + title: "red", + createdAt: "2021-06-21T11:13:32.084Z", + updatedAt: "2021-06-21T11:13:32.084Z", + }, + { + id: "faa9f2ea-2181-4472-aff3-0c7ef9fd9c62", + title: "engineer", + createdAt: "2021-06-21T11:13:32.092Z", + updatedAt: "2021-06-21T11:13:32.092Z", + }, + { + id: "f283715b-54a0-43d1-8668-9b68ebc54ca3", + title: "transmitting", + createdAt: "2021-06-21T11:13:32.097Z", + updatedAt: "2021-06-21T11:13:32.097Z", + }, + ], + }, + { + status: "draft", + id: "15b6c4fe-8069-420a-93a5-06bf91edc448", + title: "Tuna reboot Legacy", + content: + "Vel voluptas maxime est voluptatibus dolor rem. Numquam cum ipsum ut fugiat et sed. Beatae praesentium architecto quia beatae iure explicabo. Minima a culpa a odit praesentium ratione. Est laboriosam qui eligendi culpa sapiente reprehenderit ad.\n \rAlias quae tenetur. Consequatur est saepe odio voluptatum mollitia amet sit omnis. Aut sint hic temporibus modi ab quibusdam maiores laborum. Omnis harum et eum veniam aut vel. Consequatur minus reiciendis hic veritatis officia. Accusantium modi assumenda deleniti quia et voluptatibus.\n \rVoluptas deleniti quia. Et velit quos. Ipsum aut aut repellat dignissimos consequuntur dolorum et nostrum molestias. Adipisci eveniet sed repudiandae sapiente repellat ut deserunt aut recusandae.\n \rNon odio dolor sed minima voluptatibus eaque deleniti dolore. Et harum iusto vel voluptas explicabo facere. Hic officiis magnam numquam qui. Cumque sint voluptatem est. Sint maxime vitae mollitia. Omnis corporis dicta aut culpa.\n \rLibero facere omnis impedit maxime mollitia. Aperiam quae provident eos in. Neque nisi aliquam dignissimos.\n \rRem id culpa consequatur cum impedit explicabo ex autem. Ut dolorem magnam sequi et odit labore recusandae. Suscipit expedita quia dolores doloremque consequatur rem est facere aliquam. Aut laboriosam architecto velit. Dolores consequuntur sed in ipsum sit. Autem numquam et sed non et delectus.\n \rPerferendis fugiat debitis occaecati ut ullam magni beatae exercitationem ex. Excepturi ut exercitationem veniam. Adipisci et vel doloribus perspiciatis saepe. Iure atque enim possimus enim fugiat voluptatibus.\n \rDistinctio et minima nobis. Non eos rerum est necessitatibus dolor at quia. Eligendi officia cum est. Quia et quaerat rerum necessitatibus fugit.\n \rSint suscipit aperiam iure est. Qui harum voluptate quos et optio tempore. Et sint consectetur aut in nobis omnis qui. In modi saepe quo vel.\n \rUllam blanditiis aut. Ipsam ea mollitia cum exercitationem sed suscipit corporis iure. Alias ullam reiciendis voluptatem numquam neque ut. Culpa quisquam est. Quae enim molestias accusamus ratione. Tempora animi perferendis et qui libero dolorum esse architecto sed.", + slug: "tuna-reboot-legacy", + images: [ + { + uid: "rc-upload-5kqsqckl7g", + name: "random-image.jpg", + url: "https://picsum.photos/800", + type: "image/jpeg", + size: 141940, + }, + ], + createdAt: "2021-06-21T11:13:32.177Z", + updatedAt: "2021-06-21T11:13:32.249Z", + category: { + id: "07f14be2-72b4-495e-8193-2e4ce70d9be9", + title: "Libyan Dinar Relationships Mexico", + createdAt: "2021-06-21T11:13:32.249Z", + updatedAt: "2021-06-21T11:13:32.249Z", + }, + user: { + id: "ebf27bb0-4e2c-4ea4-9081-1bfb56a966a0", + firstName: "Rosemarie", + lastName: "Schmitt", + email: "rosemarie_Schmitt63@hotmail.com", + status: true, + createdAt: "2021-06-21T11:13:32.073Z", + updatedAt: "2021-06-21T11:13:32.073Z", + }, + tags: [ + { + id: "244d1b9a-450c-44fc-914e-2e00c2493171", + title: "red", + createdAt: "2021-06-21T11:13:32.084Z", + updatedAt: "2021-06-21T11:13:32.084Z", + }, + { + id: "faa9f2ea-2181-4472-aff3-0c7ef9fd9c62", + title: "engineer", + createdAt: "2021-06-21T11:13:32.092Z", + updatedAt: "2021-06-21T11:13:32.092Z", + }, + { + id: "f283715b-54a0-43d1-8668-9b68ebc54ca3", + title: "transmitting", + createdAt: "2021-06-21T11:13:32.097Z", + updatedAt: "2021-06-21T11:13:32.097Z", + }, + ], + }, + { + status: "draft", + id: "02ff5d56-b616-4b7d-9043-25c49bc988d2", + title: "Moldova bypassing models", + content: + "Labore tempore sapiente earum rerum corrupti libero. Eveniet vel harum aut. Beatae tempora suscipit est expedita. Quisquam sint dolorem. Sed quibusdam voluptatem rerum eaque est quasi.\n \rQuibusdam qui impedit aspernatur voluptas adipisci esse voluptatem quibusdam. Ut ipsum eum. Quae qui quod consectetur. Dolorum consectetur quis accusantium eveniet quo.\n \rFuga enim quos voluptas qui qui voluptatibus est. Quidem non dolores modi ut eum enim. Non quis earum autem vero consequuntur quaerat earum sed deleniti. Similique aut autem et aperiam repellendus cumque ullam maxime.\n \rEt vel ut. Voluptate expedita quia modi est. Consequatur veritatis sed. Quo porro quasi blanditiis magni quos facilis ut.\n \rAliquid et aut. Omnis cum accusantium libero non qui et incidunt sit. Repellendus eaque qui reprehenderit. Id sint dolorum. Eligendi numquam fugit quidem.\n \rExplicabo ut voluptatem nulla omnis quod. Eos vel fuga. Corporis aliquid voluptatum accusamus laborum sapiente tempore dolorem.\n \rEaque repudiandae aliquid. Quaerat ullam aut repellendus ea et similique voluptatem eos accusamus. Velit quasi enim placeat aut qui dolores. Aspernatur sed aut deserunt architecto voluptatem quo. Qui consequatur labore quam molestias blanditiis.\n \rMollitia iure veritatis dolorem adipisci sit. Omnis animi itaque. Quia nostrum optio. Et ipsa autem rerum doloribus. Vel qui ipsa nam ratione et fuga veritatis maiores dignissimos. Non et eligendi laborum quibusdam a.\n \rAutem voluptate tempore sit. Et et voluptatem. Quaerat modi vero sit quia quisquam laudantium architecto et. Rerum vero error ad unde a voluptate temporibus. Officiis molestiae reiciendis unde. Non velit similique.\n \rEsse qui ratione ipsam dicta quaerat voluptas quas eligendi. Aperiam rerum possimus aliquid minus at quis tempora. Non eligendi autem. Sunt rem pariatur dignissimos est accusamus repellendus.", + slug: "moldova-bypassing-models", + images: [ + { + uid: "rc-upload-ns3jz95agc", + name: "random-image.jpg", + url: "https://picsum.photos/800", + type: "image/jpeg", + size: 141940, + }, + ], + createdAt: "2021-06-21T11:13:33.075Z", + updatedAt: "2021-06-21T11:13:33.678Z", + category: { + id: "ad291690-f03c-47c3-af90-4bb75b20def4", + title: "Purple Berkshire Tuna", + createdAt: "2021-06-21T11:13:33.678Z", + updatedAt: "2021-06-21T11:13:33.678Z", + }, + user: { + id: "17291210-242d-4259-b04b-d89d05aa86b7", + firstName: "Griffin", + lastName: "Carroll", + email: "griffin.Carroll91@gmail.com", + status: true, + createdAt: "2021-06-21T11:13:32.992Z", + updatedAt: "2021-06-21T11:13:32.992Z", + }, + tags: [ + { + id: "efb9b9b9-6697-4988-af41-6aa7e10b2030", + title: "auto Loan Account", + createdAt: "2021-06-21T11:13:32.998Z", + updatedAt: "2021-06-21T11:13:32.998Z", + }, + { + id: "ff780da9-2a1b-4030-8ecf-5cb9f520ec34", + title: "brand", + createdAt: "2021-06-21T11:13:33.006Z", + updatedAt: "2021-06-21T11:13:33.006Z", + }, + { + id: "9f84d769-c026-44b0-a946-16dd94178a0f", + title: "boliviano Mvdol", + createdAt: "2021-06-21T11:13:33.016Z", + updatedAt: "2021-06-21T11:13:33.016Z", + }, + ], + }, + { + status: "draft", + id: "0b71db63-f4ec-407b-b00d-9f0c0368d5ae", + title: "Deposit Small discrete", + content: + "Ea saepe quia impedit. Officiis fuga ex ut dolorem consectetur. Aperiam deserunt atque aperiam doloremque est.\n \rRem reprehenderit et id eum vero quia. Et optio in perferendis iure. Soluta aperiam ut veritatis saepe.\n \rIllo laborum dolor provident neque odit aut eligendi. Ipsa neque molestias et rerum. Aut molestiae vel doloribus. Qui maxime possimus exercitationem sint. Laborum dolores praesentium hic et.\n \rId ipsam corrupti cum aperiam ut voluptatem omnis ipsa eveniet. Cum id voluptas. Et exercitationem vel recusandae maiores quisquam commodi provident.\n \rQuo ad expedita facilis maiores qui non. Sapiente quos similique ipsa saepe quibusdam. Nobis quidem libero saepe magnam sed soluta quas dolor. Dolor consequatur cumque et voluptatem a quisquam.\n \rSimilique perferendis quo consequatur saepe et non nulla maiores autem. Voluptas voluptates accusantium illo quam eius quo quaerat. Ut esse voluptas voluptatem aut dolore minima doloremque voluptatem iste. Et aspernatur vel cupiditate quos.\n \rCum accusamus ipsa provident harum. Natus officiis odio. Saepe molestias et fuga cum esse ipsam sit. Qui illum aliquam delectus aut et et. Voluptas eum voluptatem porro odio autem quia. Nulla et aliquid id dolor omnis et natus quia.\n \rDoloribus libero impedit molestiae doloribus laboriosam et animi. Nemo fuga at voluptatem quia et. Minima reiciendis et porro eveniet animi voluptatum ratione impedit dicta. Odio doloribus iste earum odit rerum modi.\n \rEt incidunt molestias labore odit illo sed sit. Voluptates ut exercitationem quis earum eos ipsa ut nostrum nobis. Dicta sint voluptas aliquid consequatur quas rem asperiores ducimus. Placeat earum iusto et rerum. Sunt et alias et magni delectus eos sed veniam repellat.\n \rTotam debitis deleniti repellendus labore praesentium sed occaecati officiis. Quo laborum et quasi voluptas cum omnis minima earum illum. Natus consequatur atque maxime.", + slug: "deposit-small-discrete", + images: [ + { + uid: "rc-upload-s7ga9wpmki", + name: "random-image.jpg", + url: "https://picsum.photos/800", + type: "image/jpeg", + size: 141940, + }, + ], + createdAt: "2021-06-21T11:13:33.092Z", + updatedAt: "2021-06-21T11:13:33.678Z", + category: { + id: "ad291690-f03c-47c3-af90-4bb75b20def4", + title: "Purple Berkshire Tuna", + createdAt: "2021-06-21T11:13:33.678Z", + updatedAt: "2021-06-21T11:13:33.678Z", + }, + user: { + id: "17291210-242d-4259-b04b-d89d05aa86b7", + firstName: "Griffin", + lastName: "Carroll", + email: "griffin.Carroll91@gmail.com", + status: true, + createdAt: "2021-06-21T11:13:32.992Z", + updatedAt: "2021-06-21T11:13:32.992Z", + }, + tags: [ + { + id: "efb9b9b9-6697-4988-af41-6aa7e10b2030", + title: "auto Loan Account", + createdAt: "2021-06-21T11:13:32.998Z", + updatedAt: "2021-06-21T11:13:32.998Z", + }, + { + id: "ff780da9-2a1b-4030-8ecf-5cb9f520ec34", + title: "brand", + createdAt: "2021-06-21T11:13:33.006Z", + updatedAt: "2021-06-21T11:13:33.006Z", + }, + { + id: "9f84d769-c026-44b0-a946-16dd94178a0f", + title: "boliviano Mvdol", + createdAt: "2021-06-21T11:13:33.016Z", + updatedAt: "2021-06-21T11:13:33.016Z", + }, + ], + }, + { + status: "draft", + id: "06bad81a-7712-44db-9d65-3aee7b5439b7", + title: "Turnpike Director copying", + content: + "Accusamus omnis earum illo eligendi ut sit dolore consequatur. Sint est et mollitia optio laborum. Mollitia et sit doloribus in dolorem.\n \rOmnis fuga repellendus asperiores dolor sequi pariatur et. Tempore accusantium explicabo odit dolore accusantium alias. Quis dolorum quia aut sed nam est. Alias totam voluptates saepe autem.\n \rNatus sit mollitia eligendi aliquid velit perspiciatis voluptatem fugit. Et et eveniet non ut unde sit. Cupiditate odit voluptatem ipsa. Doloremque deleniti est sit animi ab dolores ut architecto. Harum temporibus placeat adipisci asperiores optio cupiditate explicabo. Quia consectetur et sed.\n \rAb at aspernatur quo. Aut sed quibusdam quia nulla minus quam eius nihil tenetur. Similique voluptate dicta. Vel necessitatibus et est amet qui et. Omnis voluptate commodi laborum cum nihil sed. Non explicabo voluptates non exercitationem est aut est.\n \rRepudiandae error dolorem accusamus vel consequatur tenetur nam. Eos veniam a id sapiente blanditiis dicta quo. Quasi molestias consequuntur beatae aperiam possimus ullam cumque ut. Sapiente sunt est odit. Optio nihil sed.\n \rDolor cumque totam impedit ea. Sint iusto odit nihil eos consectetur suscipit. Non doloribus quos ducimus tenetur omnis omnis repellendus provident molestias. Fuga omnis pariatur enim.\n \rNostrum iusto et qui voluptatem culpa eius animi voluptatem. Quos molestiae totam fuga. Pariatur aut ut aperiam iste voluptatem tenetur est excepturi. Laboriosam quia nemo facilis voluptatibus. Enim modi consequatur non et voluptates velit. Neque quasi et deserunt id aut incidunt.\n \rSint ut ex. Sit eligendi vero mollitia sequi consequatur. Labore est voluptatum suscipit quod consectetur natus.\n \rQuis quos dolore accusantium. Blanditiis ut est quo. Ea distinctio tempore nulla reiciendis. Exercitationem necessitatibus doloremque cumque accusantium aut quis. Ex ipsam et exercitationem et nulla consequatur maiores. Rem unde maiores reprehenderit ut perferendis aut nemo doloremque.\n \rSuscipit commodi porro qui libero et et reprehenderit vel. Non et molestiae. Est vero reiciendis quia et rerum. Quis cum omnis blanditiis fugit ut architecto.", + slug: "turnpike-director-copying", + images: [ + { + uid: "rc-upload-vttftltant", + name: "random-image.jpg", + url: "https://picsum.photos/800", + type: "image/jpeg", + size: 141940, + }, + ], + createdAt: "2021-06-21T11:13:33.107Z", + updatedAt: "2021-06-21T11:13:33.678Z", + category: { + id: "ad291690-f03c-47c3-af90-4bb75b20def4", + title: "Purple Berkshire Tuna", + createdAt: "2021-06-21T11:13:33.678Z", + updatedAt: "2021-06-21T11:13:33.678Z", + }, + user: { + id: "17291210-242d-4259-b04b-d89d05aa86b7", + firstName: "Griffin", + lastName: "Carroll", + email: "griffin.Carroll91@gmail.com", + status: true, + createdAt: "2021-06-21T11:13:32.992Z", + updatedAt: "2021-06-21T11:13:32.992Z", + }, + tags: [ + { + id: "efb9b9b9-6697-4988-af41-6aa7e10b2030", + title: "auto Loan Account", + createdAt: "2021-06-21T11:13:32.998Z", + updatedAt: "2021-06-21T11:13:32.998Z", + }, + { + id: "ff780da9-2a1b-4030-8ecf-5cb9f520ec34", + title: "brand", + createdAt: "2021-06-21T11:13:33.006Z", + updatedAt: "2021-06-21T11:13:33.006Z", + }, + { + id: "9f84d769-c026-44b0-a946-16dd94178a0f", + title: "boliviano Mvdol", + createdAt: "2021-06-21T11:13:33.016Z", + updatedAt: "2021-06-21T11:13:33.016Z", + }, + ], + }, + { + status: "draft", + id: "1a8f237f-759d-428d-8ab1-5e5b98599504", + title: "Wireless Investor Wooden", + content: + "Dolor rerum id incidunt. Tenetur nam odio corporis. Et quod inventore facere qui ut aut quia.\n \rDistinctio voluptas sit aut odit dolore ut hic. Occaecati ut ipsam sapiente suscipit nisi et omnis eveniet. Omnis accusantium eum. Mollitia dolores accusamus repudiandae occaecati sed enim in voluptate. Ut id velit et et atque et assumenda itaque officiis. Repudiandae cumque consectetur et numquam et saepe veniam.\n \rQuo autem dolores quo voluptatem suscipit nulla dolorum cum laudantium. Sunt aut saepe eum cumque perspiciatis. Id nobis doloremque nesciunt vero architecto.\n \rOmnis et quia esse perferendis cum in. Et et delectus vitae nihil. Distinctio magni nesciunt. Sint dolorum voluptatem quia optio veniam alias. Rerum magnam occaecati rem molestias. Voluptatem natus magni est voluptas voluptas.\n \rUt eum autem recusandae libero vel nemo fugiat est itaque. Eveniet quasi officia fugiat. Dolor temporibus qui mollitia sint reiciendis. Rerum in id. Laudantium dignissimos nobis.\n \rEsse et sint ratione placeat et similique alias modi. Ipsam laudantium reprehenderit officia illo dolor omnis id dolor. Fugiat vero molestias aliquid iusto nostrum repellendus error.\n \rInventore dolor sit pariatur amet est dolores reiciendis. Aut dolor vitae deserunt. Sit cumque nam voluptas corrupti accusamus ea et eligendi hic. Amet eos facilis eaque fugit ut et dolorem qui. Et esse vitae facilis qui labore voluptate aut autem. Et maiores qui.\n \rMinus officiis tempore magni dolorem itaque et. Accusamus magnam distinctio. Quisquam officia dolorem quam aut voluptatem quasi animi.\n \rEt quia et eos quaerat officia modi. Temporibus et ipsa saepe eos velit. Qui dicta est est ea qui odit beatae ut perferendis.\n \rRepellendus officia et magnam veritatis tempora soluta molestiae atque. Aut perferendis et. Exercitationem quia ea. Fugiat vel rerum ullam. Eos aut consequatur harum perspiciatis consectetur quisquam eius. Placeat dolorem enim aliquid minus.", + slug: "wireless-investor-wooden", + images: [ + { + uid: "rc-upload-focb6qzs3j", + name: "random-image.jpg", + url: "https://picsum.photos/800", + type: "image/jpeg", + size: 141940, + }, + ], + createdAt: "2021-06-21T11:13:33.881Z", + updatedAt: "2021-06-21T11:13:34.073Z", + category: { + id: "ba984fd7-be8c-46c6-80b6-7f39075afaf2", + title: "Salad Somali Shilling Bandwidth-Monitored", + createdAt: "2021-06-21T11:13:34.073Z", + updatedAt: "2021-06-21T11:13:34.073Z", + }, + user: { + id: "6487ea84-1878-4647-ba89-99cd45b4fd76", + firstName: "Jailyn", + lastName: "Runte", + email: "jailyn_Runte@hotmail.com", + status: true, + createdAt: "2021-06-21T11:13:33.725Z", + updatedAt: "2021-06-21T11:13:33.725Z", + }, + tags: [ + { + id: "9ef69848-0808-4363-92f9-83db545b456c", + title: "disintermediate", + createdAt: "2021-06-21T11:13:33.731Z", + updatedAt: "2021-06-21T11:13:33.731Z", + }, + { + id: "c0a81769-0811-4b0b-acd6-a535b95870e7", + title: "mozambique", + createdAt: "2021-06-21T11:13:33.736Z", + updatedAt: "2021-06-21T11:13:33.736Z", + }, + { + id: "598af14d-2599-444a-adca-faf2e1275806", + title: "driver", + createdAt: "2021-06-21T11:13:33.748Z", + updatedAt: "2021-06-21T11:13:33.748Z", + }, + ], + }, + { + status: "published", + id: "09b8d9e5-c0ae-40cb-811a-94a46f805a63", + title: "Armenian Dram uniform deposit", + content: + "Ea et aut deserunt id impedit placeat. Et aut molestiae omnis cum ullam maiores aliquam dolorem ut. Soluta voluptatibus error et numquam provident. Eos et et ex architecto enim veritatis. Id fugit et et non voluptatem ipsam.\n \rBlanditiis iusto corrupti necessitatibus et est. Deleniti totam tempore architecto pariatur aut saepe perspiciatis. Ea est temporibus et aperiam iste consectetur beatae. Eius sunt quos distinctio voluptas assumenda dolorem.\n \rRatione quam voluptatem quis voluptatibus quod recusandae. Velit maiores ut. Omnis mollitia sit minus aspernatur quas veritatis. Reiciendis nihil culpa nisi voluptate quaerat quibusdam. Qui asperiores blanditiis et non hic magni non aut libero. Ratione et dolores a sed reprehenderit facere.\n \rDelectus voluptates voluptatem sed non repudiandae commodi. Temporibus modi omnis aut officia quo nihil dignissimos corrupti omnis. Inventore aut iste corporis sed aut accusantium dolores quaerat. Perspiciatis maiores et facere animi similique.\n \rEt cum facere quibusdam aut. Voluptatem ab voluptas quasi facilis facere excepturi. Sapiente nesciunt nihil.\n \rConsectetur eos et quod. Quas voluptas nam in nihil omnis velit officia in. Numquam voluptas minus vel nesciunt. Voluptatum et voluptas fuga beatae. Consequuntur illum maxime modi autem vel ut provident ut. Facilis nobis laudantium quia provident quidem eveniet sunt atque.\n \rConsequuntur qui iusto alias sint tempore et accusamus dicta aliquam. Ea itaque quia architecto ut. Deserunt ut iste ratione. Quia maiores atque et laboriosam voluptatem.\n \rQuae quam voluptas dolorum sint aperiam beatae. Id accusantium sint accusamus voluptatibus voluptates repudiandae at. Hic molestiae iure ea nemo enim modi tempora.\n \rVitae explicabo sequi error ratione qui officia. Recusandae corrupti eum nostrum non consequuntur est. Commodi natus minima et. Fugit veniam necessitatibus nostrum. Alias fugit alias eos recusandae explicabo dolorem voluptatem. Quis laborum qui.\n \rTemporibus eius libero modi vel corrupti. Vel sunt quis libero ducimus aliquam vero neque aut. Sit nemo quisquam accusamus blanditiis eos.", + slug: "armenian-dram-uniform-deposit", + images: [ + { + uid: "rc-upload-46ffepvnn6", + name: "random-image.jpg", + url: "https://picsum.photos/800", + type: "image/jpeg", + size: 141940, + }, + ], + createdAt: "2021-06-21T11:13:34.202Z", + updatedAt: "2021-06-21T11:13:34.600Z", + category: { + id: "38b55d73-1fe4-4f25-b8a5-944e62b53279", + title: "Dynamic Chicken Assistant", + createdAt: "2021-06-21T11:13:34.600Z", + updatedAt: "2021-06-21T11:13:34.600Z", + }, + user: { + id: "a990da0e-0030-42b0-9157-a02ff6389b12", + firstName: "Michaela", + lastName: "Hammes", + email: "michaela.Hammes@hotmail.com", + status: true, + createdAt: "2021-06-21T11:13:34.119Z", + updatedAt: "2021-06-21T11:13:34.119Z", + }, + tags: [ + { + id: "627038fd-6366-429e-be03-1052be2cc77d", + title: "architect", + createdAt: "2021-06-21T11:13:34.123Z", + updatedAt: "2021-06-21T11:13:34.123Z", + }, + { + id: "f910d052-6e00-46a8-9e6f-703674dd2926", + title: "whiteboard", + createdAt: "2021-06-21T11:13:34.131Z", + updatedAt: "2021-06-21T11:13:34.131Z", + }, + { + id: "5b54c42d-74b4-4d60-adf6-47a96e39f72c", + title: "row", + createdAt: "2021-06-21T11:13:34.137Z", + updatedAt: "2021-06-21T11:13:34.137Z", + }, + ], + }, + { + status: "published", + id: "177f9271-33e5-4bf9-9700-60b45b52afba", + title: "Fully-configurable red Credit Card Account", + content: + "Quos error consectetur consequatur ipsam temporibus at dolorem ut eos. Quisquam et veritatis est omnis sint perspiciatis tempora consequatur. Voluptatem minima et consequuntur. Cum quia ducimus saepe cumque quae. Magni molestias recusandae. Quia non ipsum.\n \rExpedita autem et inventore. Ipsa aut veniam animi. Deserunt quia accusantium quasi qui.\n \rEaque ut doloremque. Et earum impedit eum. Optio tenetur nulla at.\n \rTempore non quam quasi voluptatum debitis velit adipisci quibusdam unde. Sunt omnis nobis voluptatem tempora dicta libero sed at. Modi non tempore quasi ducimus odio aut. Voluptatem maxime at in aperiam adipisci sit dolorem quasi maxime. Et officiis vitae consequatur et rerum. In et doloribus enim eveniet quia qui deleniti nobis.\n \rExplicabo dicta a. Blanditiis ullam ab consequatur repudiandae. Consequuntur nesciunt assumenda dolore ad numquam temporibus.\n \rNihil explicabo eum non impedit quam consequuntur amet voluptatibus. Voluptatum fuga ad aut eligendi et perspiciatis ea quasi. Reprehenderit consequatur a quasi enim aut placeat quisquam sunt. Dolorem odio quisquam nesciunt ipsa ut. Perspiciatis fugit omnis saepe libero voluptatem.\n \rIpsam quidem excepturi sapiente. Molestiae vitae dolorem veniam ut. Ea soluta quas sequi libero et sapiente. Qui incidunt fugit maxime autem. Non est in quas reprehenderit ut possimus.\n \rQuis porro asperiores ipsa et earum. Distinctio ex odio delectus dolor voluptatibus. Est non et quas nihil et dolorum alias reiciendis. Ratione nam dolor voluptas consequatur exercitationem.\n \rAdipisci quia deleniti molestias eveniet perspiciatis pariatur. Expedita libero hic nihil minima et praesentium. Quia voluptas voluptas velit suscipit possimus ipsam fuga vel velit. Et iure harum et officiis sint.\n \rNihil nam voluptatem ducimus velit ut voluptatem quis qui ducimus. Eos quod ex et ab adipisci voluptates fuga consequatur ipsa. Molestiae est aut dolorum deleniti voluptas. Tenetur alias nulla vel voluptatem quasi vel incidunt. Quam amet commodi odit itaque quae. Sed aliquam earum.", + slug: "fully-configurable-red-credit-card-account", + images: [ + { + uid: "rc-upload-iu2gqw0z9c", + name: "random-image.jpg", + url: "https://picsum.photos/800", + type: "image/jpeg", + size: 141940, + }, + ], + createdAt: "2021-06-21T11:13:34.306Z", + updatedAt: "2021-06-21T11:13:34.600Z", + category: { + id: "38b55d73-1fe4-4f25-b8a5-944e62b53279", + title: "Dynamic Chicken Assistant", + createdAt: "2021-06-21T11:13:34.600Z", + updatedAt: "2021-06-21T11:13:34.600Z", + }, + user: { + id: "a990da0e-0030-42b0-9157-a02ff6389b12", + firstName: "Michaela", + lastName: "Hammes", + email: "michaela.Hammes@hotmail.com", + status: true, + createdAt: "2021-06-21T11:13:34.119Z", + updatedAt: "2021-06-21T11:13:34.119Z", + }, + tags: [ + { + id: "627038fd-6366-429e-be03-1052be2cc77d", + title: "architect", + createdAt: "2021-06-21T11:13:34.123Z", + updatedAt: "2021-06-21T11:13:34.123Z", + }, + { + id: "f910d052-6e00-46a8-9e6f-703674dd2926", + title: "whiteboard", + createdAt: "2021-06-21T11:13:34.131Z", + updatedAt: "2021-06-21T11:13:34.131Z", + }, + { + id: "5b54c42d-74b4-4d60-adf6-47a96e39f72c", + title: "row", + createdAt: "2021-06-21T11:13:34.137Z", + updatedAt: "2021-06-21T11:13:34.137Z", + }, + ], + }, + { + status: "draft", + id: "011edb32-f071-424a-8747-81d894f52906", + title: "Games initiatives online", + content: + "Mollitia eius rerum temporibus omnis dolorem. Asperiores occaecati ut consequuntur est et reprehenderit id excepturi. Quibusdam dolorem ea eos. Ducimus ut alias. Explicabo adipisci natus. Sit consequatur non enim quo harum quis.\n \rUt reiciendis aut ad. Est et dolorem. Odio omnis non ea. Sapiente quos optio architecto eos aspernatur vel est.\n \rDoloribus nisi dignissimos eum sed molestias natus. Aperiam ea aliquid eos doloribus consequatur et et. Voluptas sed ut. Et consequatur aut voluptatem dicta necessitatibus sunt quia. Non delectus molestiae. Quasi praesentium dignissimos facilis et qui et.\n \rEt qui et et voluptatibus voluptate alias. Quibusdam ut omnis sunt pariatur. Explicabo facere atque facilis laboriosam. Reprehenderit rem ratione sed laborum omnis alias illo quas aperiam. At porro animi sit eaque dolore. Tempora autem numquam eos voluptas.\n \rMinima non minus tempora deleniti quia. Enim maiores sit. Soluta a id iusto illo placeat dolore delectus praesentium sunt.\n \rPorro esse quis atque veritatis iusto assumenda reprehenderit cupiditate. Provident saepe blanditiis fugit neque amet ad reprehenderit. Dolores consectetur omnis officia aut odio.\n \rDolorum totam consectetur maxime. Mollitia ea quo libero distinctio doloremque ipsam. Inventore delectus qui consequuntur sapiente ea maiores doloribus hic et. Sapiente vel ut vitae ad omnis incidunt. Odio deleniti hic tempore ut omnis ullam. Eveniet provident cum cupiditate rerum.\n \rVoluptas laboriosam quas temporibus illo. Ducimus natus sit possimus blanditiis impedit illum sit dolorem. Corporis officiis aut ipsam laboriosam aut. Voluptatem nemo reiciendis quam omnis quia. Alias voluptatibus et non libero.\n \rDolores eaque in autem voluptatum sit officia aut. Accusamus est dolores repellendus sit nihil. Ut quibusdam recusandae laborum sequi. Veritatis nesciunt ut eius iure. Fuga odio rem voluptas et quasi a culpa a.\n \rEa voluptas recusandae aperiam occaecati quae dicta quibusdam fugiat. Illo quas maiores qui vero consequuntur pariatur. Amet voluptas eum sit. Ut minus non sunt tenetur. Nihil est quae vitae minus repellat cum minus nisi.", + slug: "games-initiatives-online", + images: [ + { + uid: "rc-upload-aypuqgh2hm", + name: "random-image.jpg", + url: "https://picsum.photos/800", + type: "image/jpeg", + size: 141940, + }, + ], + createdAt: "2021-06-21T11:13:34.806Z", + updatedAt: "2021-06-21T11:13:35.195Z", + category: { + id: "73bdc4c0-0cc2-49bb-bd6f-550deb795468", + title: "Deposit Capacitor Hdd", + createdAt: "2021-06-21T11:13:35.195Z", + updatedAt: "2021-06-21T11:13:35.195Z", + }, + user: { + id: "0e1e8a86-b09e-4df4-80f3-5047d57c9cdf", + firstName: "Ansley", + lastName: "McCullough", + email: "ansley_McCullough53@yahoo.com", + status: true, + createdAt: "2021-06-21T11:13:34.699Z", + updatedAt: "2021-06-21T11:13:34.699Z", + }, + tags: [ + { + id: "e51fd147-2efd-409c-acd5-84d4e27ba71e", + title: "monitor", + createdAt: "2021-06-21T11:13:34.703Z", + updatedAt: "2021-06-21T11:13:34.703Z", + }, + { + id: "02058bdc-41df-45ed-9884-e9ff9dc2bd18", + title: "kuwait", + createdAt: "2021-06-21T11:13:34.707Z", + updatedAt: "2021-06-21T11:13:34.707Z", + }, + { + id: "6dedd9d7-1cc8-4016-ba03-78c2ba806f1a", + title: "framework", + createdAt: "2021-06-21T11:13:34.712Z", + updatedAt: "2021-06-21T11:13:34.712Z", + }, + ], + }, + { + status: "draft", + id: "04921a99-4918-4288-adea-f478a9e9e6bd", + title: "Sensor Motorway Bedfordshire", + content: + "Accusantium sed voluptas consequatur veniam ab voluptates. Est ipsam ipsum placeat ea ipsam recusandae consequatur. Quia reprehenderit aut. Eos esse non vero reiciendis distinctio atque qui. Nulla architecto nulla nihil veniam et. Ut maxime incidunt dolor facilis architecto dolorem voluptas.\n \rEum unde consequatur consequatur fuga est tenetur suscipit. Odio est dolorem dolorum nesciunt reiciendis ut. Ab quo eaque nulla et blanditiis aut omnis. Voluptate minima sit nihil.\n \rEt a et at. Nihil non enim est qui quisquam fugiat cumque quaerat. Nemo qui ducimus.\n \rPerspiciatis possimus aliquam corporis esse corrupti minima quam. Vel eos rem necessitatibus aliquid modi sint saepe provident. Aut voluptates dolor rem quae provident.\n \rNihil et voluptates qui molestias voluptate est est atque. Officia et fuga maiores porro dolores rerum exercitationem laboriosam. Est voluptate voluptatibus porro. Nemo enim nam est saepe. Dignissimos perspiciatis molestiae autem illo et vel.\n \rMinus sed accusamus velit dicta natus. Molestias neque labore. Voluptatem exercitationem non at error dolor in dolores. Eveniet animi et. Deleniti aut non voluptas voluptas quia omnis.\n \rVoluptate et consectetur. Laboriosam beatae non delectus aut placeat corrupti. Est ad magni omnis officiis sit aut at alias.\n \rVoluptas deserunt architecto. Esse sed voluptatum. Eos quos consectetur placeat quia nemo.\n \rPossimus ea architecto reiciendis adipisci et. Sit laboriosam et vel nostrum dolore accusamus. Deleniti repellat vel et facilis praesentium fugit voluptates autem.\n \rDolorem vero earum nemo amet laboriosam. Voluptatum sint quis iusto ut minus est quia et est. Doloribus dolorem sequi debitis. Unde consequuntur qui et voluptatibus odio hic repudiandae itaque.", + slug: "sensor-motorway-bedfordshire", + images: [ + { + uid: "rc-upload-5xqp4f2y17", + name: "random-image.jpg", + url: "https://picsum.photos/800", + type: "image/jpeg", + size: 141940, + }, + ], + createdAt: "2021-06-21T11:13:35.045Z", + updatedAt: "2021-06-21T11:13:35.195Z", + category: { + id: "73bdc4c0-0cc2-49bb-bd6f-550deb795468", + title: "Deposit Capacitor Hdd", + createdAt: "2021-06-21T11:13:35.195Z", + updatedAt: "2021-06-21T11:13:35.195Z", + }, + user: { + id: "0e1e8a86-b09e-4df4-80f3-5047d57c9cdf", + firstName: "Ansley", + lastName: "McCullough", + email: "ansley_McCullough53@yahoo.com", + status: true, + createdAt: "2021-06-21T11:13:34.699Z", + updatedAt: "2021-06-21T11:13:34.699Z", + }, + tags: [ + { + id: "e51fd147-2efd-409c-acd5-84d4e27ba71e", + title: "monitor", + createdAt: "2021-06-21T11:13:34.703Z", + updatedAt: "2021-06-21T11:13:34.703Z", + }, + { + id: "02058bdc-41df-45ed-9884-e9ff9dc2bd18", + title: "kuwait", + createdAt: "2021-06-21T11:13:34.707Z", + updatedAt: "2021-06-21T11:13:34.707Z", + }, + { + id: "6dedd9d7-1cc8-4016-ba03-78c2ba806f1a", + title: "framework", + createdAt: "2021-06-21T11:13:34.712Z", + updatedAt: "2021-06-21T11:13:34.712Z", + }, + ], + }, + ], + count: 10, + total: 135, + page: 1, + pageCount: 14, + }, + [ + "Server", + "nginx/1.17.10", + "Date", + "Mon, 21 Jun 2021 12:07:07 GMT", + "Content-Type", + "application/json; charset=utf-8", + "Content-Length", + "32420", + "Connection", + "close", + "Vary", + "Accept-Encoding", + "X-Powered-By", + "Express", + "Access-Control-Allow-Origin", + "*", + "ETag", + 'W/"7ea4-FLkQQcM/K2rMJ8DU8rb7gsS/ZRw"', + ], + ); + +nock("https://api.nestjsx-crud.refine.dev:443", { encodedQueryParams: true }) + .get("/posts") + .query({ limit: "10", page: "1", offset: "0", "sort%5B0%5D": "id%2CASC" }) + .reply( + 200, + { + data: [ + { + status: "draft", + id: "011edb32-f071-424a-8747-81d894f52906", + title: "Games initiatives online", + content: + "Mollitia eius rerum temporibus omnis dolorem. Asperiores occaecati ut consequuntur est et reprehenderit id excepturi. Quibusdam dolorem ea eos. Ducimus ut alias. Explicabo adipisci natus. Sit consequatur non enim quo harum quis.\n \rUt reiciendis aut ad. Est et dolorem. Odio omnis non ea. Sapiente quos optio architecto eos aspernatur vel est.\n \rDoloribus nisi dignissimos eum sed molestias natus. Aperiam ea aliquid eos doloribus consequatur et et. Voluptas sed ut. Et consequatur aut voluptatem dicta necessitatibus sunt quia. Non delectus molestiae. Quasi praesentium dignissimos facilis et qui et.\n \rEt qui et et voluptatibus voluptate alias. Quibusdam ut omnis sunt pariatur. Explicabo facere atque facilis laboriosam. Reprehenderit rem ratione sed laborum omnis alias illo quas aperiam. At porro animi sit eaque dolore. Tempora autem numquam eos voluptas.\n \rMinima non minus tempora deleniti quia. Enim maiores sit. Soluta a id iusto illo placeat dolore delectus praesentium sunt.\n \rPorro esse quis atque veritatis iusto assumenda reprehenderit cupiditate. Provident saepe blanditiis fugit neque amet ad reprehenderit. Dolores consectetur omnis officia aut odio.\n \rDolorum totam consectetur maxime. Mollitia ea quo libero distinctio doloremque ipsam. Inventore delectus qui consequuntur sapiente ea maiores doloribus hic et. Sapiente vel ut vitae ad omnis incidunt. Odio deleniti hic tempore ut omnis ullam. Eveniet provident cum cupiditate rerum.\n \rVoluptas laboriosam quas temporibus illo. Ducimus natus sit possimus blanditiis impedit illum sit dolorem. Corporis officiis aut ipsam laboriosam aut. Voluptatem nemo reiciendis quam omnis quia. Alias voluptatibus et non libero.\n \rDolores eaque in autem voluptatum sit officia aut. Accusamus est dolores repellendus sit nihil. Ut quibusdam recusandae laborum sequi. Veritatis nesciunt ut eius iure. Fuga odio rem voluptas et quasi a culpa a.\n \rEa voluptas recusandae aperiam occaecati quae dicta quibusdam fugiat. Illo quas maiores qui vero consequuntur pariatur. Amet voluptas eum sit. Ut minus non sunt tenetur. Nihil est quae vitae minus repellat cum minus nisi.", + slug: "games-initiatives-online", + images: [ + { + uid: "rc-upload-aypuqgh2hm", + name: "random-image.jpg", + url: "https://picsum.photos/800", + type: "image/jpeg", + size: 141940, + }, + ], + createdAt: "2021-06-21T11:13:34.806Z", + updatedAt: "2021-06-21T11:13:35.195Z", + category: { + id: "73bdc4c0-0cc2-49bb-bd6f-550deb795468", + title: "Deposit Capacitor Hdd", + createdAt: "2021-06-21T11:13:35.195Z", + updatedAt: "2021-06-21T11:13:35.195Z", + }, + user: { + id: "0e1e8a86-b09e-4df4-80f3-5047d57c9cdf", + firstName: "Ansley", + lastName: "McCullough", + email: "ansley_McCullough53@yahoo.com", + status: true, + createdAt: "2021-06-21T11:13:34.699Z", + updatedAt: "2021-06-21T11:13:34.699Z", + }, + tags: [ + { + id: "e51fd147-2efd-409c-acd5-84d4e27ba71e", + title: "monitor", + createdAt: "2021-06-21T11:13:34.703Z", + updatedAt: "2021-06-21T11:13:34.703Z", + }, + { + id: "02058bdc-41df-45ed-9884-e9ff9dc2bd18", + title: "kuwait", + createdAt: "2021-06-21T11:13:34.707Z", + updatedAt: "2021-06-21T11:13:34.707Z", + }, + { + id: "6dedd9d7-1cc8-4016-ba03-78c2ba806f1a", + title: "framework", + createdAt: "2021-06-21T11:13:34.712Z", + updatedAt: "2021-06-21T11:13:34.712Z", + }, + ], + }, + { + status: "draft", + id: "02ff5d56-b616-4b7d-9043-25c49bc988d2", + title: "Moldova bypassing models", + content: + "Labore tempore sapiente earum rerum corrupti libero. Eveniet vel harum aut. Beatae tempora suscipit est expedita. Quisquam sint dolorem. Sed quibusdam voluptatem rerum eaque est quasi.\n \rQuibusdam qui impedit aspernatur voluptas adipisci esse voluptatem quibusdam. Ut ipsum eum. Quae qui quod consectetur. Dolorum consectetur quis accusantium eveniet quo.\n \rFuga enim quos voluptas qui qui voluptatibus est. Quidem non dolores modi ut eum enim. Non quis earum autem vero consequuntur quaerat earum sed deleniti. Similique aut autem et aperiam repellendus cumque ullam maxime.\n \rEt vel ut. Voluptate expedita quia modi est. Consequatur veritatis sed. Quo porro quasi blanditiis magni quos facilis ut.\n \rAliquid et aut. Omnis cum accusantium libero non qui et incidunt sit. Repellendus eaque qui reprehenderit. Id sint dolorum. Eligendi numquam fugit quidem.\n \rExplicabo ut voluptatem nulla omnis quod. Eos vel fuga. Corporis aliquid voluptatum accusamus laborum sapiente tempore dolorem.\n \rEaque repudiandae aliquid. Quaerat ullam aut repellendus ea et similique voluptatem eos accusamus. Velit quasi enim placeat aut qui dolores. Aspernatur sed aut deserunt architecto voluptatem quo. Qui consequatur labore quam molestias blanditiis.\n \rMollitia iure veritatis dolorem adipisci sit. Omnis animi itaque. Quia nostrum optio. Et ipsa autem rerum doloribus. Vel qui ipsa nam ratione et fuga veritatis maiores dignissimos. Non et eligendi laborum quibusdam a.\n \rAutem voluptate tempore sit. Et et voluptatem. Quaerat modi vero sit quia quisquam laudantium architecto et. Rerum vero error ad unde a voluptate temporibus. Officiis molestiae reiciendis unde. Non velit similique.\n \rEsse qui ratione ipsam dicta quaerat voluptas quas eligendi. Aperiam rerum possimus aliquid minus at quis tempora. Non eligendi autem. Sunt rem pariatur dignissimos est accusamus repellendus.", + slug: "moldova-bypassing-models", + images: [ + { + uid: "rc-upload-ns3jz95agc", + name: "random-image.jpg", + url: "https://picsum.photos/800", + type: "image/jpeg", + size: 141940, + }, + ], + createdAt: "2021-06-21T11:13:33.075Z", + updatedAt: "2021-06-21T11:13:33.678Z", + category: { + id: "ad291690-f03c-47c3-af90-4bb75b20def4", + title: "Purple Berkshire Tuna", + createdAt: "2021-06-21T11:13:33.678Z", + updatedAt: "2021-06-21T11:13:33.678Z", + }, + user: { + id: "17291210-242d-4259-b04b-d89d05aa86b7", + firstName: "Griffin", + lastName: "Carroll", + email: "griffin.Carroll91@gmail.com", + status: true, + createdAt: "2021-06-21T11:13:32.992Z", + updatedAt: "2021-06-21T11:13:32.992Z", + }, + tags: [ + { + id: "9f84d769-c026-44b0-a946-16dd94178a0f", + title: "boliviano Mvdol", + createdAt: "2021-06-21T11:13:33.016Z", + updatedAt: "2021-06-21T11:13:33.016Z", + }, + { + id: "efb9b9b9-6697-4988-af41-6aa7e10b2030", + title: "auto Loan Account", + createdAt: "2021-06-21T11:13:32.998Z", + updatedAt: "2021-06-21T11:13:32.998Z", + }, + { + id: "ff780da9-2a1b-4030-8ecf-5cb9f520ec34", + title: "brand", + createdAt: "2021-06-21T11:13:33.006Z", + updatedAt: "2021-06-21T11:13:33.006Z", + }, + ], + }, + { + status: "draft", + id: "04921a99-4918-4288-adea-f478a9e9e6bd", + title: "Sensor Motorway Bedfordshire", + content: + "Accusantium sed voluptas consequatur veniam ab voluptates. Est ipsam ipsum placeat ea ipsam recusandae consequatur. Quia reprehenderit aut. Eos esse non vero reiciendis distinctio atque qui. Nulla architecto nulla nihil veniam et. Ut maxime incidunt dolor facilis architecto dolorem voluptas.\n \rEum unde consequatur consequatur fuga est tenetur suscipit. Odio est dolorem dolorum nesciunt reiciendis ut. Ab quo eaque nulla et blanditiis aut omnis. Voluptate minima sit nihil.\n \rEt a et at. Nihil non enim est qui quisquam fugiat cumque quaerat. Nemo qui ducimus.\n \rPerspiciatis possimus aliquam corporis esse corrupti minima quam. Vel eos rem necessitatibus aliquid modi sint saepe provident. Aut voluptates dolor rem quae provident.\n \rNihil et voluptates qui molestias voluptate est est atque. Officia et fuga maiores porro dolores rerum exercitationem laboriosam. Est voluptate voluptatibus porro. Nemo enim nam est saepe. Dignissimos perspiciatis molestiae autem illo et vel.\n \rMinus sed accusamus velit dicta natus. Molestias neque labore. Voluptatem exercitationem non at error dolor in dolores. Eveniet animi et. Deleniti aut non voluptas voluptas quia omnis.\n \rVoluptate et consectetur. Laboriosam beatae non delectus aut placeat corrupti. Est ad magni omnis officiis sit aut at alias.\n \rVoluptas deserunt architecto. Esse sed voluptatum. Eos quos consectetur placeat quia nemo.\n \rPossimus ea architecto reiciendis adipisci et. Sit laboriosam et vel nostrum dolore accusamus. Deleniti repellat vel et facilis praesentium fugit voluptates autem.\n \rDolorem vero earum nemo amet laboriosam. Voluptatum sint quis iusto ut minus est quia et est. Doloribus dolorem sequi debitis. Unde consequuntur qui et voluptatibus odio hic repudiandae itaque.", + slug: "sensor-motorway-bedfordshire", + images: [ + { + uid: "rc-upload-5xqp4f2y17", + name: "random-image.jpg", + url: "https://picsum.photos/800", + type: "image/jpeg", + size: 141940, + }, + ], + createdAt: "2021-06-21T11:13:35.045Z", + updatedAt: "2021-06-21T11:13:35.195Z", + category: { + id: "73bdc4c0-0cc2-49bb-bd6f-550deb795468", + title: "Deposit Capacitor Hdd", + createdAt: "2021-06-21T11:13:35.195Z", + updatedAt: "2021-06-21T11:13:35.195Z", + }, + user: { + id: "0e1e8a86-b09e-4df4-80f3-5047d57c9cdf", + firstName: "Ansley", + lastName: "McCullough", + email: "ansley_McCullough53@yahoo.com", + status: true, + createdAt: "2021-06-21T11:13:34.699Z", + updatedAt: "2021-06-21T11:13:34.699Z", + }, + tags: [ + { + id: "02058bdc-41df-45ed-9884-e9ff9dc2bd18", + title: "kuwait", + createdAt: "2021-06-21T11:13:34.707Z", + updatedAt: "2021-06-21T11:13:34.707Z", + }, + { + id: "6dedd9d7-1cc8-4016-ba03-78c2ba806f1a", + title: "framework", + createdAt: "2021-06-21T11:13:34.712Z", + updatedAt: "2021-06-21T11:13:34.712Z", + }, + { + id: "e51fd147-2efd-409c-acd5-84d4e27ba71e", + title: "monitor", + createdAt: "2021-06-21T11:13:34.703Z", + updatedAt: "2021-06-21T11:13:34.703Z", + }, + ], + }, + { + status: "draft", + id: "06bad81a-7712-44db-9d65-3aee7b5439b7", + title: "Turnpike Director copying", + content: + "Accusamus omnis earum illo eligendi ut sit dolore consequatur. Sint est et mollitia optio laborum. Mollitia et sit doloribus in dolorem.\n \rOmnis fuga repellendus asperiores dolor sequi pariatur et. Tempore accusantium explicabo odit dolore accusantium alias. Quis dolorum quia aut sed nam est. Alias totam voluptates saepe autem.\n \rNatus sit mollitia eligendi aliquid velit perspiciatis voluptatem fugit. Et et eveniet non ut unde sit. Cupiditate odit voluptatem ipsa. Doloremque deleniti est sit animi ab dolores ut architecto. Harum temporibus placeat adipisci asperiores optio cupiditate explicabo. Quia consectetur et sed.\n \rAb at aspernatur quo. Aut sed quibusdam quia nulla minus quam eius nihil tenetur. Similique voluptate dicta. Vel necessitatibus et est amet qui et. Omnis voluptate commodi laborum cum nihil sed. Non explicabo voluptates non exercitationem est aut est.\n \rRepudiandae error dolorem accusamus vel consequatur tenetur nam. Eos veniam a id sapiente blanditiis dicta quo. Quasi molestias consequuntur beatae aperiam possimus ullam cumque ut. Sapiente sunt est odit. Optio nihil sed.\n \rDolor cumque totam impedit ea. Sint iusto odit nihil eos consectetur suscipit. Non doloribus quos ducimus tenetur omnis omnis repellendus provident molestias. Fuga omnis pariatur enim.\n \rNostrum iusto et qui voluptatem culpa eius animi voluptatem. Quos molestiae totam fuga. Pariatur aut ut aperiam iste voluptatem tenetur est excepturi. Laboriosam quia nemo facilis voluptatibus. Enim modi consequatur non et voluptates velit. Neque quasi et deserunt id aut incidunt.\n \rSint ut ex. Sit eligendi vero mollitia sequi consequatur. Labore est voluptatum suscipit quod consectetur natus.\n \rQuis quos dolore accusantium. Blanditiis ut est quo. Ea distinctio tempore nulla reiciendis. Exercitationem necessitatibus doloremque cumque accusantium aut quis. Ex ipsam et exercitationem et nulla consequatur maiores. Rem unde maiores reprehenderit ut perferendis aut nemo doloremque.\n \rSuscipit commodi porro qui libero et et reprehenderit vel. Non et molestiae. Est vero reiciendis quia et rerum. Quis cum omnis blanditiis fugit ut architecto.", + slug: "turnpike-director-copying", + images: [ + { + uid: "rc-upload-vttftltant", + name: "random-image.jpg", + url: "https://picsum.photos/800", + type: "image/jpeg", + size: 141940, + }, + ], + createdAt: "2021-06-21T11:13:33.107Z", + updatedAt: "2021-06-21T11:13:33.678Z", + category: { + id: "ad291690-f03c-47c3-af90-4bb75b20def4", + title: "Purple Berkshire Tuna", + createdAt: "2021-06-21T11:13:33.678Z", + updatedAt: "2021-06-21T11:13:33.678Z", + }, + user: { + id: "17291210-242d-4259-b04b-d89d05aa86b7", + firstName: "Griffin", + lastName: "Carroll", + email: "griffin.Carroll91@gmail.com", + status: true, + createdAt: "2021-06-21T11:13:32.992Z", + updatedAt: "2021-06-21T11:13:32.992Z", + }, + tags: [ + { + id: "efb9b9b9-6697-4988-af41-6aa7e10b2030", + title: "auto Loan Account", + createdAt: "2021-06-21T11:13:32.998Z", + updatedAt: "2021-06-21T11:13:32.998Z", + }, + { + id: "ff780da9-2a1b-4030-8ecf-5cb9f520ec34", + title: "brand", + createdAt: "2021-06-21T11:13:33.006Z", + updatedAt: "2021-06-21T11:13:33.006Z", + }, + { + id: "9f84d769-c026-44b0-a946-16dd94178a0f", + title: "boliviano Mvdol", + createdAt: "2021-06-21T11:13:33.016Z", + updatedAt: "2021-06-21T11:13:33.016Z", + }, + ], + }, + { + status: "published", + id: "09b8d9e5-c0ae-40cb-811a-94a46f805a63", + title: "Armenian Dram uniform deposit", + content: + "Ea et aut deserunt id impedit placeat. Et aut molestiae omnis cum ullam maiores aliquam dolorem ut. Soluta voluptatibus error et numquam provident. Eos et et ex architecto enim veritatis. Id fugit et et non voluptatem ipsam.\n \rBlanditiis iusto corrupti necessitatibus et est. Deleniti totam tempore architecto pariatur aut saepe perspiciatis. Ea est temporibus et aperiam iste consectetur beatae. Eius sunt quos distinctio voluptas assumenda dolorem.\n \rRatione quam voluptatem quis voluptatibus quod recusandae. Velit maiores ut. Omnis mollitia sit minus aspernatur quas veritatis. Reiciendis nihil culpa nisi voluptate quaerat quibusdam. Qui asperiores blanditiis et non hic magni non aut libero. Ratione et dolores a sed reprehenderit facere.\n \rDelectus voluptates voluptatem sed non repudiandae commodi. Temporibus modi omnis aut officia quo nihil dignissimos corrupti omnis. Inventore aut iste corporis sed aut accusantium dolores quaerat. Perspiciatis maiores et facere animi similique.\n \rEt cum facere quibusdam aut. Voluptatem ab voluptas quasi facilis facere excepturi. Sapiente nesciunt nihil.\n \rConsectetur eos et quod. Quas voluptas nam in nihil omnis velit officia in. Numquam voluptas minus vel nesciunt. Voluptatum et voluptas fuga beatae. Consequuntur illum maxime modi autem vel ut provident ut. Facilis nobis laudantium quia provident quidem eveniet sunt atque.\n \rConsequuntur qui iusto alias sint tempore et accusamus dicta aliquam. Ea itaque quia architecto ut. Deserunt ut iste ratione. Quia maiores atque et laboriosam voluptatem.\n \rQuae quam voluptas dolorum sint aperiam beatae. Id accusantium sint accusamus voluptatibus voluptates repudiandae at. Hic molestiae iure ea nemo enim modi tempora.\n \rVitae explicabo sequi error ratione qui officia. Recusandae corrupti eum nostrum non consequuntur est. Commodi natus minima et. Fugit veniam necessitatibus nostrum. Alias fugit alias eos recusandae explicabo dolorem voluptatem. Quis laborum qui.\n \rTemporibus eius libero modi vel corrupti. Vel sunt quis libero ducimus aliquam vero neque aut. Sit nemo quisquam accusamus blanditiis eos.", + slug: "armenian-dram-uniform-deposit", + images: [ + { + uid: "rc-upload-46ffepvnn6", + name: "random-image.jpg", + url: "https://picsum.photos/800", + type: "image/jpeg", + size: 141940, + }, + ], + createdAt: "2021-06-21T11:13:34.202Z", + updatedAt: "2021-06-21T11:13:34.600Z", + category: { + id: "38b55d73-1fe4-4f25-b8a5-944e62b53279", + title: "Dynamic Chicken Assistant", + createdAt: "2021-06-21T11:13:34.600Z", + updatedAt: "2021-06-21T11:13:34.600Z", + }, + user: { + id: "a990da0e-0030-42b0-9157-a02ff6389b12", + firstName: "Michaela", + lastName: "Hammes", + email: "michaela.Hammes@hotmail.com", + status: true, + createdAt: "2021-06-21T11:13:34.119Z", + updatedAt: "2021-06-21T11:13:34.119Z", + }, + tags: [ + { + id: "627038fd-6366-429e-be03-1052be2cc77d", + title: "architect", + createdAt: "2021-06-21T11:13:34.123Z", + updatedAt: "2021-06-21T11:13:34.123Z", + }, + { + id: "5b54c42d-74b4-4d60-adf6-47a96e39f72c", + title: "row", + createdAt: "2021-06-21T11:13:34.137Z", + updatedAt: "2021-06-21T11:13:34.137Z", + }, + { + id: "f910d052-6e00-46a8-9e6f-703674dd2926", + title: "whiteboard", + createdAt: "2021-06-21T11:13:34.131Z", + updatedAt: "2021-06-21T11:13:34.131Z", + }, + ], + }, + { + status: "draft", + id: "0b71db63-f4ec-407b-b00d-9f0c0368d5ae", + title: "Deposit Small discrete", + content: + "Ea saepe quia impedit. Officiis fuga ex ut dolorem consectetur. Aperiam deserunt atque aperiam doloremque est.\n \rRem reprehenderit et id eum vero quia. Et optio in perferendis iure. Soluta aperiam ut veritatis saepe.\n \rIllo laborum dolor provident neque odit aut eligendi. Ipsa neque molestias et rerum. Aut molestiae vel doloribus. Qui maxime possimus exercitationem sint. Laborum dolores praesentium hic et.\n \rId ipsam corrupti cum aperiam ut voluptatem omnis ipsa eveniet. Cum id voluptas. Et exercitationem vel recusandae maiores quisquam commodi provident.\n \rQuo ad expedita facilis maiores qui non. Sapiente quos similique ipsa saepe quibusdam. Nobis quidem libero saepe magnam sed soluta quas dolor. Dolor consequatur cumque et voluptatem a quisquam.\n \rSimilique perferendis quo consequatur saepe et non nulla maiores autem. Voluptas voluptates accusantium illo quam eius quo quaerat. Ut esse voluptas voluptatem aut dolore minima doloremque voluptatem iste. Et aspernatur vel cupiditate quos.\n \rCum accusamus ipsa provident harum. Natus officiis odio. Saepe molestias et fuga cum esse ipsam sit. Qui illum aliquam delectus aut et et. Voluptas eum voluptatem porro odio autem quia. Nulla et aliquid id dolor omnis et natus quia.\n \rDoloribus libero impedit molestiae doloribus laboriosam et animi. Nemo fuga at voluptatem quia et. Minima reiciendis et porro eveniet animi voluptatum ratione impedit dicta. Odio doloribus iste earum odit rerum modi.\n \rEt incidunt molestias labore odit illo sed sit. Voluptates ut exercitationem quis earum eos ipsa ut nostrum nobis. Dicta sint voluptas aliquid consequatur quas rem asperiores ducimus. Placeat earum iusto et rerum. Sunt et alias et magni delectus eos sed veniam repellat.\n \rTotam debitis deleniti repellendus labore praesentium sed occaecati officiis. Quo laborum et quasi voluptas cum omnis minima earum illum. Natus consequatur atque maxime.", + slug: "deposit-small-discrete", + images: [ + { + uid: "rc-upload-s7ga9wpmki", + name: "random-image.jpg", + url: "https://picsum.photos/800", + type: "image/jpeg", + size: 141940, + }, + ], + createdAt: "2021-06-21T11:13:33.092Z", + updatedAt: "2021-06-21T11:13:33.678Z", + category: { + id: "ad291690-f03c-47c3-af90-4bb75b20def4", + title: "Purple Berkshire Tuna", + createdAt: "2021-06-21T11:13:33.678Z", + updatedAt: "2021-06-21T11:13:33.678Z", + }, + user: { + id: "17291210-242d-4259-b04b-d89d05aa86b7", + firstName: "Griffin", + lastName: "Carroll", + email: "griffin.Carroll91@gmail.com", + status: true, + createdAt: "2021-06-21T11:13:32.992Z", + updatedAt: "2021-06-21T11:13:32.992Z", + }, + tags: [ + { + id: "9f84d769-c026-44b0-a946-16dd94178a0f", + title: "boliviano Mvdol", + createdAt: "2021-06-21T11:13:33.016Z", + updatedAt: "2021-06-21T11:13:33.016Z", + }, + { + id: "ff780da9-2a1b-4030-8ecf-5cb9f520ec34", + title: "brand", + createdAt: "2021-06-21T11:13:33.006Z", + updatedAt: "2021-06-21T11:13:33.006Z", + }, + { + id: "efb9b9b9-6697-4988-af41-6aa7e10b2030", + title: "auto Loan Account", + createdAt: "2021-06-21T11:13:32.998Z", + updatedAt: "2021-06-21T11:13:32.998Z", + }, + ], + }, + { + status: "draft", + id: "15b6c4fe-8069-420a-93a5-06bf91edc448", + title: "Tuna reboot Legacy", + content: + "Vel voluptas maxime est voluptatibus dolor rem. Numquam cum ipsum ut fugiat et sed. Beatae praesentium architecto quia beatae iure explicabo. Minima a culpa a odit praesentium ratione. Est laboriosam qui eligendi culpa sapiente reprehenderit ad.\n \rAlias quae tenetur. Consequatur est saepe odio voluptatum mollitia amet sit omnis. Aut sint hic temporibus modi ab quibusdam maiores laborum. Omnis harum et eum veniam aut vel. Consequatur minus reiciendis hic veritatis officia. Accusantium modi assumenda deleniti quia et voluptatibus.\n \rVoluptas deleniti quia. Et velit quos. Ipsum aut aut repellat dignissimos consequuntur dolorum et nostrum molestias. Adipisci eveniet sed repudiandae sapiente repellat ut deserunt aut recusandae.\n \rNon odio dolor sed minima voluptatibus eaque deleniti dolore. Et harum iusto vel voluptas explicabo facere. Hic officiis magnam numquam qui. Cumque sint voluptatem est. Sint maxime vitae mollitia. Omnis corporis dicta aut culpa.\n \rLibero facere omnis impedit maxime mollitia. Aperiam quae provident eos in. Neque nisi aliquam dignissimos.\n \rRem id culpa consequatur cum impedit explicabo ex autem. Ut dolorem magnam sequi et odit labore recusandae. Suscipit expedita quia dolores doloremque consequatur rem est facere aliquam. Aut laboriosam architecto velit. Dolores consequuntur sed in ipsum sit. Autem numquam et sed non et delectus.\n \rPerferendis fugiat debitis occaecati ut ullam magni beatae exercitationem ex. Excepturi ut exercitationem veniam. Adipisci et vel doloribus perspiciatis saepe. Iure atque enim possimus enim fugiat voluptatibus.\n \rDistinctio et minima nobis. Non eos rerum est necessitatibus dolor at quia. Eligendi officia cum est. Quia et quaerat rerum necessitatibus fugit.\n \rSint suscipit aperiam iure est. Qui harum voluptate quos et optio tempore. Et sint consectetur aut in nobis omnis qui. In modi saepe quo vel.\n \rUllam blanditiis aut. Ipsam ea mollitia cum exercitationem sed suscipit corporis iure. Alias ullam reiciendis voluptatem numquam neque ut. Culpa quisquam est. Quae enim molestias accusamus ratione. Tempora animi perferendis et qui libero dolorum esse architecto sed.", + slug: "tuna-reboot-legacy", + images: [ + { + uid: "rc-upload-5kqsqckl7g", + name: "random-image.jpg", + url: "https://picsum.photos/800", + type: "image/jpeg", + size: 141940, + }, + ], + createdAt: "2021-06-21T11:13:32.177Z", + updatedAt: "2021-06-21T11:13:32.249Z", + category: { + id: "07f14be2-72b4-495e-8193-2e4ce70d9be9", + title: "Libyan Dinar Relationships Mexico", + createdAt: "2021-06-21T11:13:32.249Z", + updatedAt: "2021-06-21T11:13:32.249Z", + }, + user: { + id: "ebf27bb0-4e2c-4ea4-9081-1bfb56a966a0", + firstName: "Rosemarie", + lastName: "Schmitt", + email: "rosemarie_Schmitt63@hotmail.com", + status: true, + createdAt: "2021-06-21T11:13:32.073Z", + updatedAt: "2021-06-21T11:13:32.073Z", + }, + tags: [ + { + id: "f283715b-54a0-43d1-8668-9b68ebc54ca3", + title: "transmitting", + createdAt: "2021-06-21T11:13:32.097Z", + updatedAt: "2021-06-21T11:13:32.097Z", + }, + { + id: "faa9f2ea-2181-4472-aff3-0c7ef9fd9c62", + title: "engineer", + createdAt: "2021-06-21T11:13:32.092Z", + updatedAt: "2021-06-21T11:13:32.092Z", + }, + { + id: "244d1b9a-450c-44fc-914e-2e00c2493171", + title: "red", + createdAt: "2021-06-21T11:13:32.084Z", + updatedAt: "2021-06-21T11:13:32.084Z", + }, + ], + }, + { + status: "published", + id: "177f9271-33e5-4bf9-9700-60b45b52afba", + title: "Fully-configurable red Credit Card Account", + content: + "Quos error consectetur consequatur ipsam temporibus at dolorem ut eos. Quisquam et veritatis est omnis sint perspiciatis tempora consequatur. Voluptatem minima et consequuntur. Cum quia ducimus saepe cumque quae. Magni molestias recusandae. Quia non ipsum.\n \rExpedita autem et inventore. Ipsa aut veniam animi. Deserunt quia accusantium quasi qui.\n \rEaque ut doloremque. Et earum impedit eum. Optio tenetur nulla at.\n \rTempore non quam quasi voluptatum debitis velit adipisci quibusdam unde. Sunt omnis nobis voluptatem tempora dicta libero sed at. Modi non tempore quasi ducimus odio aut. Voluptatem maxime at in aperiam adipisci sit dolorem quasi maxime. Et officiis vitae consequatur et rerum. In et doloribus enim eveniet quia qui deleniti nobis.\n \rExplicabo dicta a. Blanditiis ullam ab consequatur repudiandae. Consequuntur nesciunt assumenda dolore ad numquam temporibus.\n \rNihil explicabo eum non impedit quam consequuntur amet voluptatibus. Voluptatum fuga ad aut eligendi et perspiciatis ea quasi. Reprehenderit consequatur a quasi enim aut placeat quisquam sunt. Dolorem odio quisquam nesciunt ipsa ut. Perspiciatis fugit omnis saepe libero voluptatem.\n \rIpsam quidem excepturi sapiente. Molestiae vitae dolorem veniam ut. Ea soluta quas sequi libero et sapiente. Qui incidunt fugit maxime autem. Non est in quas reprehenderit ut possimus.\n \rQuis porro asperiores ipsa et earum. Distinctio ex odio delectus dolor voluptatibus. Est non et quas nihil et dolorum alias reiciendis. Ratione nam dolor voluptas consequatur exercitationem.\n \rAdipisci quia deleniti molestias eveniet perspiciatis pariatur. Expedita libero hic nihil minima et praesentium. Quia voluptas voluptas velit suscipit possimus ipsam fuga vel velit. Et iure harum et officiis sint.\n \rNihil nam voluptatem ducimus velit ut voluptatem quis qui ducimus. Eos quod ex et ab adipisci voluptates fuga consequatur ipsa. Molestiae est aut dolorum deleniti voluptas. Tenetur alias nulla vel voluptatem quasi vel incidunt. Quam amet commodi odit itaque quae. Sed aliquam earum.", + slug: "fully-configurable-red-credit-card-account", + images: [ + { + uid: "rc-upload-iu2gqw0z9c", + name: "random-image.jpg", + url: "https://picsum.photos/800", + type: "image/jpeg", + size: 141940, + }, + ], + createdAt: "2021-06-21T11:13:34.306Z", + updatedAt: "2021-06-21T11:13:34.600Z", + category: { + id: "38b55d73-1fe4-4f25-b8a5-944e62b53279", + title: "Dynamic Chicken Assistant", + createdAt: "2021-06-21T11:13:34.600Z", + updatedAt: "2021-06-21T11:13:34.600Z", + }, + user: { + id: "a990da0e-0030-42b0-9157-a02ff6389b12", + firstName: "Michaela", + lastName: "Hammes", + email: "michaela.Hammes@hotmail.com", + status: true, + createdAt: "2021-06-21T11:13:34.119Z", + updatedAt: "2021-06-21T11:13:34.119Z", + }, + tags: [ + { + id: "5b54c42d-74b4-4d60-adf6-47a96e39f72c", + title: "row", + createdAt: "2021-06-21T11:13:34.137Z", + updatedAt: "2021-06-21T11:13:34.137Z", + }, + { + id: "f910d052-6e00-46a8-9e6f-703674dd2926", + title: "whiteboard", + createdAt: "2021-06-21T11:13:34.131Z", + updatedAt: "2021-06-21T11:13:34.131Z", + }, + { + id: "627038fd-6366-429e-be03-1052be2cc77d", + title: "architect", + createdAt: "2021-06-21T11:13:34.123Z", + updatedAt: "2021-06-21T11:13:34.123Z", + }, + ], + }, + { + status: "draft", + id: "1a8f237f-759d-428d-8ab1-5e5b98599504", + title: "Wireless Investor Wooden", + content: + "Dolor rerum id incidunt. Tenetur nam odio corporis. Et quod inventore facere qui ut aut quia.\n \rDistinctio voluptas sit aut odit dolore ut hic. Occaecati ut ipsam sapiente suscipit nisi et omnis eveniet. Omnis accusantium eum. Mollitia dolores accusamus repudiandae occaecati sed enim in voluptate. Ut id velit et et atque et assumenda itaque officiis. Repudiandae cumque consectetur et numquam et saepe veniam.\n \rQuo autem dolores quo voluptatem suscipit nulla dolorum cum laudantium. Sunt aut saepe eum cumque perspiciatis. Id nobis doloremque nesciunt vero architecto.\n \rOmnis et quia esse perferendis cum in. Et et delectus vitae nihil. Distinctio magni nesciunt. Sint dolorum voluptatem quia optio veniam alias. Rerum magnam occaecati rem molestias. Voluptatem natus magni est voluptas voluptas.\n \rUt eum autem recusandae libero vel nemo fugiat est itaque. Eveniet quasi officia fugiat. Dolor temporibus qui mollitia sint reiciendis. Rerum in id. Laudantium dignissimos nobis.\n \rEsse et sint ratione placeat et similique alias modi. Ipsam laudantium reprehenderit officia illo dolor omnis id dolor. Fugiat vero molestias aliquid iusto nostrum repellendus error.\n \rInventore dolor sit pariatur amet est dolores reiciendis. Aut dolor vitae deserunt. Sit cumque nam voluptas corrupti accusamus ea et eligendi hic. Amet eos facilis eaque fugit ut et dolorem qui. Et esse vitae facilis qui labore voluptate aut autem. Et maiores qui.\n \rMinus officiis tempore magni dolorem itaque et. Accusamus magnam distinctio. Quisquam officia dolorem quam aut voluptatem quasi animi.\n \rEt quia et eos quaerat officia modi. Temporibus et ipsa saepe eos velit. Qui dicta est est ea qui odit beatae ut perferendis.\n \rRepellendus officia et magnam veritatis tempora soluta molestiae atque. Aut perferendis et. Exercitationem quia ea. Fugiat vel rerum ullam. Eos aut consequatur harum perspiciatis consectetur quisquam eius. Placeat dolorem enim aliquid minus.", + slug: "wireless-investor-wooden", + images: [ + { + uid: "rc-upload-focb6qzs3j", + name: "random-image.jpg", + url: "https://picsum.photos/800", + type: "image/jpeg", + size: 141940, + }, + ], + createdAt: "2021-06-21T11:13:33.881Z", + updatedAt: "2021-06-21T11:13:34.073Z", + category: { + id: "ba984fd7-be8c-46c6-80b6-7f39075afaf2", + title: "Salad Somali Shilling Bandwidth-Monitored", + createdAt: "2021-06-21T11:13:34.073Z", + updatedAt: "2021-06-21T11:13:34.073Z", + }, + user: { + id: "6487ea84-1878-4647-ba89-99cd45b4fd76", + firstName: "Jailyn", + lastName: "Runte", + email: "jailyn_Runte@hotmail.com", + status: true, + createdAt: "2021-06-21T11:13:33.725Z", + updatedAt: "2021-06-21T11:13:33.725Z", + }, + tags: [ + { + id: "9ef69848-0808-4363-92f9-83db545b456c", + title: "disintermediate", + createdAt: "2021-06-21T11:13:33.731Z", + updatedAt: "2021-06-21T11:13:33.731Z", + }, + { + id: "c0a81769-0811-4b0b-acd6-a535b95870e7", + title: "mozambique", + createdAt: "2021-06-21T11:13:33.736Z", + updatedAt: "2021-06-21T11:13:33.736Z", + }, + { + id: "598af14d-2599-444a-adca-faf2e1275806", + title: "driver", + createdAt: "2021-06-21T11:13:33.748Z", + updatedAt: "2021-06-21T11:13:33.748Z", + }, + ], + }, + { + status: "published", + id: "1b175cdc-4407-49d9-82cd-35e9f31afec2", + title: "User-friendly New Mexico Bedfordshire", + content: + "Nobis autem asperiores ut ea architecto dignissimos. Velit id magnam quod corrupti adipisci. Ratione ut saepe rerum omnis dolores perspiciatis sed eos. Recusandae quia animi sint perferendis vero eius sunt commodi ut.\n \rPraesentium ut pariatur voluptatem minima repellendus. Dolor deleniti non cum nostrum accusantium. A deleniti est eveniet cupiditate quo praesentium. Quia sed illo aut voluptas.\n \rIpsum in et voluptatem. Neque qui rerum et quasi sint quis voluptates. Nobis eum dolores ut vel enim officiis. Adipisci accusantium non voluptas eveniet odio eius.\n \rDolore provident pariatur et dignissimos molestiae aut id nisi. Voluptas praesentium aliquid debitis natus sapiente sunt laudantium perferendis. Architecto possimus aut laudantium explicabo quia expedita quasi atque. Ut ut occaecati vel voluptas assumenda incidunt cumque totam.\n \rMollitia facere id. Delectus quo cum et eligendi. Qui non distinctio praesentium nihil tempora ea.\n \rUt accusantium autem occaecati. Eos quae minus autem neque et quis voluptates earum eos. Excepturi veniam dolores laborum porro dolorem dolores omnis ducimus velit. Nobis earum molestias similique. Dolorem sint recusandae ea nihil voluptatem nihil rerum. Autem a fugiat eligendi tempora ut ipsa.\n \rHarum soluta fuga. Esse non praesentium quo rerum velit labore. Et in officia veritatis ipsam qui distinctio. Culpa aut quia explicabo eum et dicta sed quia. In adipisci neque consequatur at.\n \rRerum sed aut nisi et enim ut. Qui at quis dicta omnis quia beatae id. Fugiat ducimus molestiae. Nisi ratione provident. Ipsam tempora cum vel odit assumenda quibusdam debitis.\n \rDoloremque repellendus voluptatem quis. Quo et eos eligendi libero quia tempora illum rerum. Quas eum et accusamus tenetur esse in eum rerum qui. Ratione vero perspiciatis aut. Aut aliquid cum saepe. Voluptatem quo molestiae sapiente voluptas.\n \rEt ut et velit officia sequi omnis placeat. Quia dignissimos a et deleniti tenetur ea. Asperiores et magnam earum quasi. Neque explicabo autem voluptate quasi ut. Similique repellendus optio non accusantium aut assumenda et quas.", + slug: "user-friendly-new-mexico-bedfordshire", + images: [ + { + uid: "rc-upload-an6pptxt4k", + name: "random-image.jpg", + url: "https://picsum.photos/800", + type: "image/jpeg", + size: 141940, + }, + ], + createdAt: "2021-06-21T11:13:32.165Z", + updatedAt: "2021-06-21T11:13:32.249Z", + category: { + id: "07f14be2-72b4-495e-8193-2e4ce70d9be9", + title: "Libyan Dinar Relationships Mexico", + createdAt: "2021-06-21T11:13:32.249Z", + updatedAt: "2021-06-21T11:13:32.249Z", + }, + user: { + id: "ebf27bb0-4e2c-4ea4-9081-1bfb56a966a0", + firstName: "Rosemarie", + lastName: "Schmitt", + email: "rosemarie_Schmitt63@hotmail.com", + status: true, + createdAt: "2021-06-21T11:13:32.073Z", + updatedAt: "2021-06-21T11:13:32.073Z", + }, + tags: [ + { + id: "244d1b9a-450c-44fc-914e-2e00c2493171", + title: "red", + createdAt: "2021-06-21T11:13:32.084Z", + updatedAt: "2021-06-21T11:13:32.084Z", + }, + { + id: "f283715b-54a0-43d1-8668-9b68ebc54ca3", + title: "transmitting", + createdAt: "2021-06-21T11:13:32.097Z", + updatedAt: "2021-06-21T11:13:32.097Z", + }, + { + id: "faa9f2ea-2181-4472-aff3-0c7ef9fd9c62", + title: "engineer", + createdAt: "2021-06-21T11:13:32.092Z", + updatedAt: "2021-06-21T11:13:32.092Z", + }, + ], + }, + ], + count: 10, + total: 135, + page: 1, + pageCount: 14, + }, + [ + "Server", + "nginx/1.17.10", + "Date", + "Mon, 21 Jun 2021 12:09:32 GMT", + "Content-Type", + "application/json; charset=utf-8", + "Content-Length", + "32420", + "Connection", + "close", + "Vary", + "Accept-Encoding", + "X-Powered-By", + "Express", + "Access-Control-Allow-Origin", + "*", + "ETag", + 'W/"7ea4-2SqDnfRUB3QcPmud7/kA9AUl71g"', + ], + ); + +nock("https://api.nestjsx-crud.refine.dev:443", { encodedQueryParams: true }) + .get("/posts") + .query({ + s: "%7B%22%24and%22%3A%5B%7B%22category.id%22%3A%7B%22%24in%22%3A%5B%2273bdc4c0-0cc2-49bb-bd6f-550deb795468%22%5D%7D%7D%5D%7D", + limit: "10", + page: "1", + offset: "0", + }) + .reply( + 200, + { + data: [ + { + status: "draft", + id: "24b016ee-6779-425b-99c0-2e939e253cfd", + title: "Implement Personal Loan Account bifurcated", + content: + "Voluptate illum ut et. Veniam minima aut repudiandae dolor in sequi labore dignissimos. Sed non voluptas voluptatem soluta veniam in magnam omnis. Ipsa neque recusandae distinctio commodi enim quisquam animi. Vero sint sed.\n \rId quidem aspernatur sequi aut accusamus pariatur. Velit necessitatibus odio eum. Veritatis sed nostrum a voluptate.\n \rExpedita nihil porro sint quis vero vero qui quo inventore. Sint et incidunt earum necessitatibus qui nulla et qui consectetur. Sint est dicta libero amet velit aliquam dolores ut et. Velit dicta placeat culpa quibusdam cumque ad. Dolor necessitatibus dolore quia recusandae. Mollitia sapiente autem vero aut sunt consequatur iusto et facilis.\n \rNesciunt ut velit perferendis culpa. Velit quod architecto enim aut. Quas et quo ipsum voluptates beatae voluptas aspernatur iusto deserunt.\n \rAutem dolores facere aut voluptatum. Et occaecati facilis esse reprehenderit et. Magni eos ut rerum repellat perspiciatis molestias asperiores. Facilis odit aliquam qui. Et voluptatem in dolor omnis.\n \rSapiente recusandae id. Voluptas vitae non consequatur est. Veniam et debitis perferendis occaecati. Dolor et voluptates voluptas tempora. Voluptatem et mollitia et asperiores voluptas sit expedita. Iusto maiores itaque.\n \rIn ea eum quia. Itaque deleniti praesentium. Facere nesciunt odit cupiditate doloremque voluptatum. Sunt accusamus culpa officiis facilis eius omnis tempora.\n \rOmnis tempora magni amet repellat aut fugit ut maxime mollitia. Iusto voluptas molestiae molestias ea eaque sunt facere omnis. Esse et suscipit pariatur et aliquam molestiae veritatis provident error. Quia sunt dolore dolores velit impedit sunt molestiae nesciunt nihil. Repudiandae aut ex consequatur recusandae maxime quae.\n \rNon sunt ut laborum reprehenderit sit sed consequatur veritatis quia. Voluptate nemo id fugit vel ut. Asperiores ut provident eos velit ut fuga nesciunt aspernatur. Velit modi eos possimus non. Quis cupiditate quod.\n \rEos ipsum soluta rerum. Ut molestiae laborum fugiat voluptatem iste non qui sit voluptates. In eaque temporibus. Quasi beatae qui. Eum nemo sapiente molestias quod dolor.", + slug: "implement-personal-loan-account-bifurcated", + images: [ + { + uid: "rc-upload-rdc6wuyk7t", + name: "random-image.jpg", + url: "https://picsum.photos/800", + type: "image/jpeg", + size: 141940, + }, + ], + createdAt: "2021-06-21T11:13:34.722Z", + updatedAt: "2021-06-21T11:13:35.195Z", + category: { + id: "73bdc4c0-0cc2-49bb-bd6f-550deb795468", + title: "Deposit Capacitor Hdd", + createdAt: "2021-06-21T11:13:35.195Z", + updatedAt: "2021-06-21T11:13:35.195Z", + }, + user: { + id: "0e1e8a86-b09e-4df4-80f3-5047d57c9cdf", + firstName: "Ansley", + lastName: "McCullough", + email: "ansley_McCullough53@yahoo.com", + status: true, + createdAt: "2021-06-21T11:13:34.699Z", + updatedAt: "2021-06-21T11:13:34.699Z", + }, + tags: [ + { + id: "e51fd147-2efd-409c-acd5-84d4e27ba71e", + title: "monitor", + createdAt: "2021-06-21T11:13:34.703Z", + updatedAt: "2021-06-21T11:13:34.703Z", + }, + { + id: "02058bdc-41df-45ed-9884-e9ff9dc2bd18", + title: "kuwait", + createdAt: "2021-06-21T11:13:34.707Z", + updatedAt: "2021-06-21T11:13:34.707Z", + }, + { + id: "6dedd9d7-1cc8-4016-ba03-78c2ba806f1a", + title: "framework", + createdAt: "2021-06-21T11:13:34.712Z", + updatedAt: "2021-06-21T11:13:34.712Z", + }, + ], + }, + { + status: "draft", + id: "011edb32-f071-424a-8747-81d894f52906", + title: "Games initiatives online", + content: + "Mollitia eius rerum temporibus omnis dolorem. Asperiores occaecati ut consequuntur est et reprehenderit id excepturi. Quibusdam dolorem ea eos. Ducimus ut alias. Explicabo adipisci natus. Sit consequatur non enim quo harum quis.\n \rUt reiciendis aut ad. Est et dolorem. Odio omnis non ea. Sapiente quos optio architecto eos aspernatur vel est.\n \rDoloribus nisi dignissimos eum sed molestias natus. Aperiam ea aliquid eos doloribus consequatur et et. Voluptas sed ut. Et consequatur aut voluptatem dicta necessitatibus sunt quia. Non delectus molestiae. Quasi praesentium dignissimos facilis et qui et.\n \rEt qui et et voluptatibus voluptate alias. Quibusdam ut omnis sunt pariatur. Explicabo facere atque facilis laboriosam. Reprehenderit rem ratione sed laborum omnis alias illo quas aperiam. At porro animi sit eaque dolore. Tempora autem numquam eos voluptas.\n \rMinima non minus tempora deleniti quia. Enim maiores sit. Soluta a id iusto illo placeat dolore delectus praesentium sunt.\n \rPorro esse quis atque veritatis iusto assumenda reprehenderit cupiditate. Provident saepe blanditiis fugit neque amet ad reprehenderit. Dolores consectetur omnis officia aut odio.\n \rDolorum totam consectetur maxime. Mollitia ea quo libero distinctio doloremque ipsam. Inventore delectus qui consequuntur sapiente ea maiores doloribus hic et. Sapiente vel ut vitae ad omnis incidunt. Odio deleniti hic tempore ut omnis ullam. Eveniet provident cum cupiditate rerum.\n \rVoluptas laboriosam quas temporibus illo. Ducimus natus sit possimus blanditiis impedit illum sit dolorem. Corporis officiis aut ipsam laboriosam aut. Voluptatem nemo reiciendis quam omnis quia. Alias voluptatibus et non libero.\n \rDolores eaque in autem voluptatum sit officia aut. Accusamus est dolores repellendus sit nihil. Ut quibusdam recusandae laborum sequi. Veritatis nesciunt ut eius iure. Fuga odio rem voluptas et quasi a culpa a.\n \rEa voluptas recusandae aperiam occaecati quae dicta quibusdam fugiat. Illo quas maiores qui vero consequuntur pariatur. Amet voluptas eum sit. Ut minus non sunt tenetur. Nihil est quae vitae minus repellat cum minus nisi.", + slug: "games-initiatives-online", + images: [ + { + uid: "rc-upload-aypuqgh2hm", + name: "random-image.jpg", + url: "https://picsum.photos/800", + type: "image/jpeg", + size: 141940, + }, + ], + createdAt: "2021-06-21T11:13:34.806Z", + updatedAt: "2021-06-21T11:13:35.195Z", + category: { + id: "73bdc4c0-0cc2-49bb-bd6f-550deb795468", + title: "Deposit Capacitor Hdd", + createdAt: "2021-06-21T11:13:35.195Z", + updatedAt: "2021-06-21T11:13:35.195Z", + }, + user: { + id: "0e1e8a86-b09e-4df4-80f3-5047d57c9cdf", + firstName: "Ansley", + lastName: "McCullough", + email: "ansley_McCullough53@yahoo.com", + status: true, + createdAt: "2021-06-21T11:13:34.699Z", + updatedAt: "2021-06-21T11:13:34.699Z", + }, + tags: [ + { + id: "e51fd147-2efd-409c-acd5-84d4e27ba71e", + title: "monitor", + createdAt: "2021-06-21T11:13:34.703Z", + updatedAt: "2021-06-21T11:13:34.703Z", + }, + { + id: "02058bdc-41df-45ed-9884-e9ff9dc2bd18", + title: "kuwait", + createdAt: "2021-06-21T11:13:34.707Z", + updatedAt: "2021-06-21T11:13:34.707Z", + }, + { + id: "6dedd9d7-1cc8-4016-ba03-78c2ba806f1a", + title: "framework", + createdAt: "2021-06-21T11:13:34.712Z", + updatedAt: "2021-06-21T11:13:34.712Z", + }, + ], + }, + { + status: "published", + id: "52e29ad3-886e-45ff-8511-d4b2b3c80f28", + title: "Mesh value-added Personal Loan Account", + content: + "Quis perspiciatis voluptas ipsa perferendis quo vel cupiditate dolor qui. Non et pariatur ut. Magnam ab molestiae. Praesentium officia occaecati sunt officia quia quia error. Inventore animi qui. Velit in accusantium sint quae provident cumque qui.\n \rDelectus enim molestiae repellat error nobis voluptas. Tempora et architecto consectetur aliquam quod aliquid repellendus ut. Ut aut cum et quod laboriosam. Harum aut aut praesentium repellat. Exercitationem officia est aut.\n \rSit iusto eaque quam aut numquam voluptatem a ut voluptates. Eaque ad dolorem repudiandae. Modi labore qui.\n \rVel temporibus laboriosam vitae. Id beatae illo aut eos quibusdam et. Dolores provident amet facilis ut occaecati velit alias qui. Repudiandae mollitia velit. Consequatur occaecati quasi deserunt ea.\n \rMolestiae et repellat quia voluptatem expedita quas aut sint eum. Recusandae molestiae id eius debitis. Dolorum non est illum ipsa cum porro. Nihil sapiente quaerat optio maxime neque quaerat consequatur assumenda. Hic tempora dignissimos ut error vel. Sit quis officia ab esse.\n \rPlaceat enim occaecati voluptas deleniti aut. Cum dolore suscipit a exercitationem. Dolores commodi excepturi consequatur. Eveniet sed aut similique eum. Amet modi neque et reiciendis sit.\n \rIllo enim omnis aperiam ducimus placeat facilis distinctio doloremque. Quasi porro quae et. Asperiores ut consequatur consequatur vel minus consequatur eligendi voluptatem laboriosam. Incidunt ipsa culpa quaerat. Aliquid molestiae sed sed ut consequatur ea omnis.\n \rQuis saepe soluta debitis nemo. Et laboriosam consequuntur cupiditate error. Expedita sequi occaecati quo porro laudantium sunt atque excepturi.\n \rNon vel quibusdam accusantium dolor nulla. Sed aut incidunt in et culpa. Iure nulla amet corporis neque similique eos.\n \rCorporis doloribus aliquid in porro error. Sed enim accusamus maiores consequatur. Debitis dolorem rerum suscipit non unde numquam. Sit sed nesciunt nisi necessitatibus numquam numquam et. Omnis veritatis quo temporibus neque est officia culpa quia est. Et ullam dolor tempore.", + slug: "mesh-value-added-personal-loan-account", + images: [ + { + uid: "rc-upload-5noyzt0blz", + name: "random-image.jpg", + url: "https://picsum.photos/800", + type: "image/jpeg", + size: 141940, + }, + ], + createdAt: "2021-06-21T11:13:34.890Z", + updatedAt: "2021-06-21T11:13:35.195Z", + category: { + id: "73bdc4c0-0cc2-49bb-bd6f-550deb795468", + title: "Deposit Capacitor Hdd", + createdAt: "2021-06-21T11:13:35.195Z", + updatedAt: "2021-06-21T11:13:35.195Z", + }, + user: { + id: "0e1e8a86-b09e-4df4-80f3-5047d57c9cdf", + firstName: "Ansley", + lastName: "McCullough", + email: "ansley_McCullough53@yahoo.com", + status: true, + createdAt: "2021-06-21T11:13:34.699Z", + updatedAt: "2021-06-21T11:13:34.699Z", + }, + tags: [ + { + id: "e51fd147-2efd-409c-acd5-84d4e27ba71e", + title: "monitor", + createdAt: "2021-06-21T11:13:34.703Z", + updatedAt: "2021-06-21T11:13:34.703Z", + }, + { + id: "02058bdc-41df-45ed-9884-e9ff9dc2bd18", + title: "kuwait", + createdAt: "2021-06-21T11:13:34.707Z", + updatedAt: "2021-06-21T11:13:34.707Z", + }, + { + id: "6dedd9d7-1cc8-4016-ba03-78c2ba806f1a", + title: "framework", + createdAt: "2021-06-21T11:13:34.712Z", + updatedAt: "2021-06-21T11:13:34.712Z", + }, + ], + }, + { + status: "draft", + id: "402a3b6f-dc3d-4637-99be-e1a23c0361f7", + title: "Lime Bacon Guam", + content: + "Temporibus ut nobis non dignissimos porro quo sunt. Velit voluptatem quod eius ea repellendus dolorem. At dolorum molestiae adipisci possimus. Atque et ea id quisquam omnis ut. Eius qui laboriosam molestias explicabo eos. Consequatur dolorem vitae sit iste provident atque numquam.\n \rQui et ea nostrum voluptate quia magnam velit. Laudantium porro placeat. Maiores deleniti nobis iusto delectus voluptas praesentium.\n \rMagni officia voluptatum maiores reprehenderit aut. Et optio dolor et aut qui minus. Reprehenderit dolorem aut voluptatem qui consequatur. Eligendi odio iusto porro at. Eveniet accusantium eaque eum vero provident. Ut laudantium necessitatibus veritatis fugit.\n \rRepudiandae debitis facere enim eaque natus ut alias recusandae cupiditate. Eos cupiditate consequatur consectetur deserunt ea sit officia aperiam est. Eum sit quidem debitis ipsam ut qui nesciunt. Voluptas voluptatem aliquam odit voluptatem dolorem at reiciendis. Porro omnis dolores nemo non.\n \rMagni autem quasi dolores ratione praesentium quas sit. Voluptate voluptatem odit aliquam deserunt architecto. Accusantium itaque quo magni et aliquid eum. Minus vero minus non veritatis reprehenderit corrupti. Omnis ab itaque saepe maxime.\n \rDignissimos reprehenderit consequatur. Dignissimos incidunt accusamus. Repellat veniam maxime neque sit sunt quo aut est. Sed cum praesentium reiciendis sint. Quia veritatis et voluptas fuga numquam.\n \rRerum assumenda rem tempora veritatis velit similique. Rerum sit autem minus. Hic fuga aliquam ea consequuntur ut praesentium omnis ad. Voluptas vel quo veritatis eius.\n \rIpsam vero ex possimus est. Harum voluptates repellat fugiat asperiores esse ut vero non. Laborum maiores reprehenderit est omnis nesciunt quaerat omnis non. Magnam animi porro delectus eum. Reiciendis eos maiores ut fugit. Autem rerum neque qui vel modi sunt suscipit praesentium deserunt.\n \rTotam nulla blanditiis asperiores. Ex voluptas reiciendis ut voluptates aut non ipsam. Rerum eos facilis. Neque totam hic enim quia maxime et. Repudiandae quasi perspiciatis molestias qui est deleniti voluptatum saepe dolorem.\n \rSed consequatur quis animi pariatur et labore natus. Debitis reprehenderit odio ducimus maxime aut modi. Voluptas quidem ut nisi quam est ducimus illum. Vel sed dolor beatae officiis ut.", + slug: "lime-bacon-guam", + images: [ + { + uid: "rc-upload-97i8s9lb61", + name: "random-image.jpg", + url: "https://picsum.photos/800", + type: "image/jpeg", + size: 141940, + }, + ], + createdAt: "2021-06-21T11:13:34.906Z", + updatedAt: "2021-06-21T11:13:35.195Z", + category: { + id: "73bdc4c0-0cc2-49bb-bd6f-550deb795468", + title: "Deposit Capacitor Hdd", + createdAt: "2021-06-21T11:13:35.195Z", + updatedAt: "2021-06-21T11:13:35.195Z", + }, + user: { + id: "0e1e8a86-b09e-4df4-80f3-5047d57c9cdf", + firstName: "Ansley", + lastName: "McCullough", + email: "ansley_McCullough53@yahoo.com", + status: true, + createdAt: "2021-06-21T11:13:34.699Z", + updatedAt: "2021-06-21T11:13:34.699Z", + }, + tags: [ + { + id: "e51fd147-2efd-409c-acd5-84d4e27ba71e", + title: "monitor", + createdAt: "2021-06-21T11:13:34.703Z", + updatedAt: "2021-06-21T11:13:34.703Z", + }, + { + id: "02058bdc-41df-45ed-9884-e9ff9dc2bd18", + title: "kuwait", + createdAt: "2021-06-21T11:13:34.707Z", + updatedAt: "2021-06-21T11:13:34.707Z", + }, + { + id: "6dedd9d7-1cc8-4016-ba03-78c2ba806f1a", + title: "framework", + createdAt: "2021-06-21T11:13:34.712Z", + updatedAt: "2021-06-21T11:13:34.712Z", + }, + ], + }, + { + status: "draft", + id: "2cf82473-8ff3-43da-abbe-914f18954fb8", + title: "Human Cambodia El Salvador Colon US Dollar", + content: + "Repudiandae saepe repellendus natus. Porro quod numquam animi quos totam eos eos vitae recusandae. Dolor accusantium adipisci ab voluptatem impedit maxime a. Id harum aut. Dolore quis dolores quasi amet sit est natus minima error.\n \rVoluptatem dolorem autem ut. Error velit et tenetur sed dolore cumque quia. Fuga eos accusantium. Voluptatem pariatur vitae qui.\n \rIpsa molestiae ea libero non. Velit voluptatum amet est quos aliquam quod. Voluptas temporibus laboriosam id dolor dolores aut voluptatem.\n \rQuis nesciunt et repellendus sit qui enim autem. Quod perspiciatis eius sed. Ipsum natus explicabo nulla magni eaque velit eveniet id. Eos quia voluptate iusto nulla. Laudantium et eligendi ea inventore libero voluptas.\n \rEos dolores quidem ut corrupti quae culpa tempore. Tenetur impedit veritatis architecto placeat nisi quis praesentium repellendus officiis. Nemo eveniet exercitationem repellendus quo iste possimus nihil. Accusantium incidunt omnis sequi unde autem. Aut et odit. Id laudantium dolorum rerum.\n \rDolores non consequatur quia eum et odit aut tenetur provident. Et consequatur est. Et quos ipsum. Ut non eligendi quam voluptate.\n \rDicta qui ullam illo voluptatem odio aut. Non iusto eligendi. In et odio omnis id cupiditate.\n \rVoluptatem sit aut ea. Eum numquam quae accusantium occaecati ea. Voluptas dolorum enim et. Dolorum omnis quae possimus qui. Aut voluptatem quis nostrum libero omnis facere. Iure beatae sed velit.\n \rQuam earum reprehenderit voluptates quidem sit quo. Soluta natus consectetur aut labore dolores vel rerum. Nihil quas sed reiciendis harum labore hic ea dolor culpa.\n \rNeque nihil inventore sed voluptas est rerum aut labore eligendi. Magnam voluptas et omnis laborum beatae soluta sit illum. Reiciendis nisi doloremque sint quae. Error nesciunt qui est placeat dolorum sit nam aliquam. In aperiam quis et.", + slug: "human-cambodia-el-salvador-colon-us-dollar", + images: [ + { + uid: "rc-upload-c8v5f16teb", + name: "random-image.jpg", + url: "https://picsum.photos/800", + type: "image/jpeg", + size: 141940, + }, + ], + createdAt: "2021-06-21T11:13:34.920Z", + updatedAt: "2021-06-21T11:13:35.195Z", + category: { + id: "73bdc4c0-0cc2-49bb-bd6f-550deb795468", + title: "Deposit Capacitor Hdd", + createdAt: "2021-06-21T11:13:35.195Z", + updatedAt: "2021-06-21T11:13:35.195Z", + }, + user: { + id: "0e1e8a86-b09e-4df4-80f3-5047d57c9cdf", + firstName: "Ansley", + lastName: "McCullough", + email: "ansley_McCullough53@yahoo.com", + status: true, + createdAt: "2021-06-21T11:13:34.699Z", + updatedAt: "2021-06-21T11:13:34.699Z", + }, + tags: [ + { + id: "e51fd147-2efd-409c-acd5-84d4e27ba71e", + title: "monitor", + createdAt: "2021-06-21T11:13:34.703Z", + updatedAt: "2021-06-21T11:13:34.703Z", + }, + { + id: "02058bdc-41df-45ed-9884-e9ff9dc2bd18", + title: "kuwait", + createdAt: "2021-06-21T11:13:34.707Z", + updatedAt: "2021-06-21T11:13:34.707Z", + }, + { + id: "6dedd9d7-1cc8-4016-ba03-78c2ba806f1a", + title: "framework", + createdAt: "2021-06-21T11:13:34.712Z", + updatedAt: "2021-06-21T11:13:34.712Z", + }, + ], + }, + { + status: "draft", + id: "2bec3c49-0016-4f18-bbf9-153b7a77b897", + title: "Gloves Shoes compressing", + content: + "Sapiente facere similique dolore alias tempora. Dolorum illum et voluptatum et quas modi vel eum. Aliquam aut aspernatur enim fugit iure distinctio ut. Dicta voluptas numquam et impedit et perferendis aut voluptatem dolore. Sed ex repudiandae voluptatem corrupti quo id quia. Commodi deleniti quaerat nobis ullam quidem omnis dolor et odit.\n \rRepellat et aperiam quas amet libero officia dignissimos consectetur suscipit. Pariatur ut voluptas omnis odit. Esse quisquam rerum in tempora dolor laboriosam non quo ducimus. Repellat quia sapiente.\n \rEaque debitis quia dolorum corporis. Et sint delectus sint vel dicta error sunt quis. Quaerat aut molestias laudantium. Doloremque ex ducimus quo consequatur assumenda eos.\n \rNam iste aliquid porro deserunt impedit eaque. Quo esse exercitationem sunt sed voluptatum aperiam. Animi consectetur veniam possimus quo ea dolor. Voluptas culpa repellendus eos distinctio recusandae laborum eum aut possimus. Eligendi magni blanditiis neque error aspernatur dolor.\n \rQuaerat reprehenderit est velit aut voluptatibus voluptatum. Ut ut dolore qui quidem. Qui rem est magnam ullam. Dolorem est sapiente nihil voluptatem.\n \rDicta et harum dolores vitae consequatur. Eum dolores nulla aut. Vel qui in expedita amet dignissimos unde. Sint dolore ad et provident excepturi.\n \rQui magnam enim consequuntur perspiciatis iusto quaerat nemo. Dolores harum labore et blanditiis id minus ullam laboriosam. Et ab molestias. Nihil autem dolor est blanditiis id. Doloremque dolor molestias iusto vero dolorem nobis et quasi autem. At culpa voluptate omnis enim.\n \rFuga eius dolor veniam veniam. Sint sapiente adipisci id hic nam laudantium officiis deserunt error. Sequi cupiditate sequi nulla praesentium sunt. Minima nisi eaque quis fugit unde officia voluptatibus non nihil. Assumenda culpa est ipsam eligendi officiis nostrum perferendis. Aut perspiciatis sed at deserunt quia quidem provident fugit.\n \rIpsam quaerat dolorum et ad a voluptatem quis delectus. Illo porro nemo quisquam rerum dolorem. Totam accusantium quisquam possimus sint quam alias. Et voluptatibus suscipit.\n \rDolores laudantium aut quo nulla cum quidem sit neque est. Et iure quas voluptas non adipisci sequi. Aut amet rem veniam debitis fugiat aspernatur.", + slug: "gloves-shoes-compressing", + images: [ + { + uid: "rc-upload-wlcvjxusbz", + name: "random-image.jpg", + url: "https://picsum.photos/800", + type: "image/jpeg", + size: 141940, + }, + ], + createdAt: "2021-06-21T11:13:34.947Z", + updatedAt: "2021-06-21T11:13:35.195Z", + category: { + id: "73bdc4c0-0cc2-49bb-bd6f-550deb795468", + title: "Deposit Capacitor Hdd", + createdAt: "2021-06-21T11:13:35.195Z", + updatedAt: "2021-06-21T11:13:35.195Z", + }, + user: { + id: "0e1e8a86-b09e-4df4-80f3-5047d57c9cdf", + firstName: "Ansley", + lastName: "McCullough", + email: "ansley_McCullough53@yahoo.com", + status: true, + createdAt: "2021-06-21T11:13:34.699Z", + updatedAt: "2021-06-21T11:13:34.699Z", + }, + tags: [ + { + id: "e51fd147-2efd-409c-acd5-84d4e27ba71e", + title: "monitor", + createdAt: "2021-06-21T11:13:34.703Z", + updatedAt: "2021-06-21T11:13:34.703Z", + }, + { + id: "02058bdc-41df-45ed-9884-e9ff9dc2bd18", + title: "kuwait", + createdAt: "2021-06-21T11:13:34.707Z", + updatedAt: "2021-06-21T11:13:34.707Z", + }, + { + id: "6dedd9d7-1cc8-4016-ba03-78c2ba806f1a", + title: "framework", + createdAt: "2021-06-21T11:13:34.712Z", + updatedAt: "2021-06-21T11:13:34.712Z", + }, + ], + }, + { + status: "published", + id: "46adc294-bdac-46b6-a8ba-cdea8ec81d9f", + title: "Redundant Practical Tokelau", + content: + "In facere aspernatur dolor deserunt quis odio eligendi nulla omnis. Est voluptates eum perferendis doloribus ut et quidem et. Voluptas corporis quis fugiat aut sunt. Consequatur sapiente deleniti iste ut quae est non similique. Expedita perspiciatis non exercitationem necessitatibus quo eveniet sunt. Minus possimus et ut fugiat ipsa.\n \rDoloremque sed dolores perferendis consectetur cum similique. Reprehenderit aut saepe sit adipisci ratione. Corrupti voluptatem temporibus aperiam. Repellat facere quo perferendis. Dolores cum adipisci adipisci repellendus sed ut modi. Temporibus laudantium repudiandae officia qui distinctio tempora magnam.\n \rMinima dolores officia. Est adipisci omnis consequatur non ut magnam. Quo nulla a veniam perspiciatis quos ipsam. Assumenda quia fuga. Quam neque unde. Et quia quibusdam qui velit nihil reiciendis sapiente accusamus voluptatem.\n \rUt eum consectetur voluptates. Qui rerum doloribus voluptas qui porro nemo veniam. Tempore iste accusamus qui quo. Odio possimus dolores cupiditate.\n \rQuas quasi et id ut aut amet atque. Enim nesciunt magnam eaque ut. Et quas animi. Assumenda molestias iste quis et et. Et a qui est pariatur fuga illum dolorem.\n \rSint dolores consequatur dolorem corporis asperiores mollitia fugit enim qui. Exercitationem illum ab rerum. Accusantium placeat delectus quibusdam est nesciunt soluta. Delectus accusamus ducimus impedit repellat laudantium dolorum consequatur eos. Doloremque omnis qui. Quibusdam animi voluptas et iusto et.\n \rDistinctio ratione laudantium culpa magni incidunt. Nam dolor aut corrupti est rerum consequatur eos. In voluptatibus eum optio nostrum maiores porro. Doloremque rerum modi id maxime ut. Vel est velit recusandae ea ducimus odio pariatur.\n \rNobis in quo ut praesentium voluptate exercitationem. Eos amet et assumenda ea qui laboriosam soluta. Doloribus non omnis dignissimos quaerat praesentium eos alias qui veniam. Sit laborum aspernatur fuga sunt quis repellendus. Quibusdam reprehenderit voluptatem.\n \rIpsa facilis reiciendis quasi molestiae est. Placeat quibusdam veritatis modi sunt et ut. Qui aut sed non sed sint architecto ut aspernatur. Excepturi non sit nemo debitis sint quam. Mollitia facilis in qui maiores vel ratione.\n \rHic voluptate tenetur unde exercitationem. Quam perspiciatis debitis quia sequi sint consequatur et. Fuga vel vel distinctio maxime. Repellat aperiam maiores pariatur eius consequuntur nihil ipsam. Molestiae nihil saepe rem nulla praesentium ut consequatur dolor.", + slug: "redundant-practical-tokelau", + images: [ + { + uid: "rc-upload-dvgg9adn0b", + name: "random-image.jpg", + url: "https://picsum.photos/800", + type: "image/jpeg", + size: 141940, + }, + ], + createdAt: "2021-06-21T11:13:34.990Z", + updatedAt: "2021-06-21T11:13:35.195Z", + category: { + id: "73bdc4c0-0cc2-49bb-bd6f-550deb795468", + title: "Deposit Capacitor Hdd", + createdAt: "2021-06-21T11:13:35.195Z", + updatedAt: "2021-06-21T11:13:35.195Z", + }, + user: { + id: "0e1e8a86-b09e-4df4-80f3-5047d57c9cdf", + firstName: "Ansley", + lastName: "McCullough", + email: "ansley_McCullough53@yahoo.com", + status: true, + createdAt: "2021-06-21T11:13:34.699Z", + updatedAt: "2021-06-21T11:13:34.699Z", + }, + tags: [ + { + id: "e51fd147-2efd-409c-acd5-84d4e27ba71e", + title: "monitor", + createdAt: "2021-06-21T11:13:34.703Z", + updatedAt: "2021-06-21T11:13:34.703Z", + }, + { + id: "02058bdc-41df-45ed-9884-e9ff9dc2bd18", + title: "kuwait", + createdAt: "2021-06-21T11:13:34.707Z", + updatedAt: "2021-06-21T11:13:34.707Z", + }, + { + id: "6dedd9d7-1cc8-4016-ba03-78c2ba806f1a", + title: "framework", + createdAt: "2021-06-21T11:13:34.712Z", + updatedAt: "2021-06-21T11:13:34.712Z", + }, + ], + }, + { + status: "published", + id: "36edd561-9b75-42b3-bc7b-25f821c8f4bb", + title: "Progressive Congolese Franc Generic", + content: + "Doloremque harum qui. Rerum libero ab. Corporis magni autem vel consequuntur doloremque corrupti necessitatibus nesciunt. Doloribus tempora sit dolore facere possimus ullam cum aperiam voluptatem.\n \rNulla soluta omnis voluptate est et illo odio ut. Fugiat eius earum sunt pariatur. Et tenetur qui a voluptas molestiae. Labore repudiandae dicta atque. Voluptatibus eos eos quae vero consequatur. Odit ratione sequi.\n \rVoluptatem rerum amet quidem fugiat reiciendis consequatur eum voluptatem. Alias officia et veniam. Voluptatum et dolorum blanditiis cumque optio nihil doloremque vel quam. Sapiente qui ex. Odit sunt quia ipsa in.\n \rQui autem aut aut nam sequi debitis autem veniam labore. Quis omnis quas minima qui recusandae et quo aut maiores. Voluptates at rerum eius at dolorum. Et error aut deserunt quam eaque est sed qui earum. Vel nostrum cumque possimus numquam.\n \rIste architecto sed quasi ipsum qui ea. Est distinctio cumque est nulla. Aut modi sed labore commodi nisi. Libero consequatur necessitatibus eum temporibus qui quae natus sit et. Illum cumque deleniti et natus cum autem rerum quo iste.\n \rMolestiae itaque autem enim autem atque voluptatem fugit. Sit tempora minus earum quia deleniti quis voluptates eum. Tempore provident accusantium expedita fugiat aliquid. Qui ipsam iste voluptate magni.\n \rUnde aliquam ea commodi impedit et. Tenetur libero molestiae ea nemo suscipit nesciunt. Dolores molestiae dolores. Aspernatur voluptatem doloribus velit beatae. Porro quia ad pariatur similique aut fugiat. Ipsam eligendi illum.\n \rDolore magnam enim dolor nulla saepe consequatur. Ipsam quidem quasi qui molestias cumque ut culpa. Rem velit eos harum odit eligendi voluptatem consequatur qui.\n \rDeserunt aspernatur enim illum qui ut rerum quia totam. Omnis at et a. Distinctio eos debitis non et tenetur dolorum illo quaerat aperiam. Vitae necessitatibus aut ut hic ipsum accusantium et.\n \rSapiente maxime voluptates quos expedita aut. Et est facilis consequuntur molestiae assumenda. Cumque ipsum veritatis beatae dolor iure laudantium vitae ab. Vero est vel architecto non dolorum praesentium.", + slug: "progressive-congolese-franc-generic", + images: [ + { + uid: "rc-upload-am7o29bvpm", + name: "random-image.jpg", + url: "https://picsum.photos/800", + type: "image/jpeg", + size: 141940, + }, + ], + createdAt: "2021-06-21T11:13:35.009Z", + updatedAt: "2021-06-21T11:13:35.195Z", + category: { + id: "73bdc4c0-0cc2-49bb-bd6f-550deb795468", + title: "Deposit Capacitor Hdd", + createdAt: "2021-06-21T11:13:35.195Z", + updatedAt: "2021-06-21T11:13:35.195Z", + }, + user: { + id: "0e1e8a86-b09e-4df4-80f3-5047d57c9cdf", + firstName: "Ansley", + lastName: "McCullough", + email: "ansley_McCullough53@yahoo.com", + status: true, + createdAt: "2021-06-21T11:13:34.699Z", + updatedAt: "2021-06-21T11:13:34.699Z", + }, + tags: [ + { + id: "e51fd147-2efd-409c-acd5-84d4e27ba71e", + title: "monitor", + createdAt: "2021-06-21T11:13:34.703Z", + updatedAt: "2021-06-21T11:13:34.703Z", + }, + { + id: "02058bdc-41df-45ed-9884-e9ff9dc2bd18", + title: "kuwait", + createdAt: "2021-06-21T11:13:34.707Z", + updatedAt: "2021-06-21T11:13:34.707Z", + }, + { + id: "6dedd9d7-1cc8-4016-ba03-78c2ba806f1a", + title: "framework", + createdAt: "2021-06-21T11:13:34.712Z", + updatedAt: "2021-06-21T11:13:34.712Z", + }, + ], + }, + { + status: "draft", + id: "4744d7a6-3696-4917-b861-4494cd6da9d6", + title: "Envisioneer Azerbaijanian Manat haptic", + content: + "Nostrum laboriosam excepturi voluptas accusamus. Minima accusantium sint magni deleniti et. Ea error corporis rem accusamus et sapiente et quibusdam. Voluptatum atque ea iure.\n \rAd earum ut adipisci rerum. Ipsa quia atque praesentium. Quod voluptatibus hic porro recusandae porro. Et qui quaerat officiis voluptates sequi sint quaerat nemo.\n \rDolores ipsam laudantium molestiae est ducimus libero earum. Sapiente reiciendis occaecati modi ut error praesentium. Amet odio quo autem ducimus aut sit. Omnis nobis minima necessitatibus id.\n \rNihil facere omnis dolorem aut et nisi. Et cumque facere atque in sit mollitia. Asperiores assumenda laudantium quia. Distinctio quas ipsa.\n \rEa aliquid sit error quaerat asperiores illum. Voluptatem sunt deserunt accusamus et. Aut qui facilis qui tempora. Sit qui ea hic ab molestiae. Autem impedit culpa praesentium corporis perferendis.\n \rDebitis quibusdam dolor exercitationem voluptatum id eum rerum nulla debitis. Impedit omnis repellendus voluptatem et occaecati numquam quos magnam quidem. Atque voluptas accusantium incidunt necessitatibus reprehenderit voluptas laborum id.\n \rAut aut voluptas sint reprehenderit unde. Adipisci quas qui saepe. Sit necessitatibus ratione laboriosam nisi. Velit nulla ut.\n \rVoluptas sed est maxime accusantium et mollitia dolore. Et est quisquam et et qui dolorem eum. Incidunt distinctio aut consequatur qui quis laborum ut.\n \rUt tempore vitae illum. Ut repellat unde et debitis qui et eos. Blanditiis voluptas nostrum quo eius nam dolorem qui. Ut debitis aliquid distinctio aut doloribus.\n \rIn sed veniam aspernatur. Adipisci minus autem excepturi libero est est atque dolorem. Provident ut at suscipit aperiam commodi et. Non est maxime sunt enim quibusdam odit nulla ut est. Occaecati nemo aliquam officiis molestias earum perspiciatis.", + slug: "envisioneer-azerbaijanian-manat-haptic", + images: [ + { + uid: "rc-upload-cweb1i33cz", + name: "random-image.jpg", + url: "https://picsum.photos/800", + type: "image/jpeg", + size: 141940, + }, + ], + createdAt: "2021-06-21T11:13:35.028Z", + updatedAt: "2021-06-21T11:13:35.195Z", + category: { + id: "73bdc4c0-0cc2-49bb-bd6f-550deb795468", + title: "Deposit Capacitor Hdd", + createdAt: "2021-06-21T11:13:35.195Z", + updatedAt: "2021-06-21T11:13:35.195Z", + }, + user: { + id: "0e1e8a86-b09e-4df4-80f3-5047d57c9cdf", + firstName: "Ansley", + lastName: "McCullough", + email: "ansley_McCullough53@yahoo.com", + status: true, + createdAt: "2021-06-21T11:13:34.699Z", + updatedAt: "2021-06-21T11:13:34.699Z", + }, + tags: [ + { + id: "e51fd147-2efd-409c-acd5-84d4e27ba71e", + title: "monitor", + createdAt: "2021-06-21T11:13:34.703Z", + updatedAt: "2021-06-21T11:13:34.703Z", + }, + { + id: "02058bdc-41df-45ed-9884-e9ff9dc2bd18", + title: "kuwait", + createdAt: "2021-06-21T11:13:34.707Z", + updatedAt: "2021-06-21T11:13:34.707Z", + }, + { + id: "6dedd9d7-1cc8-4016-ba03-78c2ba806f1a", + title: "framework", + createdAt: "2021-06-21T11:13:34.712Z", + updatedAt: "2021-06-21T11:13:34.712Z", + }, + ], + }, + { + status: "draft", + id: "04921a99-4918-4288-adea-f478a9e9e6bd", + title: "Sensor Motorway Bedfordshire", + content: + "Accusantium sed voluptas consequatur veniam ab voluptates. Est ipsam ipsum placeat ea ipsam recusandae consequatur. Quia reprehenderit aut. Eos esse non vero reiciendis distinctio atque qui. Nulla architecto nulla nihil veniam et. Ut maxime incidunt dolor facilis architecto dolorem voluptas.\n \rEum unde consequatur consequatur fuga est tenetur suscipit. Odio est dolorem dolorum nesciunt reiciendis ut. Ab quo eaque nulla et blanditiis aut omnis. Voluptate minima sit nihil.\n \rEt a et at. Nihil non enim est qui quisquam fugiat cumque quaerat. Nemo qui ducimus.\n \rPerspiciatis possimus aliquam corporis esse corrupti minima quam. Vel eos rem necessitatibus aliquid modi sint saepe provident. Aut voluptates dolor rem quae provident.\n \rNihil et voluptates qui molestias voluptate est est atque. Officia et fuga maiores porro dolores rerum exercitationem laboriosam. Est voluptate voluptatibus porro. Nemo enim nam est saepe. Dignissimos perspiciatis molestiae autem illo et vel.\n \rMinus sed accusamus velit dicta natus. Molestias neque labore. Voluptatem exercitationem non at error dolor in dolores. Eveniet animi et. Deleniti aut non voluptas voluptas quia omnis.\n \rVoluptate et consectetur. Laboriosam beatae non delectus aut placeat corrupti. Est ad magni omnis officiis sit aut at alias.\n \rVoluptas deserunt architecto. Esse sed voluptatum. Eos quos consectetur placeat quia nemo.\n \rPossimus ea architecto reiciendis adipisci et. Sit laboriosam et vel nostrum dolore accusamus. Deleniti repellat vel et facilis praesentium fugit voluptates autem.\n \rDolorem vero earum nemo amet laboriosam. Voluptatum sint quis iusto ut minus est quia et est. Doloribus dolorem sequi debitis. Unde consequuntur qui et voluptatibus odio hic repudiandae itaque.", + slug: "sensor-motorway-bedfordshire", + images: [ + { + uid: "rc-upload-5xqp4f2y17", + name: "random-image.jpg", + url: "https://picsum.photos/800", + type: "image/jpeg", + size: 141940, + }, + ], + createdAt: "2021-06-21T11:13:35.045Z", + updatedAt: "2021-06-21T11:13:35.195Z", + category: { + id: "73bdc4c0-0cc2-49bb-bd6f-550deb795468", + title: "Deposit Capacitor Hdd", + createdAt: "2021-06-21T11:13:35.195Z", + updatedAt: "2021-06-21T11:13:35.195Z", + }, + user: { + id: "0e1e8a86-b09e-4df4-80f3-5047d57c9cdf", + firstName: "Ansley", + lastName: "McCullough", + email: "ansley_McCullough53@yahoo.com", + status: true, + createdAt: "2021-06-21T11:13:34.699Z", + updatedAt: "2021-06-21T11:13:34.699Z", + }, + tags: [ + { + id: "e51fd147-2efd-409c-acd5-84d4e27ba71e", + title: "monitor", + createdAt: "2021-06-21T11:13:34.703Z", + updatedAt: "2021-06-21T11:13:34.703Z", + }, + { + id: "02058bdc-41df-45ed-9884-e9ff9dc2bd18", + title: "kuwait", + createdAt: "2021-06-21T11:13:34.707Z", + updatedAt: "2021-06-21T11:13:34.707Z", + }, + { + id: "6dedd9d7-1cc8-4016-ba03-78c2ba806f1a", + title: "framework", + createdAt: "2021-06-21T11:13:34.712Z", + updatedAt: "2021-06-21T11:13:34.712Z", + }, + ], + }, + ], + count: 10, + total: 24, + page: 1, + pageCount: 3, + }, + [ + "Server", + "nginx/1.17.10", + "Date", + "Mon, 21 Jun 2021 12:11:57 GMT", + "Content-Type", + "application/json; charset=utf-8", + "Content-Length", + "33234", + "Connection", + "close", + "Vary", + "Accept-Encoding", + "X-Powered-By", + "Express", + "Access-Control-Allow-Origin", + "*", + "ETag", + 'W/"81d2-vPVofthpCawXT6l6ap6pmAHnCrs"', + ], + ); + +nock("https://api.nestjsx-crud.refine.dev:443", { encodedQueryParams: true }) + .get("/posts") + .query({ + s: "%7B%22%24and%22%3A%5B%7B%22category.id%22%3A%7B%22%24in%22%3A%5B%2273bdc4c0-0cc2-49bb-bd6f-550deb795468%22%5D%7D%7D%5D%7D", + limit: "10", + page: "1", + offset: "0", + "sort%5B0%5D": "id%2CASC", + }) + .reply( + 200, + { + data: [ + { + status: "draft", + id: "011edb32-f071-424a-8747-81d894f52906", + title: "Games initiatives online", + content: + "Mollitia eius rerum temporibus omnis dolorem. Asperiores occaecati ut consequuntur est et reprehenderit id excepturi. Quibusdam dolorem ea eos. Ducimus ut alias. Explicabo adipisci natus. Sit consequatur non enim quo harum quis.\n \rUt reiciendis aut ad. Est et dolorem. Odio omnis non ea. Sapiente quos optio architecto eos aspernatur vel est.\n \rDoloribus nisi dignissimos eum sed molestias natus. Aperiam ea aliquid eos doloribus consequatur et et. Voluptas sed ut. Et consequatur aut voluptatem dicta necessitatibus sunt quia. Non delectus molestiae. Quasi praesentium dignissimos facilis et qui et.\n \rEt qui et et voluptatibus voluptate alias. Quibusdam ut omnis sunt pariatur. Explicabo facere atque facilis laboriosam. Reprehenderit rem ratione sed laborum omnis alias illo quas aperiam. At porro animi sit eaque dolore. Tempora autem numquam eos voluptas.\n \rMinima non minus tempora deleniti quia. Enim maiores sit. Soluta a id iusto illo placeat dolore delectus praesentium sunt.\n \rPorro esse quis atque veritatis iusto assumenda reprehenderit cupiditate. Provident saepe blanditiis fugit neque amet ad reprehenderit. Dolores consectetur omnis officia aut odio.\n \rDolorum totam consectetur maxime. Mollitia ea quo libero distinctio doloremque ipsam. Inventore delectus qui consequuntur sapiente ea maiores doloribus hic et. Sapiente vel ut vitae ad omnis incidunt. Odio deleniti hic tempore ut omnis ullam. Eveniet provident cum cupiditate rerum.\n \rVoluptas laboriosam quas temporibus illo. Ducimus natus sit possimus blanditiis impedit illum sit dolorem. Corporis officiis aut ipsam laboriosam aut. Voluptatem nemo reiciendis quam omnis quia. Alias voluptatibus et non libero.\n \rDolores eaque in autem voluptatum sit officia aut. Accusamus est dolores repellendus sit nihil. Ut quibusdam recusandae laborum sequi. Veritatis nesciunt ut eius iure. Fuga odio rem voluptas et quasi a culpa a.\n \rEa voluptas recusandae aperiam occaecati quae dicta quibusdam fugiat. Illo quas maiores qui vero consequuntur pariatur. Amet voluptas eum sit. Ut minus non sunt tenetur. Nihil est quae vitae minus repellat cum minus nisi.", + slug: "games-initiatives-online", + images: [ + { + uid: "rc-upload-aypuqgh2hm", + name: "random-image.jpg", + url: "https://picsum.photos/800", + type: "image/jpeg", + size: 141940, + }, + ], + createdAt: "2021-06-21T11:13:34.806Z", + updatedAt: "2021-06-21T11:13:35.195Z", + category: { + id: "73bdc4c0-0cc2-49bb-bd6f-550deb795468", + title: "Deposit Capacitor Hdd", + createdAt: "2021-06-21T11:13:35.195Z", + updatedAt: "2021-06-21T11:13:35.195Z", + }, + user: { + id: "0e1e8a86-b09e-4df4-80f3-5047d57c9cdf", + firstName: "Ansley", + lastName: "McCullough", + email: "ansley_McCullough53@yahoo.com", + status: true, + createdAt: "2021-06-21T11:13:34.699Z", + updatedAt: "2021-06-21T11:13:34.699Z", + }, + tags: [ + { + id: "e51fd147-2efd-409c-acd5-84d4e27ba71e", + title: "monitor", + createdAt: "2021-06-21T11:13:34.703Z", + updatedAt: "2021-06-21T11:13:34.703Z", + }, + { + id: "02058bdc-41df-45ed-9884-e9ff9dc2bd18", + title: "kuwait", + createdAt: "2021-06-21T11:13:34.707Z", + updatedAt: "2021-06-21T11:13:34.707Z", + }, + { + id: "6dedd9d7-1cc8-4016-ba03-78c2ba806f1a", + title: "framework", + createdAt: "2021-06-21T11:13:34.712Z", + updatedAt: "2021-06-21T11:13:34.712Z", + }, + ], + }, + { + status: "draft", + id: "04921a99-4918-4288-adea-f478a9e9e6bd", + title: "Sensor Motorway Bedfordshire", + content: + "Accusantium sed voluptas consequatur veniam ab voluptates. Est ipsam ipsum placeat ea ipsam recusandae consequatur. Quia reprehenderit aut. Eos esse non vero reiciendis distinctio atque qui. Nulla architecto nulla nihil veniam et. Ut maxime incidunt dolor facilis architecto dolorem voluptas.\n \rEum unde consequatur consequatur fuga est tenetur suscipit. Odio est dolorem dolorum nesciunt reiciendis ut. Ab quo eaque nulla et blanditiis aut omnis. Voluptate minima sit nihil.\n \rEt a et at. Nihil non enim est qui quisquam fugiat cumque quaerat. Nemo qui ducimus.\n \rPerspiciatis possimus aliquam corporis esse corrupti minima quam. Vel eos rem necessitatibus aliquid modi sint saepe provident. Aut voluptates dolor rem quae provident.\n \rNihil et voluptates qui molestias voluptate est est atque. Officia et fuga maiores porro dolores rerum exercitationem laboriosam. Est voluptate voluptatibus porro. Nemo enim nam est saepe. Dignissimos perspiciatis molestiae autem illo et vel.\n \rMinus sed accusamus velit dicta natus. Molestias neque labore. Voluptatem exercitationem non at error dolor in dolores. Eveniet animi et. Deleniti aut non voluptas voluptas quia omnis.\n \rVoluptate et consectetur. Laboriosam beatae non delectus aut placeat corrupti. Est ad magni omnis officiis sit aut at alias.\n \rVoluptas deserunt architecto. Esse sed voluptatum. Eos quos consectetur placeat quia nemo.\n \rPossimus ea architecto reiciendis adipisci et. Sit laboriosam et vel nostrum dolore accusamus. Deleniti repellat vel et facilis praesentium fugit voluptates autem.\n \rDolorem vero earum nemo amet laboriosam. Voluptatum sint quis iusto ut minus est quia et est. Doloribus dolorem sequi debitis. Unde consequuntur qui et voluptatibus odio hic repudiandae itaque.", + slug: "sensor-motorway-bedfordshire", + images: [ + { + uid: "rc-upload-5xqp4f2y17", + name: "random-image.jpg", + url: "https://picsum.photos/800", + type: "image/jpeg", + size: 141940, + }, + ], + createdAt: "2021-06-21T11:13:35.045Z", + updatedAt: "2021-06-21T11:13:35.195Z", + category: { + id: "73bdc4c0-0cc2-49bb-bd6f-550deb795468", + title: "Deposit Capacitor Hdd", + createdAt: "2021-06-21T11:13:35.195Z", + updatedAt: "2021-06-21T11:13:35.195Z", + }, + user: { + id: "0e1e8a86-b09e-4df4-80f3-5047d57c9cdf", + firstName: "Ansley", + lastName: "McCullough", + email: "ansley_McCullough53@yahoo.com", + status: true, + createdAt: "2021-06-21T11:13:34.699Z", + updatedAt: "2021-06-21T11:13:34.699Z", + }, + tags: [ + { + id: "6dedd9d7-1cc8-4016-ba03-78c2ba806f1a", + title: "framework", + createdAt: "2021-06-21T11:13:34.712Z", + updatedAt: "2021-06-21T11:13:34.712Z", + }, + { + id: "02058bdc-41df-45ed-9884-e9ff9dc2bd18", + title: "kuwait", + createdAt: "2021-06-21T11:13:34.707Z", + updatedAt: "2021-06-21T11:13:34.707Z", + }, + { + id: "e51fd147-2efd-409c-acd5-84d4e27ba71e", + title: "monitor", + createdAt: "2021-06-21T11:13:34.703Z", + updatedAt: "2021-06-21T11:13:34.703Z", + }, + ], + }, + { + status: "draft", + id: "24b016ee-6779-425b-99c0-2e939e253cfd", + title: "Implement Personal Loan Account bifurcated", + content: + "Voluptate illum ut et. Veniam minima aut repudiandae dolor in sequi labore dignissimos. Sed non voluptas voluptatem soluta veniam in magnam omnis. Ipsa neque recusandae distinctio commodi enim quisquam animi. Vero sint sed.\n \rId quidem aspernatur sequi aut accusamus pariatur. Velit necessitatibus odio eum. Veritatis sed nostrum a voluptate.\n \rExpedita nihil porro sint quis vero vero qui quo inventore. Sint et incidunt earum necessitatibus qui nulla et qui consectetur. Sint est dicta libero amet velit aliquam dolores ut et. Velit dicta placeat culpa quibusdam cumque ad. Dolor necessitatibus dolore quia recusandae. Mollitia sapiente autem vero aut sunt consequatur iusto et facilis.\n \rNesciunt ut velit perferendis culpa. Velit quod architecto enim aut. Quas et quo ipsum voluptates beatae voluptas aspernatur iusto deserunt.\n \rAutem dolores facere aut voluptatum. Et occaecati facilis esse reprehenderit et. Magni eos ut rerum repellat perspiciatis molestias asperiores. Facilis odit aliquam qui. Et voluptatem in dolor omnis.\n \rSapiente recusandae id. Voluptas vitae non consequatur est. Veniam et debitis perferendis occaecati. Dolor et voluptates voluptas tempora. Voluptatem et mollitia et asperiores voluptas sit expedita. Iusto maiores itaque.\n \rIn ea eum quia. Itaque deleniti praesentium. Facere nesciunt odit cupiditate doloremque voluptatum. Sunt accusamus culpa officiis facilis eius omnis tempora.\n \rOmnis tempora magni amet repellat aut fugit ut maxime mollitia. Iusto voluptas molestiae molestias ea eaque sunt facere omnis. Esse et suscipit pariatur et aliquam molestiae veritatis provident error. Quia sunt dolore dolores velit impedit sunt molestiae nesciunt nihil. Repudiandae aut ex consequatur recusandae maxime quae.\n \rNon sunt ut laborum reprehenderit sit sed consequatur veritatis quia. Voluptate nemo id fugit vel ut. Asperiores ut provident eos velit ut fuga nesciunt aspernatur. Velit modi eos possimus non. Quis cupiditate quod.\n \rEos ipsum soluta rerum. Ut molestiae laborum fugiat voluptatem iste non qui sit voluptates. In eaque temporibus. Quasi beatae qui. Eum nemo sapiente molestias quod dolor.", + slug: "implement-personal-loan-account-bifurcated", + images: [ + { + uid: "rc-upload-rdc6wuyk7t", + name: "random-image.jpg", + url: "https://picsum.photos/800", + type: "image/jpeg", + size: 141940, + }, + ], + createdAt: "2021-06-21T11:13:34.722Z", + updatedAt: "2021-06-21T11:13:35.195Z", + category: { + id: "73bdc4c0-0cc2-49bb-bd6f-550deb795468", + title: "Deposit Capacitor Hdd", + createdAt: "2021-06-21T11:13:35.195Z", + updatedAt: "2021-06-21T11:13:35.195Z", + }, + user: { + id: "0e1e8a86-b09e-4df4-80f3-5047d57c9cdf", + firstName: "Ansley", + lastName: "McCullough", + email: "ansley_McCullough53@yahoo.com", + status: true, + createdAt: "2021-06-21T11:13:34.699Z", + updatedAt: "2021-06-21T11:13:34.699Z", + }, + tags: [ + { + id: "e51fd147-2efd-409c-acd5-84d4e27ba71e", + title: "monitor", + createdAt: "2021-06-21T11:13:34.703Z", + updatedAt: "2021-06-21T11:13:34.703Z", + }, + { + id: "02058bdc-41df-45ed-9884-e9ff9dc2bd18", + title: "kuwait", + createdAt: "2021-06-21T11:13:34.707Z", + updatedAt: "2021-06-21T11:13:34.707Z", + }, + { + id: "6dedd9d7-1cc8-4016-ba03-78c2ba806f1a", + title: "framework", + createdAt: "2021-06-21T11:13:34.712Z", + updatedAt: "2021-06-21T11:13:34.712Z", + }, + ], + }, + { + status: "draft", + id: "2bec3c49-0016-4f18-bbf9-153b7a77b897", + title: "Gloves Shoes compressing", + content: + "Sapiente facere similique dolore alias tempora. Dolorum illum et voluptatum et quas modi vel eum. Aliquam aut aspernatur enim fugit iure distinctio ut. Dicta voluptas numquam et impedit et perferendis aut voluptatem dolore. Sed ex repudiandae voluptatem corrupti quo id quia. Commodi deleniti quaerat nobis ullam quidem omnis dolor et odit.\n \rRepellat et aperiam quas amet libero officia dignissimos consectetur suscipit. Pariatur ut voluptas omnis odit. Esse quisquam rerum in tempora dolor laboriosam non quo ducimus. Repellat quia sapiente.\n \rEaque debitis quia dolorum corporis. Et sint delectus sint vel dicta error sunt quis. Quaerat aut molestias laudantium. Doloremque ex ducimus quo consequatur assumenda eos.\n \rNam iste aliquid porro deserunt impedit eaque. Quo esse exercitationem sunt sed voluptatum aperiam. Animi consectetur veniam possimus quo ea dolor. Voluptas culpa repellendus eos distinctio recusandae laborum eum aut possimus. Eligendi magni blanditiis neque error aspernatur dolor.\n \rQuaerat reprehenderit est velit aut voluptatibus voluptatum. Ut ut dolore qui quidem. Qui rem est magnam ullam. Dolorem est sapiente nihil voluptatem.\n \rDicta et harum dolores vitae consequatur. Eum dolores nulla aut. Vel qui in expedita amet dignissimos unde. Sint dolore ad et provident excepturi.\n \rQui magnam enim consequuntur perspiciatis iusto quaerat nemo. Dolores harum labore et blanditiis id minus ullam laboriosam. Et ab molestias. Nihil autem dolor est blanditiis id. Doloremque dolor molestias iusto vero dolorem nobis et quasi autem. At culpa voluptate omnis enim.\n \rFuga eius dolor veniam veniam. Sint sapiente adipisci id hic nam laudantium officiis deserunt error. Sequi cupiditate sequi nulla praesentium sunt. Minima nisi eaque quis fugit unde officia voluptatibus non nihil. Assumenda culpa est ipsam eligendi officiis nostrum perferendis. Aut perspiciatis sed at deserunt quia quidem provident fugit.\n \rIpsam quaerat dolorum et ad a voluptatem quis delectus. Illo porro nemo quisquam rerum dolorem. Totam accusantium quisquam possimus sint quam alias. Et voluptatibus suscipit.\n \rDolores laudantium aut quo nulla cum quidem sit neque est. Et iure quas voluptas non adipisci sequi. Aut amet rem veniam debitis fugiat aspernatur.", + slug: "gloves-shoes-compressing", + images: [ + { + uid: "rc-upload-wlcvjxusbz", + name: "random-image.jpg", + url: "https://picsum.photos/800", + type: "image/jpeg", + size: 141940, + }, + ], + createdAt: "2021-06-21T11:13:34.947Z", + updatedAt: "2021-06-21T11:13:35.195Z", + category: { + id: "73bdc4c0-0cc2-49bb-bd6f-550deb795468", + title: "Deposit Capacitor Hdd", + createdAt: "2021-06-21T11:13:35.195Z", + updatedAt: "2021-06-21T11:13:35.195Z", + }, + user: { + id: "0e1e8a86-b09e-4df4-80f3-5047d57c9cdf", + firstName: "Ansley", + lastName: "McCullough", + email: "ansley_McCullough53@yahoo.com", + status: true, + createdAt: "2021-06-21T11:13:34.699Z", + updatedAt: "2021-06-21T11:13:34.699Z", + }, + tags: [ + { + id: "e51fd147-2efd-409c-acd5-84d4e27ba71e", + title: "monitor", + createdAt: "2021-06-21T11:13:34.703Z", + updatedAt: "2021-06-21T11:13:34.703Z", + }, + { + id: "02058bdc-41df-45ed-9884-e9ff9dc2bd18", + title: "kuwait", + createdAt: "2021-06-21T11:13:34.707Z", + updatedAt: "2021-06-21T11:13:34.707Z", + }, + { + id: "6dedd9d7-1cc8-4016-ba03-78c2ba806f1a", + title: "framework", + createdAt: "2021-06-21T11:13:34.712Z", + updatedAt: "2021-06-21T11:13:34.712Z", + }, + ], + }, + { + status: "draft", + id: "2cf82473-8ff3-43da-abbe-914f18954fb8", + title: "Human Cambodia El Salvador Colon US Dollar", + content: + "Repudiandae saepe repellendus natus. Porro quod numquam animi quos totam eos eos vitae recusandae. Dolor accusantium adipisci ab voluptatem impedit maxime a. Id harum aut. Dolore quis dolores quasi amet sit est natus minima error.\n \rVoluptatem dolorem autem ut. Error velit et tenetur sed dolore cumque quia. Fuga eos accusantium. Voluptatem pariatur vitae qui.\n \rIpsa molestiae ea libero non. Velit voluptatum amet est quos aliquam quod. Voluptas temporibus laboriosam id dolor dolores aut voluptatem.\n \rQuis nesciunt et repellendus sit qui enim autem. Quod perspiciatis eius sed. Ipsum natus explicabo nulla magni eaque velit eveniet id. Eos quia voluptate iusto nulla. Laudantium et eligendi ea inventore libero voluptas.\n \rEos dolores quidem ut corrupti quae culpa tempore. Tenetur impedit veritatis architecto placeat nisi quis praesentium repellendus officiis. Nemo eveniet exercitationem repellendus quo iste possimus nihil. Accusantium incidunt omnis sequi unde autem. Aut et odit. Id laudantium dolorum rerum.\n \rDolores non consequatur quia eum et odit aut tenetur provident. Et consequatur est. Et quos ipsum. Ut non eligendi quam voluptate.\n \rDicta qui ullam illo voluptatem odio aut. Non iusto eligendi. In et odio omnis id cupiditate.\n \rVoluptatem sit aut ea. Eum numquam quae accusantium occaecati ea. Voluptas dolorum enim et. Dolorum omnis quae possimus qui. Aut voluptatem quis nostrum libero omnis facere. Iure beatae sed velit.\n \rQuam earum reprehenderit voluptates quidem sit quo. Soluta natus consectetur aut labore dolores vel rerum. Nihil quas sed reiciendis harum labore hic ea dolor culpa.\n \rNeque nihil inventore sed voluptas est rerum aut labore eligendi. Magnam voluptas et omnis laborum beatae soluta sit illum. Reiciendis nisi doloremque sint quae. Error nesciunt qui est placeat dolorum sit nam aliquam. In aperiam quis et.", + slug: "human-cambodia-el-salvador-colon-us-dollar", + images: [ + { + uid: "rc-upload-c8v5f16teb", + name: "random-image.jpg", + url: "https://picsum.photos/800", + type: "image/jpeg", + size: 141940, + }, + ], + createdAt: "2021-06-21T11:13:34.920Z", + updatedAt: "2021-06-21T11:13:35.195Z", + category: { + id: "73bdc4c0-0cc2-49bb-bd6f-550deb795468", + title: "Deposit Capacitor Hdd", + createdAt: "2021-06-21T11:13:35.195Z", + updatedAt: "2021-06-21T11:13:35.195Z", + }, + user: { + id: "0e1e8a86-b09e-4df4-80f3-5047d57c9cdf", + firstName: "Ansley", + lastName: "McCullough", + email: "ansley_McCullough53@yahoo.com", + status: true, + createdAt: "2021-06-21T11:13:34.699Z", + updatedAt: "2021-06-21T11:13:34.699Z", + }, + tags: [ + { + id: "e51fd147-2efd-409c-acd5-84d4e27ba71e", + title: "monitor", + createdAt: "2021-06-21T11:13:34.703Z", + updatedAt: "2021-06-21T11:13:34.703Z", + }, + { + id: "02058bdc-41df-45ed-9884-e9ff9dc2bd18", + title: "kuwait", + createdAt: "2021-06-21T11:13:34.707Z", + updatedAt: "2021-06-21T11:13:34.707Z", + }, + { + id: "6dedd9d7-1cc8-4016-ba03-78c2ba806f1a", + title: "framework", + createdAt: "2021-06-21T11:13:34.712Z", + updatedAt: "2021-06-21T11:13:34.712Z", + }, + ], + }, + { + status: "published", + id: "36edd561-9b75-42b3-bc7b-25f821c8f4bb", + title: "Progressive Congolese Franc Generic", + content: + "Doloremque harum qui. Rerum libero ab. Corporis magni autem vel consequuntur doloremque corrupti necessitatibus nesciunt. Doloribus tempora sit dolore facere possimus ullam cum aperiam voluptatem.\n \rNulla soluta omnis voluptate est et illo odio ut. Fugiat eius earum sunt pariatur. Et tenetur qui a voluptas molestiae. Labore repudiandae dicta atque. Voluptatibus eos eos quae vero consequatur. Odit ratione sequi.\n \rVoluptatem rerum amet quidem fugiat reiciendis consequatur eum voluptatem. Alias officia et veniam. Voluptatum et dolorum blanditiis cumque optio nihil doloremque vel quam. Sapiente qui ex. Odit sunt quia ipsa in.\n \rQui autem aut aut nam sequi debitis autem veniam labore. Quis omnis quas minima qui recusandae et quo aut maiores. Voluptates at rerum eius at dolorum. Et error aut deserunt quam eaque est sed qui earum. Vel nostrum cumque possimus numquam.\n \rIste architecto sed quasi ipsum qui ea. Est distinctio cumque est nulla. Aut modi sed labore commodi nisi. Libero consequatur necessitatibus eum temporibus qui quae natus sit et. Illum cumque deleniti et natus cum autem rerum quo iste.\n \rMolestiae itaque autem enim autem atque voluptatem fugit. Sit tempora minus earum quia deleniti quis voluptates eum. Tempore provident accusantium expedita fugiat aliquid. Qui ipsam iste voluptate magni.\n \rUnde aliquam ea commodi impedit et. Tenetur libero molestiae ea nemo suscipit nesciunt. Dolores molestiae dolores. Aspernatur voluptatem doloribus velit beatae. Porro quia ad pariatur similique aut fugiat. Ipsam eligendi illum.\n \rDolore magnam enim dolor nulla saepe consequatur. Ipsam quidem quasi qui molestias cumque ut culpa. Rem velit eos harum odit eligendi voluptatem consequatur qui.\n \rDeserunt aspernatur enim illum qui ut rerum quia totam. Omnis at et a. Distinctio eos debitis non et tenetur dolorum illo quaerat aperiam. Vitae necessitatibus aut ut hic ipsum accusantium et.\n \rSapiente maxime voluptates quos expedita aut. Et est facilis consequuntur molestiae assumenda. Cumque ipsum veritatis beatae dolor iure laudantium vitae ab. Vero est vel architecto non dolorum praesentium.", + slug: "progressive-congolese-franc-generic", + images: [ + { + uid: "rc-upload-am7o29bvpm", + name: "random-image.jpg", + url: "https://picsum.photos/800", + type: "image/jpeg", + size: 141940, + }, + ], + createdAt: "2021-06-21T11:13:35.009Z", + updatedAt: "2021-06-21T11:13:35.195Z", + category: { + id: "73bdc4c0-0cc2-49bb-bd6f-550deb795468", + title: "Deposit Capacitor Hdd", + createdAt: "2021-06-21T11:13:35.195Z", + updatedAt: "2021-06-21T11:13:35.195Z", + }, + user: { + id: "0e1e8a86-b09e-4df4-80f3-5047d57c9cdf", + firstName: "Ansley", + lastName: "McCullough", + email: "ansley_McCullough53@yahoo.com", + status: true, + createdAt: "2021-06-21T11:13:34.699Z", + updatedAt: "2021-06-21T11:13:34.699Z", + }, + tags: [ + { + id: "e51fd147-2efd-409c-acd5-84d4e27ba71e", + title: "monitor", + createdAt: "2021-06-21T11:13:34.703Z", + updatedAt: "2021-06-21T11:13:34.703Z", + }, + { + id: "02058bdc-41df-45ed-9884-e9ff9dc2bd18", + title: "kuwait", + createdAt: "2021-06-21T11:13:34.707Z", + updatedAt: "2021-06-21T11:13:34.707Z", + }, + { + id: "6dedd9d7-1cc8-4016-ba03-78c2ba806f1a", + title: "framework", + createdAt: "2021-06-21T11:13:34.712Z", + updatedAt: "2021-06-21T11:13:34.712Z", + }, + ], + }, + { + status: "draft", + id: "402a3b6f-dc3d-4637-99be-e1a23c0361f7", + title: "Lime Bacon Guam", + content: + "Temporibus ut nobis non dignissimos porro quo sunt. Velit voluptatem quod eius ea repellendus dolorem. At dolorum molestiae adipisci possimus. Atque et ea id quisquam omnis ut. Eius qui laboriosam molestias explicabo eos. Consequatur dolorem vitae sit iste provident atque numquam.\n \rQui et ea nostrum voluptate quia magnam velit. Laudantium porro placeat. Maiores deleniti nobis iusto delectus voluptas praesentium.\n \rMagni officia voluptatum maiores reprehenderit aut. Et optio dolor et aut qui minus. Reprehenderit dolorem aut voluptatem qui consequatur. Eligendi odio iusto porro at. Eveniet accusantium eaque eum vero provident. Ut laudantium necessitatibus veritatis fugit.\n \rRepudiandae debitis facere enim eaque natus ut alias recusandae cupiditate. Eos cupiditate consequatur consectetur deserunt ea sit officia aperiam est. Eum sit quidem debitis ipsam ut qui nesciunt. Voluptas voluptatem aliquam odit voluptatem dolorem at reiciendis. Porro omnis dolores nemo non.\n \rMagni autem quasi dolores ratione praesentium quas sit. Voluptate voluptatem odit aliquam deserunt architecto. Accusantium itaque quo magni et aliquid eum. Minus vero minus non veritatis reprehenderit corrupti. Omnis ab itaque saepe maxime.\n \rDignissimos reprehenderit consequatur. Dignissimos incidunt accusamus. Repellat veniam maxime neque sit sunt quo aut est. Sed cum praesentium reiciendis sint. Quia veritatis et voluptas fuga numquam.\n \rRerum assumenda rem tempora veritatis velit similique. Rerum sit autem minus. Hic fuga aliquam ea consequuntur ut praesentium omnis ad. Voluptas vel quo veritatis eius.\n \rIpsam vero ex possimus est. Harum voluptates repellat fugiat asperiores esse ut vero non. Laborum maiores reprehenderit est omnis nesciunt quaerat omnis non. Magnam animi porro delectus eum. Reiciendis eos maiores ut fugit. Autem rerum neque qui vel modi sunt suscipit praesentium deserunt.\n \rTotam nulla blanditiis asperiores. Ex voluptas reiciendis ut voluptates aut non ipsam. Rerum eos facilis. Neque totam hic enim quia maxime et. Repudiandae quasi perspiciatis molestias qui est deleniti voluptatum saepe dolorem.\n \rSed consequatur quis animi pariatur et labore natus. Debitis reprehenderit odio ducimus maxime aut modi. Voluptas quidem ut nisi quam est ducimus illum. Vel sed dolor beatae officiis ut.", + slug: "lime-bacon-guam", + images: [ + { + uid: "rc-upload-97i8s9lb61", + name: "random-image.jpg", + url: "https://picsum.photos/800", + type: "image/jpeg", + size: 141940, + }, + ], + createdAt: "2021-06-21T11:13:34.906Z", + updatedAt: "2021-06-21T11:13:35.195Z", + category: { + id: "73bdc4c0-0cc2-49bb-bd6f-550deb795468", + title: "Deposit Capacitor Hdd", + createdAt: "2021-06-21T11:13:35.195Z", + updatedAt: "2021-06-21T11:13:35.195Z", + }, + user: { + id: "0e1e8a86-b09e-4df4-80f3-5047d57c9cdf", + firstName: "Ansley", + lastName: "McCullough", + email: "ansley_McCullough53@yahoo.com", + status: true, + createdAt: "2021-06-21T11:13:34.699Z", + updatedAt: "2021-06-21T11:13:34.699Z", + }, + tags: [ + { + id: "02058bdc-41df-45ed-9884-e9ff9dc2bd18", + title: "kuwait", + createdAt: "2021-06-21T11:13:34.707Z", + updatedAt: "2021-06-21T11:13:34.707Z", + }, + { + id: "e51fd147-2efd-409c-acd5-84d4e27ba71e", + title: "monitor", + createdAt: "2021-06-21T11:13:34.703Z", + updatedAt: "2021-06-21T11:13:34.703Z", + }, + { + id: "6dedd9d7-1cc8-4016-ba03-78c2ba806f1a", + title: "framework", + createdAt: "2021-06-21T11:13:34.712Z", + updatedAt: "2021-06-21T11:13:34.712Z", + }, + ], + }, + { + status: "published", + id: "46adc294-bdac-46b6-a8ba-cdea8ec81d9f", + title: "Redundant Practical Tokelau", + content: + "In facere aspernatur dolor deserunt quis odio eligendi nulla omnis. Est voluptates eum perferendis doloribus ut et quidem et. Voluptas corporis quis fugiat aut sunt. Consequatur sapiente deleniti iste ut quae est non similique. Expedita perspiciatis non exercitationem necessitatibus quo eveniet sunt. Minus possimus et ut fugiat ipsa.\n \rDoloremque sed dolores perferendis consectetur cum similique. Reprehenderit aut saepe sit adipisci ratione. Corrupti voluptatem temporibus aperiam. Repellat facere quo perferendis. Dolores cum adipisci adipisci repellendus sed ut modi. Temporibus laudantium repudiandae officia qui distinctio tempora magnam.\n \rMinima dolores officia. Est adipisci omnis consequatur non ut magnam. Quo nulla a veniam perspiciatis quos ipsam. Assumenda quia fuga. Quam neque unde. Et quia quibusdam qui velit nihil reiciendis sapiente accusamus voluptatem.\n \rUt eum consectetur voluptates. Qui rerum doloribus voluptas qui porro nemo veniam. Tempore iste accusamus qui quo. Odio possimus dolores cupiditate.\n \rQuas quasi et id ut aut amet atque. Enim nesciunt magnam eaque ut. Et quas animi. Assumenda molestias iste quis et et. Et a qui est pariatur fuga illum dolorem.\n \rSint dolores consequatur dolorem corporis asperiores mollitia fugit enim qui. Exercitationem illum ab rerum. Accusantium placeat delectus quibusdam est nesciunt soluta. Delectus accusamus ducimus impedit repellat laudantium dolorum consequatur eos. Doloremque omnis qui. Quibusdam animi voluptas et iusto et.\n \rDistinctio ratione laudantium culpa magni incidunt. Nam dolor aut corrupti est rerum consequatur eos. In voluptatibus eum optio nostrum maiores porro. Doloremque rerum modi id maxime ut. Vel est velit recusandae ea ducimus odio pariatur.\n \rNobis in quo ut praesentium voluptate exercitationem. Eos amet et assumenda ea qui laboriosam soluta. Doloribus non omnis dignissimos quaerat praesentium eos alias qui veniam. Sit laborum aspernatur fuga sunt quis repellendus. Quibusdam reprehenderit voluptatem.\n \rIpsa facilis reiciendis quasi molestiae est. Placeat quibusdam veritatis modi sunt et ut. Qui aut sed non sed sint architecto ut aspernatur. Excepturi non sit nemo debitis sint quam. Mollitia facilis in qui maiores vel ratione.\n \rHic voluptate tenetur unde exercitationem. Quam perspiciatis debitis quia sequi sint consequatur et. Fuga vel vel distinctio maxime. Repellat aperiam maiores pariatur eius consequuntur nihil ipsam. Molestiae nihil saepe rem nulla praesentium ut consequatur dolor.", + slug: "redundant-practical-tokelau", + images: [ + { + uid: "rc-upload-dvgg9adn0b", + name: "random-image.jpg", + url: "https://picsum.photos/800", + type: "image/jpeg", + size: 141940, + }, + ], + createdAt: "2021-06-21T11:13:34.990Z", + updatedAt: "2021-06-21T11:13:35.195Z", + category: { + id: "73bdc4c0-0cc2-49bb-bd6f-550deb795468", + title: "Deposit Capacitor Hdd", + createdAt: "2021-06-21T11:13:35.195Z", + updatedAt: "2021-06-21T11:13:35.195Z", + }, + user: { + id: "0e1e8a86-b09e-4df4-80f3-5047d57c9cdf", + firstName: "Ansley", + lastName: "McCullough", + email: "ansley_McCullough53@yahoo.com", + status: true, + createdAt: "2021-06-21T11:13:34.699Z", + updatedAt: "2021-06-21T11:13:34.699Z", + }, + tags: [ + { + id: "02058bdc-41df-45ed-9884-e9ff9dc2bd18", + title: "kuwait", + createdAt: "2021-06-21T11:13:34.707Z", + updatedAt: "2021-06-21T11:13:34.707Z", + }, + { + id: "e51fd147-2efd-409c-acd5-84d4e27ba71e", + title: "monitor", + createdAt: "2021-06-21T11:13:34.703Z", + updatedAt: "2021-06-21T11:13:34.703Z", + }, + { + id: "6dedd9d7-1cc8-4016-ba03-78c2ba806f1a", + title: "framework", + createdAt: "2021-06-21T11:13:34.712Z", + updatedAt: "2021-06-21T11:13:34.712Z", + }, + ], + }, + { + status: "draft", + id: "4744d7a6-3696-4917-b861-4494cd6da9d6", + title: "Envisioneer Azerbaijanian Manat haptic", + content: + "Nostrum laboriosam excepturi voluptas accusamus. Minima accusantium sint magni deleniti et. Ea error corporis rem accusamus et sapiente et quibusdam. Voluptatum atque ea iure.\n \rAd earum ut adipisci rerum. Ipsa quia atque praesentium. Quod voluptatibus hic porro recusandae porro. Et qui quaerat officiis voluptates sequi sint quaerat nemo.\n \rDolores ipsam laudantium molestiae est ducimus libero earum. Sapiente reiciendis occaecati modi ut error praesentium. Amet odio quo autem ducimus aut sit. Omnis nobis minima necessitatibus id.\n \rNihil facere omnis dolorem aut et nisi. Et cumque facere atque in sit mollitia. Asperiores assumenda laudantium quia. Distinctio quas ipsa.\n \rEa aliquid sit error quaerat asperiores illum. Voluptatem sunt deserunt accusamus et. Aut qui facilis qui tempora. Sit qui ea hic ab molestiae. Autem impedit culpa praesentium corporis perferendis.\n \rDebitis quibusdam dolor exercitationem voluptatum id eum rerum nulla debitis. Impedit omnis repellendus voluptatem et occaecati numquam quos magnam quidem. Atque voluptas accusantium incidunt necessitatibus reprehenderit voluptas laborum id.\n \rAut aut voluptas sint reprehenderit unde. Adipisci quas qui saepe. Sit necessitatibus ratione laboriosam nisi. Velit nulla ut.\n \rVoluptas sed est maxime accusantium et mollitia dolore. Et est quisquam et et qui dolorem eum. Incidunt distinctio aut consequatur qui quis laborum ut.\n \rUt tempore vitae illum. Ut repellat unde et debitis qui et eos. Blanditiis voluptas nostrum quo eius nam dolorem qui. Ut debitis aliquid distinctio aut doloribus.\n \rIn sed veniam aspernatur. Adipisci minus autem excepturi libero est est atque dolorem. Provident ut at suscipit aperiam commodi et. Non est maxime sunt enim quibusdam odit nulla ut est. Occaecati nemo aliquam officiis molestias earum perspiciatis.", + slug: "envisioneer-azerbaijanian-manat-haptic", + images: [ + { + uid: "rc-upload-cweb1i33cz", + name: "random-image.jpg", + url: "https://picsum.photos/800", + type: "image/jpeg", + size: 141940, + }, + ], + createdAt: "2021-06-21T11:13:35.028Z", + updatedAt: "2021-06-21T11:13:35.195Z", + category: { + id: "73bdc4c0-0cc2-49bb-bd6f-550deb795468", + title: "Deposit Capacitor Hdd", + createdAt: "2021-06-21T11:13:35.195Z", + updatedAt: "2021-06-21T11:13:35.195Z", + }, + user: { + id: "0e1e8a86-b09e-4df4-80f3-5047d57c9cdf", + firstName: "Ansley", + lastName: "McCullough", + email: "ansley_McCullough53@yahoo.com", + status: true, + createdAt: "2021-06-21T11:13:34.699Z", + updatedAt: "2021-06-21T11:13:34.699Z", + }, + tags: [ + { + id: "e51fd147-2efd-409c-acd5-84d4e27ba71e", + title: "monitor", + createdAt: "2021-06-21T11:13:34.703Z", + updatedAt: "2021-06-21T11:13:34.703Z", + }, + { + id: "02058bdc-41df-45ed-9884-e9ff9dc2bd18", + title: "kuwait", + createdAt: "2021-06-21T11:13:34.707Z", + updatedAt: "2021-06-21T11:13:34.707Z", + }, + { + id: "6dedd9d7-1cc8-4016-ba03-78c2ba806f1a", + title: "framework", + createdAt: "2021-06-21T11:13:34.712Z", + updatedAt: "2021-06-21T11:13:34.712Z", + }, + ], + }, + { + status: "published", + id: "52e29ad3-886e-45ff-8511-d4b2b3c80f28", + title: "Mesh value-added Personal Loan Account", + content: + "Quis perspiciatis voluptas ipsa perferendis quo vel cupiditate dolor qui. Non et pariatur ut. Magnam ab molestiae. Praesentium officia occaecati sunt officia quia quia error. Inventore animi qui. Velit in accusantium sint quae provident cumque qui.\n \rDelectus enim molestiae repellat error nobis voluptas. Tempora et architecto consectetur aliquam quod aliquid repellendus ut. Ut aut cum et quod laboriosam. Harum aut aut praesentium repellat. Exercitationem officia est aut.\n \rSit iusto eaque quam aut numquam voluptatem a ut voluptates. Eaque ad dolorem repudiandae. Modi labore qui.\n \rVel temporibus laboriosam vitae. Id beatae illo aut eos quibusdam et. Dolores provident amet facilis ut occaecati velit alias qui. Repudiandae mollitia velit. Consequatur occaecati quasi deserunt ea.\n \rMolestiae et repellat quia voluptatem expedita quas aut sint eum. Recusandae molestiae id eius debitis. Dolorum non est illum ipsa cum porro. Nihil sapiente quaerat optio maxime neque quaerat consequatur assumenda. Hic tempora dignissimos ut error vel. Sit quis officia ab esse.\n \rPlaceat enim occaecati voluptas deleniti aut. Cum dolore suscipit a exercitationem. Dolores commodi excepturi consequatur. Eveniet sed aut similique eum. Amet modi neque et reiciendis sit.\n \rIllo enim omnis aperiam ducimus placeat facilis distinctio doloremque. Quasi porro quae et. Asperiores ut consequatur consequatur vel minus consequatur eligendi voluptatem laboriosam. Incidunt ipsa culpa quaerat. Aliquid molestiae sed sed ut consequatur ea omnis.\n \rQuis saepe soluta debitis nemo. Et laboriosam consequuntur cupiditate error. Expedita sequi occaecati quo porro laudantium sunt atque excepturi.\n \rNon vel quibusdam accusantium dolor nulla. Sed aut incidunt in et culpa. Iure nulla amet corporis neque similique eos.\n \rCorporis doloribus aliquid in porro error. Sed enim accusamus maiores consequatur. Debitis dolorem rerum suscipit non unde numquam. Sit sed nesciunt nisi necessitatibus numquam numquam et. Omnis veritatis quo temporibus neque est officia culpa quia est. Et ullam dolor tempore.", + slug: "mesh-value-added-personal-loan-account", + images: [ + { + uid: "rc-upload-5noyzt0blz", + name: "random-image.jpg", + url: "https://picsum.photos/800", + type: "image/jpeg", + size: 141940, + }, + ], + createdAt: "2021-06-21T11:13:34.890Z", + updatedAt: "2021-06-21T11:13:35.195Z", + category: { + id: "73bdc4c0-0cc2-49bb-bd6f-550deb795468", + title: "Deposit Capacitor Hdd", + createdAt: "2021-06-21T11:13:35.195Z", + updatedAt: "2021-06-21T11:13:35.195Z", + }, + user: { + id: "0e1e8a86-b09e-4df4-80f3-5047d57c9cdf", + firstName: "Ansley", + lastName: "McCullough", + email: "ansley_McCullough53@yahoo.com", + status: true, + createdAt: "2021-06-21T11:13:34.699Z", + updatedAt: "2021-06-21T11:13:34.699Z", + }, + tags: [ + { + id: "6dedd9d7-1cc8-4016-ba03-78c2ba806f1a", + title: "framework", + createdAt: "2021-06-21T11:13:34.712Z", + updatedAt: "2021-06-21T11:13:34.712Z", + }, + { + id: "02058bdc-41df-45ed-9884-e9ff9dc2bd18", + title: "kuwait", + createdAt: "2021-06-21T11:13:34.707Z", + updatedAt: "2021-06-21T11:13:34.707Z", + }, + { + id: "e51fd147-2efd-409c-acd5-84d4e27ba71e", + title: "monitor", + createdAt: "2021-06-21T11:13:34.703Z", + updatedAt: "2021-06-21T11:13:34.703Z", + }, + ], + }, + ], + count: 10, + total: 24, + page: 1, + pageCount: 3, + }, + [ + "Server", + "nginx/1.17.10", + "Date", + "Mon, 21 Jun 2021 12:13:51 GMT", + "Content-Type", + "application/json; charset=utf-8", + "Content-Length", + "33234", + "Connection", + "close", + "Vary", + "Accept-Encoding", + "X-Powered-By", + "Express", + "Access-Control-Allow-Origin", + "*", + "ETag", + 'W/"81d2-fU5n8AE1T9c7O1lr0btQyAj+Fpk"', + ], + ); + +nock("https://api.nestjsx-crud.refine.dev:443", { + encodedQueryParams: true, +}) + .get("/posts") + .query({ + s: "%7B%22%24and%22%3A%5B%7B%22%24or%22%3A%5B%7B%22%24and%22%3A%5B%7B%22title%22%3A%7B%22%24startsL%22%3A%22a%22%7D%7D%2C%7B%22title%22%3A%7B%22%24contL%22%3A%22heuristic%22%7D%7D%5D%7D%2C%7B%22%24and%22%3A%5B%7B%22title%22%3A%7B%22%24startsL%22%3A%22e%22%7D%7D%2C%7B%22title%22%3A%7B%22%24contL%22%3A%22invoice%22%7D%7D%5D%7D%5D%7D%5D%7D", + limit: "10", + page: "1", + offset: "0", + }) + .reply( + 200, + { + data: [ + { + status: "draft", + id: "402def0f-5116-4044-81bd-e979442f2909", + title: "Engage Marshall Islands invoice", + content: + "Hic ut molestias eum soluta eligendi. Totam vero ipsa vitae enim sit et doloremque. Quo quos commodi praesentium.\n \rMaxime dolorem dolorem sit perspiciatis. Velit ut et. Ut doloribus ab natus rem voluptatibus. Qui at dolorem quo repellat numquam esse minima incidunt. Consequatur omnis autem.\n \rQuia earum dolorem aperiam corrupti natus. Consequatur dolore non ex quos facere possimus. Quod dolor repellat eaque. Cupiditate et libero aut et architecto.\n \rAlias aspernatur quibusdam quas tenetur enim et est ut. Suscipit cum sint est esse sit quis quidem rerum omnis. Commodi praesentium autem quibusdam consequatur sapiente.\n \rEt sit deserunt dolore dolore. Dolor quia est molestiae corporis sit sed sunt. Libero et similique. Dicta quia dicta. Eos iusto inventore.\n \rAspernatur porro magni est minima quia corrupti et. Rem consequuntur pariatur exercitationem sed quas exercitationem rerum. Nemo nostrum in necessitatibus at eveniet fuga nemo ea.\n \rId fuga qui accusamus qui itaque. Tempora deleniti laboriosam natus cum non. At quisquam molestias quaerat dolore veritatis. Doloremque dolores repellat et totam reiciendis harum. In quo consequatur possimus non. Odit consectetur pariatur nam autem assumenda aliquam illum.\n \rAliquid sed optio earum at esse quam. Qui repudiandae ea. Accusantium accusamus neque iste temporibus harum. Non aut necessitatibus. Et sed voluptatibus qui harum similique eos ipsum.\n \rVel et repudiandae accusamus iure eum. Quisquam est nisi consequuntur impedit ut in et voluptates. Ipsa quia autem enim recusandae consequatur minima. Quia quisquam sint. In modi quis quo odio nisi ut eum.\n \rConsectetur totam omnis consequatur. Fuga sit consequuntur error reprehenderit modi. Eveniet iusto autem eaque. Possimus commodi vel.", + slug: "engage-marshall-islands-invoice", + images: [ + { + uid: "rc-upload-94ebpngytk", + name: "random-image.jpg", + url: "https://picsum.photos/800", + type: "image/jpeg", + size: 141940, + }, + ], + createdAt: "2022-10-12T08:05:07.450Z", + updatedAt: "2022-10-12T08:05:07.629Z", + category: { + id: "c079c036-8e49-42ad-bbdc-43110f305e3e", + title: "Music Platforms Hawaii", + createdAt: "2022-10-12T08:05:07.629Z", + updatedAt: "2022-10-12T08:05:07.629Z", + }, + user: { + id: "1bb3d392-9092-42eb-bb1a-bcf3f8cc1338", + firstName: "Kelsie", + lastName: "Murray", + email: "kelsie58@yahoo.com", + status: true, + createdAt: "2022-10-12T08:05:07.270Z", + updatedAt: "2022-10-12T08:05:07.270Z", + }, + tags: [ + { + id: "8b8f5c6f-52f1-48c5-8b73-972751697e25", + title: "concrete", + createdAt: "2022-10-12T08:05:07.277Z", + updatedAt: "2022-10-12T08:05:07.277Z", + }, + { + id: "4ef83b61-d7d2-46ad-81ad-c0cc288767b2", + title: "invoice", + createdAt: "2022-10-12T08:05:07.283Z", + updatedAt: "2022-10-12T08:05:07.283Z", + }, + { + id: "55e31eba-e003-457a-aca4-bc4cee719dee", + title: "enhance", + createdAt: "2022-10-12T08:05:07.289Z", + updatedAt: "2022-10-12T08:05:07.289Z", + }, + ], + }, + { + status: "draft", + id: "d62c6cb2-9bad-481e-8432-2680aca918ec", + title: "Avon heuristic Washington", + content: + "Qui quidem ut nisi necessitatibus iste velit voluptatem. Facere recusandae placeat similique tempore sit aut quia reiciendis. Harum et sed accusantium nesciunt. Quia deleniti ut ea consequatur magnam.\n \rTempora id sed omnis corrupti est impedit ipsum veritatis nemo. Eos laudantium perspiciatis. Animi quia voluptatem sit qui aperiam vel vel sunt ab.\n \rIncidunt rerum incidunt et maiores dignissimos beatae aut quo. Rerum enim facere sed quia nesciunt omnis incidunt provident ipsa. Quos qui ipsam.\n \rAb enim blanditiis qui minima. Non veritatis porro adipisci totam similique unde incidunt dolores. Placeat quod et alias corrupti tenetur non quas voluptas. Minima inventore magni illo quasi natus sequi. Amet iusto molestiae sit sed molestiae corporis autem. Alias modi iure est suscipit est.\n \rEius soluta dignissimos in. Minus vitae ut a. Officiis quam nobis sed esse eum dolores. Libero illo eos.\n \rDolores repudiandae dolorem quae in earum fugiat voluptatem. Et excepturi animi sed et nulla ipsa. Ut quo itaque excepturi amet omnis sint id. Exercitationem praesentium ratione sed vel qui. Molestiae et qui libero consequatur voluptatibus asperiores accusantium magni. Sed vero quo sed.\n \rTempora cum blanditiis. Vero eligendi eum quaerat facilis id dolore exercitationem eligendi. Hic eum voluptate animi saepe reprehenderit quos eos sint sit. Autem ut quia enim molestias magni molestias. Iusto ea tempore ex aperiam eos omnis animi quibusdam qui.\n \rEt deleniti autem asperiores aut nesciunt non enim labore voluptate. Tenetur odio est sapiente est ut. Ut consequatur voluptas ea eius ea. Rerum expedita molestias unde temporibus et. Quos suscipit magni quo et.\n \rQuia in ut tempore commodi. Ea quia reprehenderit iusto assumenda quas. Dolor omnis exercitationem officia dolorum aut. Veniam aliquid magnam aut perspiciatis temporibus pariatur.\n \rUt soluta porro aut neque accusamus aliquam. Sed nihil aut. Consequatur quod non vel tempore rerum aut molestiae.", + slug: "avon-heuristic-washington", + images: [ + { + uid: "rc-upload-o1apezp7ae", + name: "random-image.jpg", + url: "https://picsum.photos/800", + type: "image/jpeg", + size: 141940, + }, + ], + createdAt: "2022-10-12T08:05:07.503Z", + updatedAt: "2022-10-12T08:05:07.629Z", + category: { + id: "c079c036-8e49-42ad-bbdc-43110f305e3e", + title: "Music Platforms Hawaii", + createdAt: "2022-10-12T08:05:07.629Z", + updatedAt: "2022-10-12T08:05:07.629Z", + }, + user: { + id: "1bb3d392-9092-42eb-bb1a-bcf3f8cc1338", + firstName: "Kelsie", + lastName: "Murray", + email: "kelsie58@yahoo.com", + status: true, + createdAt: "2022-10-12T08:05:07.270Z", + updatedAt: "2022-10-12T08:05:07.270Z", + }, + tags: [ + { + id: "8b8f5c6f-52f1-48c5-8b73-972751697e25", + title: "concrete", + createdAt: "2022-10-12T08:05:07.277Z", + updatedAt: "2022-10-12T08:05:07.277Z", + }, + { + id: "4ef83b61-d7d2-46ad-81ad-c0cc288767b2", + title: "invoice", + createdAt: "2022-10-12T08:05:07.283Z", + updatedAt: "2022-10-12T08:05:07.283Z", + }, + { + id: "55e31eba-e003-457a-aca4-bc4cee719dee", + title: "enhance", + createdAt: "2022-10-12T08:05:07.289Z", + updatedAt: "2022-10-12T08:05:07.289Z", + }, + ], + }, + ], + count: 2, + total: 2, + page: 1, + pageCount: 1, + }, + [ + "Date", + "Wed, 12 Oct 2022 10:58:52 GMT", + "Content-Type", + "application/json; charset=utf-8", + "Content-Length", + "6179", + "Connection", + "close", + "Vary", + "Accept-Encoding", + "X-Powered-By", + "Express", + "Access-Control-Allow-Origin", + "*", + "ETag", + 'W/"1823-IZKKvFv/U47CIu6x2eTo3YvoRq4"', + ], + ); + +nock("https://api.nestjsx-crud.refine.dev:443", { encodedQueryParams: true }) + .get("/posts") + .query({}) + .reply( + 200, + [ + { + status: "published", + id: "1b175cdc-4407-49d9-82cd-35e9f31afec2", + title: "User-friendly New Mexico Bedfordshire", + content: + "Nobis autem asperiores ut ea architecto dignissimos. Velit id magnam quod corrupti adipisci. Ratione ut saepe rerum omnis dolores perspiciatis sed eos. Recusandae quia animi sint perferendis vero eius sunt commodi ut.\n \rPraesentium ut pariatur voluptatem minima repellendus. Dolor deleniti non cum nostrum accusantium. A deleniti est eveniet cupiditate quo praesentium. Quia sed illo aut voluptas.\n \rIpsum in et voluptatem. Neque qui rerum et quasi sint quis voluptates. Nobis eum dolores ut vel enim officiis. Adipisci accusantium non voluptas eveniet odio eius.\n \rDolore provident pariatur et dignissimos molestiae aut id nisi. Voluptas praesentium aliquid debitis natus sapiente sunt laudantium perferendis. Architecto possimus aut laudantium explicabo quia expedita quasi atque. Ut ut occaecati vel voluptas assumenda incidunt cumque totam.\n \rMollitia facere id. Delectus quo cum et eligendi. Qui non distinctio praesentium nihil tempora ea.\n \rUt accusantium autem occaecati. Eos quae minus autem neque et quis voluptates earum eos. Excepturi veniam dolores laborum porro dolorem dolores omnis ducimus velit. Nobis earum molestias similique. Dolorem sint recusandae ea nihil voluptatem nihil rerum. Autem a fugiat eligendi tempora ut ipsa.\n \rHarum soluta fuga. Esse non praesentium quo rerum velit labore. Et in officia veritatis ipsam qui distinctio. Culpa aut quia explicabo eum et dicta sed quia. In adipisci neque consequatur at.\n \rRerum sed aut nisi et enim ut. Qui at quis dicta omnis quia beatae id. Fugiat ducimus molestiae. Nisi ratione provident. Ipsam tempora cum vel odit assumenda quibusdam debitis.\n \rDoloremque repellendus voluptatem quis. Quo et eos eligendi libero quia tempora illum rerum. Quas eum et accusamus tenetur esse in eum rerum qui. Ratione vero perspiciatis aut. Aut aliquid cum saepe. Voluptatem quo molestiae sapiente voluptas.\n \rEt ut et velit officia sequi omnis placeat. Quia dignissimos a et deleniti tenetur ea. Asperiores et magnam earum quasi. Neque explicabo autem voluptate quasi ut. Similique repellendus optio non accusantium aut assumenda et quas.", + slug: "user-friendly-new-mexico-bedfordshire", + images: [ + { + uid: "rc-upload-an6pptxt4k", + name: "random-image.jpg", + url: "https://picsum.photos/800", + type: "image/jpeg", + size: 141940, + }, + ], + createdAt: "2021-06-21T11:13:32.165Z", + updatedAt: "2021-06-21T11:13:32.249Z", + category: { + id: "07f14be2-72b4-495e-8193-2e4ce70d9be9", + title: "Libyan Dinar Relationships Mexico", + createdAt: "2021-06-21T11:13:32.249Z", + updatedAt: "2021-06-21T11:13:32.249Z", + }, + user: { + id: "ebf27bb0-4e2c-4ea4-9081-1bfb56a966a0", + firstName: "Rosemarie", + lastName: "Schmitt", + email: "rosemarie_Schmitt63@hotmail.com", + status: true, + createdAt: "2021-06-21T11:13:32.073Z", + updatedAt: "2021-06-21T11:13:32.073Z", + }, + tags: [ + { + id: "244d1b9a-450c-44fc-914e-2e00c2493171", + title: "red", + createdAt: "2021-06-21T11:13:32.084Z", + updatedAt: "2021-06-21T11:13:32.084Z", + }, + { + id: "faa9f2ea-2181-4472-aff3-0c7ef9fd9c62", + title: "engineer", + createdAt: "2021-06-21T11:13:32.092Z", + updatedAt: "2021-06-21T11:13:32.092Z", + }, + { + id: "f283715b-54a0-43d1-8668-9b68ebc54ca3", + title: "transmitting", + createdAt: "2021-06-21T11:13:32.097Z", + updatedAt: "2021-06-21T11:13:32.097Z", + }, + ], + }, + ], + [ + "Server", + "nginx/1.17.10", + "Date", + "Mon, 21 Jun 2021 12:07:07 GMT", + "Content-Type", + "application/json; charset=utf-8", + "Content-Length", + "32420", + "Connection", + "close", + "Vary", + "Accept-Encoding", + "X-Powered-By", + "Express", + "Access-Control-Allow-Origin", + "*", + "ETag", + 'W/"7ea4-FLkQQcM/K2rMJ8DU8rb7gsS/ZRw"', + ], + ); diff --git a/packages/rest/src/data-providers/nestjsx-crud/specs/getList/index.spec.ts b/packages/rest/src/data-providers/nestjsx-crud/specs/getList/index.spec.ts new file mode 100644 index 000000000000..af32f3cf4074 --- /dev/null +++ b/packages/rest/src/data-providers/nestjsx-crud/specs/getList/index.spec.ts @@ -0,0 +1,131 @@ +import "./index.mock"; +import { nestjsxCrudDataProvider } from ".."; + +describe("getList", () => { + it("correct response", async () => { + const { data, total } = await nestjsxCrudDataProvider.getList({ + resource: "posts", + }); + + expect(data[0]["id"]).toBe("1b175cdc-4407-49d9-82cd-35e9f31afec2"); + expect(data[0]["title"]).toBe("User-friendly New Mexico Bedfordshire"); + expect(total).toBe(135); + }); + + it("correct sorting response", async () => { + const { data, total } = await nestjsxCrudDataProvider.getList({ + resource: "posts", + sorters: [ + { + field: "id", + order: "asc", + }, + ], + }); + + expect(data[0]["id"]).toBe("011edb32-f071-424a-8747-81d894f52906"); + expect(data[0]["title"]).toBe("Games initiatives online"); + expect(total).toBe(135); + }); + + it("correct filter response", async () => { + const { data, total } = await nestjsxCrudDataProvider.getList({ + resource: "posts", + filters: [ + { + field: "category.id", + operator: "in", + value: ["73bdc4c0-0cc2-49bb-bd6f-550deb795468"], + }, + ], + }); + + expect(data[0]["category"]["title"]).toBe("Deposit Capacitor Hdd"); + expect(total).toBe(24); + }); + + it("correct filter and sort response", async () => { + const { data, total } = await nestjsxCrudDataProvider.getList({ + resource: "posts", + filters: [ + { + field: "category.id", + operator: "in", + value: ["73bdc4c0-0cc2-49bb-bd6f-550deb795468"], + }, + ], + sorters: [ + { + field: "id", + order: "asc", + }, + ], + }); + + expect(data[0]["title"]).toBe("Games initiatives online"); + expect(total).toBe(24); + }); + + it("or/and correct filter response", async () => { + const { data, total } = await nestjsxCrudDataProvider.getList({ + resource: "posts", + filters: [ + { + key: "1", + operator: "or", + value: [ + { + key: "1.1", + operator: "and", + value: [ + { + field: "title", + operator: "startswith", + value: "a", + }, + { + field: "title", + operator: "contains", + value: "heuristic", + }, + ], + }, + { + key: "1.2", + operator: "and", + value: [ + { + field: "title", + operator: "startswith", + value: "e", + }, + { + field: "title", + operator: "contains", + value: "invoice", + }, + ], + }, + ], + }, + ], + }); + + expect(data[0]["title"]).toBe("Engage Marshall Islands invoice"); + expect(data[1]["title"]).toBe("Avon heuristic Washington"); + expect(total).toBe(2); + }); + + it("correct response without pagination", async () => { + const { data, total } = await nestjsxCrudDataProvider.getList({ + resource: "posts", + pagination: { + mode: "off", + }, + }); + + expect(data[0]["id"]).toBe("1b175cdc-4407-49d9-82cd-35e9f31afec2"); + expect(data[0]["title"]).toBe("User-friendly New Mexico Bedfordshire"); + expect(total).toBe(1); + }); +}); diff --git a/packages/rest/src/data-providers/nestjsx-crud/specs/getMany/index.mock.ts b/packages/rest/src/data-providers/nestjsx-crud/specs/getMany/index.mock.ts new file mode 100644 index 000000000000..50e53fbc4a21 --- /dev/null +++ b/packages/rest/src/data-providers/nestjsx-crud/specs/getMany/index.mock.ts @@ -0,0 +1,103 @@ +import nock from "nock"; + +nock("https://api.nestjsx-crud.refine.dev:443", { encodedQueryParams: true }) + .get("/posts") + .query({ + "filter%5B0%5D": + "id%7C%7C%24in%7C%7C6536e986-e500-4933-b154-b51d60d702c2%2C7810bbc3-b133-4f85-8c6b-d7806b329f17", + }) + .reply( + 200, + [ + { + id: "6536e986-e500-4933-b154-b51d60d702c2", + title: "Refined productize SMS", + content: + "Facilis minima rerum laboriosam aspernatur a reiciendis et enim. Facilis adipisci et aliquid rerum et. Molestias suscipit molestiae explicabo porro aut. Ut fugiat odit dolorem cum cumque consequatur labore in.\n \rAccusamus laboriosam enim nulla consequuntur voluptas et quidem veniam. Et et odit molestiae et. Aut distinctio aut qui eum fugit laudantium. Quo magnam natus consequatur voluptatibus accusamus qui quidem alias. Debitis numquam voluptatem eligendi voluptas eligendi amet et qui non. Velit recusandae non aut.\n \rCommodi sunt nemo aut explicabo. Nostrum veritatis ipsum cupiditate sequi minima possimus dolor accusantium alias. Dolor aut et quisquam cupiditate.\n \rAut voluptatibus sit. Pariatur sapiente ut iste rerum labore cum aut reprehenderit a. Eligendi dolor omnis reiciendis. Sit veniam laboriosam nesciunt fuga et. Libero molestias ex et odit aut.\n \rNeque dolorem aut rem culpa inventore. Fugiat occaecati enim eveniet corrupti aliquid eveniet autem ad voluptate. Qui ut repellendus minus eveniet unde reiciendis aut. Et at quis. Et enim qui voluptas suscipit autem fuga. Omnis omnis ea molestias.\n \rOdio est est distinctio natus. Nisi dolorem non fugit quasi molestiae fugiat. Autem aspernatur explicabo voluptatem et. Fuga non accusantium omnis nobis voluptatibus quisquam eum. Nam eveniet enim sunt iusto corrupti.\n \rSint veniam molestiae beatae reprehenderit quis. Et rem reprehenderit expedita. Qui quae repellendus qui eum. Id qui non laudantium rerum laudantium optio delectus.\n \rUllam nemo eius necessitatibus. Qui dolorum accusantium dolores neque reiciendis quisquam cum. Incidunt nam modi rerum et est dolorum. Rerum voluptatem rerum excepturi odit quo maxime rerum.\n \rVoluptas earum quod totam voluptas qui. Velit libero totam ut in eos. Alias molestiae alias eum quibusdam qui voluptate ex odit. Consequuntur quaerat suscipit velit sint consequuntur dolorem et. Vitae possimus hic culpa quod eveniet eum et vitae. Facilis minus voluptatem iusto id quia magnam sit rem ab.\n \rEt qui quas quidem ut. Asperiores dolor ipsa minus assumenda molestiae quis cum perferendis. Fugit voluptates sint accusantium. Omnis adipisci laboriosam.", + slug: "refined-productize-sms", + status: "draft", + images: [ + { + uid: "rc-upload-gcyg2ny4hz", + name: "random-image.jpg", + url: "https://picsum.photos/800", + type: "image/jpeg", + size: 141940, + percent: 100, + status: "done", + }, + ], + createdAt: "2021-04-05T17:21:02.945Z", + updatedAt: "2021-04-05T17:21:02.998Z", + category: { + id: "ab50ed75-e3df-477c-a8f7-e41b59848e9e", + title: "South Carolina Refined Fresh Ball Channels", + createdAt: "2021-04-05T17:21:02.998Z", + updatedAt: "2021-04-05T17:21:02.998Z", + }, + user: { + id: "8a6066b6-8034-46a4-af6a-8a84035673c4", + firstName: "Golda", + lastName: "Weissnat", + email: "golda_Weissnat0@hotmail.com", + status: true, + createdAt: "2021-04-05T17:21:02.758Z", + updatedAt: "2021-04-05T17:21:02.758Z", + }, + }, + { + id: "7810bbc3-b133-4f85-8c6b-d7806b329f17", + title: "Savings Account Money Market Account Rubber", + content: + "Cum voluptatem nulla. Aut libero aut eius placeat aspernatur et. Qui sed quos reprehenderit quia ducimus commodi aut non dolore.\n \rAb sed consequatur delectus eos ut alias minus quaerat sed. Tempore dolor necessitatibus aut dolores ab ipsa quia et qui. Possimus voluptatibus unde omnis modi quam magni. Harum aspernatur animi magni architecto saepe. Aut tempora et id.\n \rBeatae minima eum occaecati sed. Non alias dolor eos sint commodi. Incidunt voluptatem in sed dolorem laboriosam sit quod commodi excepturi. Sunt aperiam adipisci quidem aut non et. Nisi similique magni odio ipsum. Itaque quaerat earum nesciunt iure.\n \rDolorem rerum aut laboriosam exercitationem non alias et. Quibusdam et sed est in. Odio quaerat aperiam.\n \rMolestiae perferendis maiores est cum. Est sequi dolores et. Repellat et exercitationem eos laudantium in. Eos rerum voluptatibus et est atque. Numquam vel cumque aspernatur doloremque quas labore nulla. Sunt ducimus ut architecto minima nemo mollitia veniam.\n \rVel neque beatae aut. Quia ex eveniet molestiae ut tenetur quo qui totam. Odit distinctio quae voluptatem sed. Officia sunt cumque.\n \rDeleniti commodi ut quis ut adipisci et consequuntur eveniet. Aperiam qui sunt voluptatem. Aut nihil velit similique aspernatur quibusdam error omnis ullam. Soluta omnis enim dolorem qui optio sed possimus ut.\n \rTemporibus harum nihil laborum officiis iste quo ad officiis vitae. Repellat maiores consectetur. Aperiam est explicabo esse alias. Doloremque ea dolorum reprehenderit officia. Esse a corporis a delectus perferendis impedit. Illum necessitatibus explicabo odio voluptatem totam sed odio quam.\n \rEst dignissimos sit veritatis nihil suscipit voluptatum suscipit et. Rem facilis sit consectetur facilis veritatis ex. Quo assumenda debitis ex debitis et fugiat.\n \rQuaerat non inventore totam repudiandae. Dicta dolorem occaecati et eligendi impedit rerum nisi qui illum. Ipsam excepturi unde suscipit officia quae assumenda ut.", + slug: "savings-account-money-market-account-rubber", + status: "passive", + images: [ + { + uid: "rc-upload-mfj7h4fbd4", + name: "random-image.jpg", + url: "https://picsum.photos/800", + type: "image/jpeg", + size: 141940, + percent: 100, + status: "done", + }, + ], + createdAt: "2021-04-05T17:21:03.120Z", + updatedAt: "2021-04-05T17:21:03.424Z", + category: { + id: "aafa2e39-fdba-4987-bfd3-77fca8f76e89", + title: "Haptic Montserrat Automotive", + createdAt: "2021-04-05T17:21:03.424Z", + updatedAt: "2021-04-05T17:21:03.424Z", + }, + user: { + id: "c19675a7-ab4a-48c6-8d35-1d838da3a613", + firstName: "Brenna", + lastName: "Sauer", + email: "brenna.Sauer0@yahoo.com", + status: true, + createdAt: "2021-04-05T17:21:03.040Z", + updatedAt: "2021-04-05T17:21:03.040Z", + }, + }, + ], + [ + "Server", + "nginx/1.17.10", + "Date", + "Tue, 06 Apr 2021 07:31:38 GMT", + "Content-Type", + "application/json; charset=utf-8", + "Content-Length", + "5778", + "Connection", + "close", + "Vary", + "Accept-Encoding", + "X-Powered-By", + "Express", + "ETag", + 'W/"1692-Ime4tyI1Aed2XeQHcwcNB7u9vSY"', + ], + ); diff --git a/packages/rest/src/data-providers/nestjsx-crud/specs/getMany/index.spec.ts b/packages/rest/src/data-providers/nestjsx-crud/specs/getMany/index.spec.ts new file mode 100644 index 000000000000..d0626e3a9e48 --- /dev/null +++ b/packages/rest/src/data-providers/nestjsx-crud/specs/getMany/index.spec.ts @@ -0,0 +1,18 @@ +import "./index.mock"; +import { nestjsxCrudDataProvider } from ".."; + +describe("getMany", () => { + it("correct response", async () => { + const { data } = await nestjsxCrudDataProvider.getMany!({ + resource: "posts", + ids: [ + "6536e986-e500-4933-b154-b51d60d702c2", + "7810bbc3-b133-4f85-8c6b-d7806b329f17", + ], + }); + + expect(data[0]["id"]).toBe("6536e986-e500-4933-b154-b51d60d702c2"); + expect(data[1]["id"]).toBe("7810bbc3-b133-4f85-8c6b-d7806b329f17"); + expect(data.length).toBe(2); + }); +}); diff --git a/packages/rest/src/data-providers/nestjsx-crud/specs/getOne/index.mock.ts b/packages/rest/src/data-providers/nestjsx-crud/specs/getOne/index.mock.ts new file mode 100644 index 000000000000..0bedd8dfa2e7 --- /dev/null +++ b/packages/rest/src/data-providers/nestjsx-crud/specs/getOne/index.mock.ts @@ -0,0 +1,61 @@ +import nock from "nock"; + +nock("https://api.nestjsx-crud.refine.dev:443", { encodedQueryParams: true }) + .get("/posts/6536e986-e500-4933-b154-b51d60d702c2") + .reply( + 200, + { + id: "6536e986-e500-4933-b154-b51d60d702c2", + title: "Refined productize SMS", + content: + "Facilis minima rerum laboriosam aspernatur a reiciendis et enim. Facilis adipisci et aliquid rerum et. Molestias suscipit molestiae explicabo porro aut. Ut fugiat odit dolorem cum cumque consequatur labore in.\n \rAccusamus laboriosam enim nulla consequuntur voluptas et quidem veniam. Et et odit molestiae et. Aut distinctio aut qui eum fugit laudantium. Quo magnam natus consequatur voluptatibus accusamus qui quidem alias. Debitis numquam voluptatem eligendi voluptas eligendi amet et qui non. Velit recusandae non aut.\n \rCommodi sunt nemo aut explicabo. Nostrum veritatis ipsum cupiditate sequi minima possimus dolor accusantium alias. Dolor aut et quisquam cupiditate.\n \rAut voluptatibus sit. Pariatur sapiente ut iste rerum labore cum aut reprehenderit a. Eligendi dolor omnis reiciendis. Sit veniam laboriosam nesciunt fuga et. Libero molestias ex et odit aut.\n \rNeque dolorem aut rem culpa inventore. Fugiat occaecati enim eveniet corrupti aliquid eveniet autem ad voluptate. Qui ut repellendus minus eveniet unde reiciendis aut. Et at quis. Et enim qui voluptas suscipit autem fuga. Omnis omnis ea molestias.\n \rOdio est est distinctio natus. Nisi dolorem non fugit quasi molestiae fugiat. Autem aspernatur explicabo voluptatem et. Fuga non accusantium omnis nobis voluptatibus quisquam eum. Nam eveniet enim sunt iusto corrupti.\n \rSint veniam molestiae beatae reprehenderit quis. Et rem reprehenderit expedita. Qui quae repellendus qui eum. Id qui non laudantium rerum laudantium optio delectus.\n \rUllam nemo eius necessitatibus. Qui dolorum accusantium dolores neque reiciendis quisquam cum. Incidunt nam modi rerum et est dolorum. Rerum voluptatem rerum excepturi odit quo maxime rerum.\n \rVoluptas earum quod totam voluptas qui. Velit libero totam ut in eos. Alias molestiae alias eum quibusdam qui voluptate ex odit. Consequuntur quaerat suscipit velit sint consequuntur dolorem et. Vitae possimus hic culpa quod eveniet eum et vitae. Facilis minus voluptatem iusto id quia magnam sit rem ab.\n \rEt qui quas quidem ut. Asperiores dolor ipsa minus assumenda molestiae quis cum perferendis. Fugit voluptates sint accusantium. Omnis adipisci laboriosam.", + slug: "refined-productize-sms", + status: "draft", + images: [ + { + uid: "rc-upload-gcyg2ny4hz", + name: "random-image.jpg", + url: "https://picsum.photos/800", + type: "image/jpeg", + size: 141940, + percent: 100, + status: "done", + }, + ], + createdAt: "2021-04-05T17:21:02.945Z", + updatedAt: "2021-04-05T17:21:02.998Z", + category: { + id: "ab50ed75-e3df-477c-a8f7-e41b59848e9e", + title: "South Carolina Refined Fresh Ball Channels", + createdAt: "2021-04-05T17:21:02.998Z", + updatedAt: "2021-04-05T17:21:02.998Z", + }, + user: { + id: "8a6066b6-8034-46a4-af6a-8a84035673c4", + firstName: "Golda", + lastName: "Weissnat", + email: "golda_Weissnat0@hotmail.com", + status: true, + createdAt: "2021-04-05T17:21:02.758Z", + updatedAt: "2021-04-05T17:21:02.758Z", + }, + }, + [ + "Server", + "nginx/1.17.10", + "Date", + "Tue, 06 Apr 2021 07:33:04 GMT", + "Content-Type", + "application/json; charset=utf-8", + "Content-Length", + "2974", + "Connection", + "close", + "Vary", + "Accept-Encoding", + "X-Powered-By", + "Express", + "ETag", + 'W/"b9e-w8MQ3McIGDL5izkphstmliwGqxQ"', + ], + ); diff --git a/packages/rest/src/data-providers/nestjsx-crud/specs/getOne/index.spec.ts b/packages/rest/src/data-providers/nestjsx-crud/specs/getOne/index.spec.ts new file mode 100644 index 000000000000..00e98d94f791 --- /dev/null +++ b/packages/rest/src/data-providers/nestjsx-crud/specs/getOne/index.spec.ts @@ -0,0 +1,14 @@ +import "./index.mock"; +import { nestjsxCrudDataProvider } from ".."; + +describe("getOne", () => { + it("correct response", async () => { + const { data } = await nestjsxCrudDataProvider.getOne({ + resource: "posts", + id: "6536e986-e500-4933-b154-b51d60d702c2", + }); + + expect(data.id).toBe("6536e986-e500-4933-b154-b51d60d702c2"); + expect(data.title).toBe("Refined productize SMS"); + }); +}); diff --git a/packages/rest/src/data-providers/nestjsx-crud/specs/index.ts b/packages/rest/src/data-providers/nestjsx-crud/specs/index.ts new file mode 100644 index 000000000000..ecb0ff434cd2 --- /dev/null +++ b/packages/rest/src/data-providers/nestjsx-crud/specs/index.ts @@ -0,0 +1,6 @@ +import { createNestjsxCrudDataProvider } from "../"; + +export const { dataProvider: nestjsxCrudDataProvider } = + createNestjsxCrudDataProvider({ + apiURL: "https://api.nestjsx-crud.refine.dev", + }); diff --git a/packages/rest/src/data-providers/nestjsx-crud/specs/update/index.mock.ts b/packages/rest/src/data-providers/nestjsx-crud/specs/update/index.mock.ts new file mode 100644 index 000000000000..5756b4085a32 --- /dev/null +++ b/packages/rest/src/data-providers/nestjsx-crud/specs/update/index.mock.ts @@ -0,0 +1,63 @@ +import nock from "nock"; + +nock("https://api.nestjsx-crud.refine.dev:443", { encodedQueryParams: true }) + .patch("/posts/0b4faa6d-6726-4967-be13-e9d05d9aef7f", { + title: "updated-title", + }) + .reply( + 200, + { + status: "draft", + id: "0b4faa6d-6726-4967-be13-e9d05d9aef7f", + title: "updated-title", + content: + "Ipsum voluptas exercitationem. Nesciunt soluta et asperiores sequi dolorem. Dolor velit aut est omnis facilis quisquam quo.\n \rEst sed quas illo ut. Deserunt et quas quia sint consequatur veniam. Eius rerum totam quae non et omnis minima.\n \rAnimi corporis velit consequatur qui consequatur. Debitis consequuntur tempore aut asperiores. Eum delectus culpa aut exercitationem fugiat blanditiis eligendi facere vel. Autem quo delectus id fugit culpa delectus.\n \rSuscipit nesciunt voluptatum necessitatibus non. Eveniet minus natus suscipit odit. Accusantium itaque dolores quaerat vitae.\n \rAlias iusto sunt dolorem numquam et necessitatibus est ducimus. Enim necessitatibus maxime praesentium vitae deleniti. Et aliquid ipsam et dolorum molestias consectetur reiciendis tenetur. Incidunt eaque qui ut. Quia deleniti est aut est unde. Fuga repellendus eius est harum nisi non.\n \rOptio voluptas et rerum accusamus ut molestiae molestiae. Architecto et alias architecto rerum. Dicta atque qui facilis labore totam hic. Nobis rerum illum optio qui dolorem vitae. Animi sit eligendi. Quia doloribus ad atque omnis.\n \rReprehenderit explicabo quas autem recusandae cumque vel reiciendis est. Minima cum odit ut porro et nam vitae. At suscipit sequi corporis non quaerat. Quod sint repellat officia at repudiandae. Voluptatibus ipsa quibusdam tempore nulla optio quidem vel temporibus placeat.\n \rNeque et debitis et aliquid id sunt voluptatem tempora vel. Impedit accusamus ratione maiores eaque est velit vero porro dignissimos. Unde velit soluta natus.\n \rDolore ipsam rem quas. Veniam ducimus voluptate adipisci amet recusandae maiores voluptatem ducimus consectetur. Repellat ut nisi. Et ut expedita eligendi.\n \rLibero qui sint eum similique cupiditate accusamus. Vel autem ut tempora facilis voluptate. Voluptatem explicabo ut quae inventore dolorem laudantium consequuntur ut non. Rerum sint et voluptatem aliquam occaecati aspernatur eius quo.", + slug: "updated-title", + images: [ + { + uid: "rc-upload-v4rbubtwta", + name: "random-image.jpg", + url: "https://picsum.photos/800", + type: "image/jpeg", + size: 141940, + percent: 100, + status: "done", + }, + ], + createdAt: "2021-04-06T08:28:33.805Z", + updatedAt: "2021-04-06T08:29:47.520Z", + category: { + id: "8968123a-837b-4a24-9822-9712608549d7", + title: "Clear-Thinking Deposit Reduced", + createdAt: "2021-04-06T08:28:33.977Z", + updatedAt: "2021-04-06T08:28:33.977Z", + }, + user: { + id: "830a3394-249b-454e-b5d0-6f9e9aba0dd9", + firstName: "Easter", + lastName: "Fadel", + email: "easter_Fadel@yahoo.com", + status: true, + createdAt: "2021-04-06T08:28:33.755Z", + updatedAt: "2021-04-06T08:28:33.755Z", + }, + }, + [ + "Server", + "nginx/1.17.10", + "Date", + "Tue, 06 Apr 2021 08:29:47 GMT", + "Content-Type", + "application/json; charset=utf-8", + "Content-Length", + "2715", + "Connection", + "close", + "Vary", + "Accept-Encoding", + "X-Powered-By", + "Express", + "ETag", + 'W/"a9b-hoMHAMY1AnvTcfXX7ltKTbgrkRo"', + ], + ); diff --git a/packages/rest/src/data-providers/nestjsx-crud/specs/update/index.spec.ts b/packages/rest/src/data-providers/nestjsx-crud/specs/update/index.spec.ts new file mode 100644 index 000000000000..484662dbe1ce --- /dev/null +++ b/packages/rest/src/data-providers/nestjsx-crud/specs/update/index.spec.ts @@ -0,0 +1,15 @@ +import "./index.mock"; +import { nestjsxCrudDataProvider } from ".."; + +describe("update", () => { + it("correct response", async () => { + const { data } = await nestjsxCrudDataProvider.update({ + resource: "posts", + id: "0b4faa6d-6726-4967-be13-e9d05d9aef7f", + variables: { title: "updated-title" }, + }); + + expect(data["id"]).toBe("0b4faa6d-6726-4967-be13-e9d05d9aef7f"); + expect(data["title"]).toBe("updated-title"); + }); +}); diff --git a/packages/rest/src/data-providers/nestjsx-crud/specs/utils/handleFilter.spec.ts b/packages/rest/src/data-providers/nestjsx-crud/specs/utils/handleFilter.spec.ts new file mode 100644 index 000000000000..736190dc8f97 --- /dev/null +++ b/packages/rest/src/data-providers/nestjsx-crud/specs/utils/handleFilter.spec.ts @@ -0,0 +1,210 @@ +import { RequestQueryBuilder, type SCondition } from "@nestjsx/crud-request"; +import type { CrudFilters, CrudSorting, CrudFilter } from "@refinedev/core"; +import { + handleFilter, + handleSort, + generateSearchFilter, + createSearchQuery, +} from "../../utils"; + +describe("handleFilter", () => { + it("should apply filters to the query", () => { + let query = RequestQueryBuilder.create(); + + // Apply filters + const filters: CrudFilters = [ + { + field: "age", + operator: "gt", + value: 25, + }, + { + field: "name", + operator: "contains", + value: "John", + }, + ]; + + query = handleFilter(query, filters); + + expect(decodeURIComponent(query.query())).toEqual( + 's={"$and":[{"age":{"$gt":25}},{"name":{"$contL":"John"}}]}', + ); + }); + + it("should not apply filters if none are provided", () => { + let query = RequestQueryBuilder.create(); + + query = handleFilter(query); + + expect(query.query()).toEqual(""); + }); + + it("should work with complex sort and filter parameters", () => { + let query = RequestQueryBuilder.create(); + + const sorters: CrudSorting = [ + { + field: "name", + order: "asc", + }, + ]; + + const filters: CrudFilters = [ + { + field: "age", + operator: "gte", + value: 18, + }, + { + field: "email", + operator: "eq", + value: "john", + }, + ]; + + query = handleSort(query, sorters); + query = handleFilter(query, filters); + + const expectedQuery = + 'sort[0]=name,ASC&s={"$and":[{"age":{"$gte":18}},{"email":{"$eq":"john"}}]}'; + + expect(decodeURIComponent(query.query())).toEqual(expectedQuery); + }); +}); + +describe("generateSearchFilter", () => { + it("should generate a search filter with 'and' operator", () => { + const filters: CrudFilters = [ + { + field: "name", + operator: "contains", + value: "John", + }, + { + field: "age", + operator: "gte", + value: 25, + }, + ]; + + const searchFilter = generateSearchFilter(filters); + + expect(searchFilter).toEqual({ + $and: [{ name: { $contL: "John" } }, { age: { $gte: 25 } }], + }); + }); + + it("should generate an empty search filter with 'and' operator when no filters are provided", () => { + const filters: CrudFilters = []; + + const searchFilter = generateSearchFilter(filters); + + expect(searchFilter).toEqual({ + $and: [], + }); + }); +}); + +describe("createSearchQuery", () => { + it("should create a search query for simple filters", () => { + const filter: CrudFilter = { + field: "testField", + operator: "gt", + value: 42, + }; + + const expectedQuery: SCondition = { testField: { $gt: 42 } }; + + expect(createSearchQuery(filter)).toEqual(expectedQuery); + }); + + it("should create a search query for complex filters", () => { + const filter: CrudFilter = { + operator: "and", + value: [ + { + field: "testField", + operator: "lt", + value: 10, + }, + { + field: "anotherField", + operator: "ne", + value: "testValue", + }, + ], + }; + + const expectedQuery: SCondition = { + $and: [ + { + testField: { + $lt: 10, + }, + }, + { + anotherField: { + $ne: "testValue", + }, + }, + ], + }; + + expect(createSearchQuery(filter)).toEqual(expectedQuery); + }); + + it("should create a search query for complex filters with nested filters", () => { + const filter: CrudFilter = { + operator: "and", + value: [ + { + field: "testField", + operator: "lt", + value: 10, + }, + { + operator: "or", + value: [ + { + field: "anotherField", + operator: "ne", + value: "testValue", + }, + { + field: "yetAnotherField", + operator: "eq", + value: "testValue2", + }, + ], + }, + ], + }; + + const expectedQuery: SCondition = { + $and: [ + { + testField: { + $lt: 10, + }, + }, + { + $or: [ + { + anotherField: { + $ne: "testValue", + }, + }, + { + yetAnotherField: { + $eq: "testValue2", + }, + }, + ], + }, + ], + }; + + expect(createSearchQuery(filter)).toEqual(expectedQuery); + }); +}); diff --git a/packages/rest/src/data-providers/nestjsx-crud/specs/utils/handleJoin.spec.ts b/packages/rest/src/data-providers/nestjsx-crud/specs/utils/handleJoin.spec.ts new file mode 100644 index 000000000000..a5ae21c525f9 --- /dev/null +++ b/packages/rest/src/data-providers/nestjsx-crud/specs/utils/handleJoin.spec.ts @@ -0,0 +1,82 @@ +import { + RequestQueryBuilder, + type QueryJoin, + type QueryJoinArr, +} from "@nestjsx/crud-request"; +import { handleFilter, handleJoin, handleSort } from "../../utils"; +import type { CrudFilters, CrudSorting } from "@refinedev/core"; + +describe("handleJoin", () => { + it("should apply join to the query", () => { + let query = RequestQueryBuilder.create(); + + const join: QueryJoin | QueryJoinArr | (QueryJoin | QueryJoinArr)[] = [ + { + field: "profile", + select: ["name", "age"], + }, + { + field: "posts", + select: ["title", "content"], + }, + ]; + + query = handleJoin(query, join); + + expect(decodeURIComponent(query.query())).toEqual( + "join[0]=profile||name,age&join[1]=posts||title,content", + ); + }); + + it("should not apply join if none is provided", () => { + let query = RequestQueryBuilder.create(); + + query = handleJoin(query); + + expect(query.query()).toEqual(""); + }); + + it("should work with complex sort and filter parameters", () => { + let query = RequestQueryBuilder.create(); + + const sorters: CrudSorting = [ + { + field: "name", + order: "asc", + }, + ]; + + const filters: CrudFilters = [ + { + field: "age", + operator: "gte", + value: 18, + }, + { + field: "email", + operator: "eq", + value: "john", + }, + ]; + + const join: QueryJoin | QueryJoinArr | (QueryJoin | QueryJoinArr)[] = [ + { + field: "profile", + select: ["name", "age"], + }, + { + field: "posts", + select: ["title", "content"], + }, + ]; + + query = handleJoin(query, join); + query = handleSort(query, sorters); + query = handleFilter(query, filters); + + const expectedQuery = + 'join[0]=profile||name,age&join[1]=posts||title,content&sort[0]=name,ASC&s={"$and":[{"age":{"$gte":18}},{"email":{"$eq":"john"}}]}'; + + expect(decodeURIComponent(query.query())).toEqual(expectedQuery); + }); +}); diff --git a/packages/rest/src/data-providers/nestjsx-crud/specs/utils/handlePagination.spec.ts b/packages/rest/src/data-providers/nestjsx-crud/specs/utils/handlePagination.spec.ts new file mode 100644 index 000000000000..f432988f55b8 --- /dev/null +++ b/packages/rest/src/data-providers/nestjsx-crud/specs/utils/handlePagination.spec.ts @@ -0,0 +1,83 @@ +import { RequestQueryBuilder } from "@nestjsx/crud-request"; +import { handleFilter, handlePagination, handleSort } from "../../utils"; +import type { CrudFilters, CrudSorting, Pagination } from "@refinedev/core"; + +describe("handlePagination", () => { + it("should apply pagination for server mode", () => { + let query = RequestQueryBuilder.create(); + const pagination: Pagination = { + currentPage: 2, + pageSize: 20, + mode: "server", + }; + + query = handlePagination(query, pagination); + + expect(query.query()).toContain("limit=20"); + expect(query.query()).toContain("page=2"); + expect(query.query()).toContain("offset=20"); + }); + + it("should use default pagination values if not provided", () => { + let query = RequestQueryBuilder.create(); + + query = handlePagination(query); + + expect(query.query()).toContain("limit=10"); + expect(query.query()).toContain("page=1"); + expect(query.query()).toContain("offset=0"); + }); + + it("should not apply pagination for client mode", () => { + let query = RequestQueryBuilder.create(); + const pagination: Pagination = { + currentPage: 3, + pageSize: 15, + mode: "client", + }; + + query = handlePagination(query, pagination); + + expect(query.query()).not.toContain("limit=15"); + expect(query.query()).not.toContain("page=3"); + expect(query.query()).not.toContain("offset=30"); + }); + + it("should work with complex sort and filter parameters", () => { + let query = RequestQueryBuilder.create(); + + const pagination: Pagination = { + currentPage: 3, + pageSize: 15, + }; + + const sorters: CrudSorting = [ + { + field: "name", + order: "asc", + }, + ]; + + const filters: CrudFilters = [ + { + field: "age", + operator: "gte", + value: 18, + }, + { + field: "email", + operator: "eq", + value: "john", + }, + ]; + + query = handleSort(query, sorters); + query = handleFilter(query, filters); + query = handlePagination(query, pagination); + + const expectedQuery = + 'sort[0]=name,ASC&s={"$and":[{"age":{"$gte":18}},{"email":{"$eq":"john"}}]}&limit=15&page=3&offset=30'; + + expect(decodeURIComponent(query.query())).toEqual(expectedQuery); + }); +}); diff --git a/packages/rest/src/data-providers/nestjsx-crud/specs/utils/handleSort.spec.ts b/packages/rest/src/data-providers/nestjsx-crud/specs/utils/handleSort.spec.ts new file mode 100644 index 000000000000..654c498c2fc1 --- /dev/null +++ b/packages/rest/src/data-providers/nestjsx-crud/specs/utils/handleSort.spec.ts @@ -0,0 +1,148 @@ +import { RequestQueryBuilder, type QuerySort } from "@nestjsx/crud-request"; +import type { CrudFilters, CrudSorting } from "@refinedev/core"; +import { handleFilter, generateSort, handleSort } from "../../utils"; + +describe("handleSort", () => { + it("should not modify the query if no sorters are provided", () => { + let query = RequestQueryBuilder.create(); + const initialQuery = query.query(); + + const sorters: CrudSorting = []; + + query = handleSort(query, sorters); + + expect(query.query()).toEqual(initialQuery); + }); + + it("should add sortBy to the query if sorters are provided", () => { + let query = RequestQueryBuilder.create(); + + const sorters: CrudSorting = [ + { + field: "field1", + order: "asc", + }, + { + field: "field2", + order: "desc", + }, + ]; + + query = handleSort(query, sorters); + + const expectedSortBy = "sort[0]=field1,ASC&sort[1]=field2,DESC"; + expect(decodeURIComponent(query.query())).toEqual(expectedSortBy); + }); + + it("should ignore invalid sort entries", () => { + let query = RequestQueryBuilder.create(); + + const sorters: CrudSorting = [ + { + field: "field1", + order: "asc", + }, + { + field: "", + order: "desc", + }, + ]; + + query = handleSort(query, sorters); + + const expectedSortBy = "sort[0]=field1,ASC"; + expect(decodeURIComponent(query.query())).toEqual(expectedSortBy); + }); + + it("should work with complex sort and filter parameters", () => { + let query = RequestQueryBuilder.create(); + + const sorters: CrudSorting = [ + { + field: "name", + order: "asc", + }, + ]; + + const filters: CrudFilters = [ + { + field: "age", + operator: "gte", + value: 18, + }, + { + field: "email", + operator: "eq", + value: "john", + }, + ]; + + query = handleSort(query, sorters); + query = handleFilter(query, filters); + + const expectedQuery = + 'sort[0]=name,ASC&s={"$and":[{"age":{"$gte":18}},{"email":{"$eq":"john"}}]}'; + + expect(decodeURIComponent(query.query())).toEqual(expectedQuery); + }); +}); + +describe("generateSort", () => { + it("should return undefined when no sorting is provided", () => { + expect(generateSort()).toBeUndefined(); + }); + + it("should generate an array of sort objects when sorting is provided", () => { + const sort: CrudSorting = [ + { + field: "field1", + order: "asc", + }, + { + field: "field2", + order: "desc", + }, + ]; + + const expectedSort: QuerySort[] = [ + { + field: "field1", + order: "ASC", + }, + { + field: "field2", + order: "DESC", + }, + ]; + + expect(generateSort(sort)).toEqual(expectedSort); + }); + + it("should ignore invalid sort entries", () => { + const sort: CrudSorting = [ + { + field: "field1", + order: "asc", + }, + { + field: "", + order: "desc", + }, + ]; + + const expectedSort: QuerySort[] = [ + { + field: "field1", + order: "ASC", + }, + ]; + + expect(generateSort(sort)).toEqual(expectedSort); + }); + + it("should return undefined when array is empty", () => { + const sort: CrudSorting = []; + + expect(generateSort(sort)).toBeUndefined(); + }); +}); diff --git a/packages/rest/src/data-providers/nestjsx-crud/specs/utils/mapOperator.spec.ts b/packages/rest/src/data-providers/nestjsx-crud/specs/utils/mapOperator.spec.ts new file mode 100644 index 000000000000..e9d84523fe24 --- /dev/null +++ b/packages/rest/src/data-providers/nestjsx-crud/specs/utils/mapOperator.spec.ts @@ -0,0 +1,45 @@ +import { type ComparisonOperator, CondOperator } from "@nestjsx/crud-request"; +import type { CrudOperators } from "@refinedev/core"; +import { mapOperator } from "../../utils"; + +describe("mapOperator", () => { + it("should map CrudOperators to ComparisonOperator", () => { + const operators: Partial> = { + and: "$and", + or: "$or", + ne: CondOperator.NOT_EQUALS, + lt: CondOperator.LOWER_THAN, + gt: CondOperator.GREATER_THAN, + lte: CondOperator.LOWER_THAN_EQUALS, + gte: CondOperator.GREATER_THAN_EQUALS, + in: CondOperator.IN, + nin: CondOperator.NOT_IN, + contains: CondOperator.CONTAINS_LOW, + ncontains: CondOperator.EXCLUDES_LOW, + containss: CondOperator.CONTAINS, + ncontainss: CondOperator.EXCLUDES, + null: CondOperator.IS_NULL, + nnull: CondOperator.NOT_NULL, + startswith: CondOperator.STARTS_LOW, + startswiths: CondOperator.STARTS, + endswith: CondOperator.ENDS_LOW, + endswiths: CondOperator.ENDS, + between: CondOperator.BETWEEN, + eq: CondOperator.EQUALS, + nbetween: CondOperator.EQUALS, + nendswith: CondOperator.EQUALS, + nendswiths: CondOperator.EQUALS, + nstartswith: CondOperator.EQUALS, + nstartswiths: CondOperator.EQUALS, + }; + + Object.entries(operators).forEach(([key, value]) => { + expect(mapOperator(key as CrudOperators)).toBe(value); + }); + }); + + it("should return CondOperator.EQUALS for unsupported CrudOperators", () => { + const unsupportedOperator: CrudOperators = "unsupported" as CrudOperators; + expect(mapOperator(unsupportedOperator)).toBe(CondOperator.EQUALS); + }); +}); diff --git a/packages/rest/src/data-providers/nestjsx-crud/specs/utils/transformErrorMessages.spec.ts b/packages/rest/src/data-providers/nestjsx-crud/specs/utils/transformErrorMessages.spec.ts new file mode 100644 index 000000000000..f84e51041dbc --- /dev/null +++ b/packages/rest/src/data-providers/nestjsx-crud/specs/utils/transformErrorMessages.spec.ts @@ -0,0 +1,19 @@ +import { transformErrorMessages } from "../../utils"; + +describe("transformErrorMessages", () => { + it("should transform error messages", () => { + const errorMessages = [ + "title should not be empty", + "status must be a valid enum value", + "status should not be empty", + ]; + + expect(transformErrorMessages(errorMessages)).toEqual({ + title: ["title should not be empty"], + status: [ + "status must be a valid enum value", + "status should not be empty", + ], + }); + }); +}); diff --git a/packages/rest/src/data-providers/nestjsx-crud/utils/handleFilter.ts b/packages/rest/src/data-providers/nestjsx-crud/utils/handleFilter.ts new file mode 100644 index 000000000000..4f466b5c4c29 --- /dev/null +++ b/packages/rest/src/data-providers/nestjsx-crud/utils/handleFilter.ts @@ -0,0 +1,43 @@ +import type { CrudFilters, CrudFilter } from "@refinedev/core"; +import type { RequestQueryBuilder, SCondition } from "@nestjsx/crud-request"; +import { mapOperator } from "./mapOperator"; + +export const generateSearchFilter = (filters: CrudFilters): SCondition => { + return createSearchQuery({ + operator: "and", + value: filters, + }); +}; + +export const createSearchQuery = (filter: CrudFilter): SCondition => { + if ( + filter.operator !== "and" && + filter.operator !== "or" && + "field" in filter + ) { + // query + return { + [filter.field]: { + [mapOperator(filter.operator)]: filter.value, + }, + }; + } + + const { operator } = filter; + + return { + [mapOperator(operator)]: filter.value.map((filter) => + createSearchQuery(filter), + ), + }; +}; + +export const handleFilter = ( + query: RequestQueryBuilder, + filters?: CrudFilters, +) => { + if (Array.isArray(filters) && filters.length > 0) { + query.search(generateSearchFilter(filters)); + } + return query; +}; diff --git a/packages/rest/src/data-providers/nestjsx-crud/utils/handleJoin.ts b/packages/rest/src/data-providers/nestjsx-crud/utils/handleJoin.ts new file mode 100644 index 000000000000..e40e42def2fd --- /dev/null +++ b/packages/rest/src/data-providers/nestjsx-crud/utils/handleJoin.ts @@ -0,0 +1,15 @@ +import type { + RequestQueryBuilder, + QueryJoin, + QueryJoinArr, +} from "@nestjsx/crud-request"; + +export const handleJoin = ( + query: RequestQueryBuilder, + join?: QueryJoin | QueryJoinArr | (QueryJoin | QueryJoinArr)[], +) => { + if (join) { + query.setJoin(join); + } + return query; +}; diff --git a/packages/rest/src/data-providers/nestjsx-crud/utils/handlePagination.ts b/packages/rest/src/data-providers/nestjsx-crud/utils/handlePagination.ts new file mode 100644 index 000000000000..c68125b4ba83 --- /dev/null +++ b/packages/rest/src/data-providers/nestjsx-crud/utils/handlePagination.ts @@ -0,0 +1,18 @@ +import type { RequestQueryBuilder } from "@nestjsx/crud-request"; +import type { Pagination } from "@refinedev/core"; + +export const handlePagination = ( + query: RequestQueryBuilder, + pagination?: Pagination, +) => { + const { currentPage = 1, pageSize = 10, mode = "server" } = pagination ?? {}; + + if (mode === "server") { + query + .setLimit(pageSize) + .setPage(currentPage) + .setOffset((currentPage - 1) * pageSize); + } + + return query; +}; diff --git a/packages/rest/src/data-providers/nestjsx-crud/utils/handleSort.ts b/packages/rest/src/data-providers/nestjsx-crud/utils/handleSort.ts new file mode 100644 index 000000000000..c0dd5b7825b2 --- /dev/null +++ b/packages/rest/src/data-providers/nestjsx-crud/utils/handleSort.ts @@ -0,0 +1,38 @@ +import type { + RequestQueryBuilder, + QuerySort, + QuerySortArr, + QuerySortOperator, +} from "@nestjsx/crud-request"; +import type { CrudSorting } from "@refinedev/core"; + +export type SortBy = QuerySort | QuerySortArr | Array; + +export const generateSort = (sort?: CrudSorting): SortBy | undefined => { + if (sort && sort.length > 0) { + const multipleSort: SortBy = []; + sort.map(({ field, order }) => { + if (field && order) { + multipleSort.push({ + field: field, + order: order.toUpperCase() as QuerySortOperator, + }); + } + }); + return multipleSort; + } + + return; +}; + +export const handleSort = ( + query: RequestQueryBuilder, + sorters?: CrudSorting, +) => { + const sortBy = generateSort(sorters); + if (sortBy) { + query.sortBy(sortBy); + } + + return query; +}; diff --git a/packages/rest/src/data-providers/nestjsx-crud/utils/index.ts b/packages/rest/src/data-providers/nestjsx-crud/utils/index.ts new file mode 100644 index 000000000000..2e42fafbc9f3 --- /dev/null +++ b/packages/rest/src/data-providers/nestjsx-crud/utils/index.ts @@ -0,0 +1,7 @@ +export * from "./handleFilter"; +export * from "./handleJoin"; +export * from "./handlePagination"; +export * from "./handleSort"; +export * from "./mapOperator"; +export * from "./transformErrorMessages"; +export * from "./transformHttpError"; diff --git a/packages/rest/src/data-providers/nestjsx-crud/utils/mapOperator.ts b/packages/rest/src/data-providers/nestjsx-crud/utils/mapOperator.ts new file mode 100644 index 000000000000..1e4c6874953e --- /dev/null +++ b/packages/rest/src/data-providers/nestjsx-crud/utils/mapOperator.ts @@ -0,0 +1,51 @@ +import { type ComparisonOperator, CondOperator } from "@nestjsx/crud-request"; +import type { CrudOperators } from "@refinedev/core"; + +export const mapOperator = (operator: CrudOperators): ComparisonOperator => { + switch (operator) { + case "and": + return "$and"; + case "or": + return "$or"; + case "eq": + return CondOperator.EQUALS; + case "ne": + return CondOperator.NOT_EQUALS; + case "lt": + return CondOperator.LOWER_THAN; + case "gt": + return CondOperator.GREATER_THAN; + case "lte": + return CondOperator.LOWER_THAN_EQUALS; + case "gte": + return CondOperator.GREATER_THAN_EQUALS; + case "in": + return CondOperator.IN; + case "nin": + return CondOperator.NOT_IN; + case "contains": + return CondOperator.CONTAINS_LOW; + case "ncontains": + return CondOperator.EXCLUDES_LOW; + case "containss": + return CondOperator.CONTAINS; + case "ncontainss": + return CondOperator.EXCLUDES; + case "null": + return CondOperator.IS_NULL; + case "nnull": + return CondOperator.NOT_NULL; + case "startswith": + return CondOperator.STARTS_LOW; + case "startswiths": + return CondOperator.STARTS; + case "endswith": + return CondOperator.ENDS_LOW; + case "endswiths": + return CondOperator.ENDS; + case "between": + return CondOperator.BETWEEN; + } + + return CondOperator.EQUALS; +}; diff --git a/packages/rest/src/data-providers/nestjsx-crud/utils/transformErrorMessages.ts b/packages/rest/src/data-providers/nestjsx-crud/utils/transformErrorMessages.ts new file mode 100644 index 000000000000..909ccaa2a60e --- /dev/null +++ b/packages/rest/src/data-providers/nestjsx-crud/utils/transformErrorMessages.ts @@ -0,0 +1,22 @@ +type TransformedErrors = { + [key: string]: string[]; +}; + +export const transformErrorMessages = ( + errorMessages: string[], +): TransformedErrors => { + const transformedErrors: TransformedErrors = {}; + + for (const errorMessage of errorMessages) { + const separatorIndex = errorMessage.indexOf(" "); + const field = errorMessage.substring(0, separatorIndex); + + if (transformedErrors[field]) { + transformedErrors[field].push(errorMessage); + } else { + transformedErrors[field] = [errorMessage]; + } + } + + return transformedErrors; +}; diff --git a/packages/rest/src/data-providers/nestjsx-crud/utils/transformHttpError.ts b/packages/rest/src/data-providers/nestjsx-crud/utils/transformHttpError.ts new file mode 100644 index 000000000000..b4bc17c7371f --- /dev/null +++ b/packages/rest/src/data-providers/nestjsx-crud/utils/transformHttpError.ts @@ -0,0 +1,19 @@ +import type { HttpError } from "@refinedev/core"; + +import { transformErrorMessages } from "./transformErrorMessages"; + +export const transformHttpError = (error: any): HttpError => { + const message = error.error; + const statusCode = error.statusCode; + const errorMessages = error.message; + + const errors = transformErrorMessages(errorMessages); + + const httpError: HttpError = { + statusCode, + message, + errors, + }; + + return httpError; +}; diff --git a/packages/rest/src/data-providers/simple-rest/index.ts b/packages/rest/src/data-providers/simple-rest/index.ts new file mode 100644 index 000000000000..e70005562065 --- /dev/null +++ b/packages/rest/src/data-providers/simple-rest/index.ts @@ -0,0 +1,17 @@ +import type { Options as KyOptions } from "ky"; + +import { createDataProvider } from "../../create-data-provider"; +import { simpleRestDataProviderOptions } from "./simple-rest.options"; + +type CreateSimpleRestDataProviderParams = { + apiURL: string; + kyOptions?: KyOptions; +}; + +export const createSimpleRestDataProvider = ( + params: CreateSimpleRestDataProviderParams, +) => { + const { apiURL, kyOptions } = params; + + return createDataProvider(apiURL, simpleRestDataProviderOptions, kyOptions); +}; diff --git a/packages/rest/src/data-providers/simple-rest/simple-rest.options.ts b/packages/rest/src/data-providers/simple-rest/simple-rest.options.ts new file mode 100644 index 000000000000..06017d09fa36 --- /dev/null +++ b/packages/rest/src/data-providers/simple-rest/simple-rest.options.ts @@ -0,0 +1,216 @@ +import type { + CreateParams, + CrudOperators, + CustomParams, + DeleteOneParams, + GetListParams, + GetManyParams, + GetOneParams, + UpdateParams, +} from "@refinedev/core"; +import type { KyResponse } from "ky"; +import type { AnyObject } from "../../types"; + +const mapOperator = (operator: CrudOperators): string => { + switch (operator) { + case "ne": + case "gte": + case "lte": + return `_${operator}`; + case "contains": + return "_like"; + default: + return ""; + } +}; + +export const simpleRestDataProviderOptions = { + getList: { + getEndpoint(params: GetListParams): string { + return `${params.resource}`; + }, + async buildFilters(params: GetListParams) { + const { filters = [] } = params; + + const queryFilters: Record = {}; + + filters.map((filter) => { + if ("field" in filter) { + const { field, operator, value } = filter; + + if (field === "q") { + queryFilters[field] = value; + return; + } + + const mappedOperator = mapOperator(operator); + queryFilters[`${field}${mappedOperator}`] = value; + } + }); + + return queryFilters; + }, + async buildPagination(params: GetListParams) { + const { pagination } = params; + const { currentPage = 1, pageSize = 10 } = pagination ?? {}; + + const _start = (currentPage - 1) * pageSize; + const _end = currentPage * pageSize; + + return { _start, _end }; + }, + async buildSorters(params: GetListParams) { + const { sorters = [] } = params; + + if (!sorters.length) return {}; + + const sort: string[] = []; + const order: string[] = []; + + sorters.forEach((item) => { + sort.push(item.field); + order.push(item.order); + }); + + const _sort = sort.join(","); + const _order = order.join(","); + + return { + _sort, + _order, + }; + }, + async buildQueryParams(params: GetListParams) { + const filters = await this.buildFilters(params); + + const sorters = await this.buildSorters(params); + + const pagination = await this.buildPagination(params); + + return { ...filters, ...sorters, ...pagination }; + }, + async mapResponse( + response: KyResponse, + params: GetListParams, + ): Promise { + return await response.json(); + }, + async getTotalCount( + response: KyResponse, + params: GetListParams, + ): Promise { + const totalCount = response.headers.get("x-total-count") ?? 0; + + return +totalCount; + }, + }, + getOne: { + getEndpoint(params: GetOneParams) { + return `${params.resource}/${params.id}`; + }, + async buildQueryParams(params: GetOneParams) { + return {}; + }, + async mapResponse( + response: KyResponse, + params: GetOneParams, + ): Promise> { + return await response.json(); + }, + }, + getMany: { + getEndpoint(params: GetListParams) { + return `${params.resource}`; + }, + async buildQueryParams(params: GetManyParams) { + return { ids: params.ids }; + }, + async mapResponse( + response: KyResponse, + params: GetListParams, + ) { + return await response.json(); + }, + }, + create: { + getEndpoint(params: CreateParams): string { + return `${params.resource}`; + }, + async buildQueryParams(params: CreateParams) { + return {}; + }, + async buildBodyParams(params: CreateParams) { + return params.variables; + }, + async mapResponse( + response: KyResponse, + params: CreateParams, + ): Promise> { + return await response.json(); + }, + }, + update: { + getEndpoint(params: UpdateParams): string { + return `${params.resource}/${params.id}`; + }, + async buildQueryParams(params: UpdateParams) { + return {}; + }, + async buildBodyParams(params: UpdateParams) { + return params.variables; + }, + async mapResponse( + response: KyResponse, + params: UpdateParams, + ) { + return await response.json(); + }, + }, + deleteOne: { + getEndpoint(params: DeleteOneParams) { + return `${params.resource}/${params.id}`; + }, + async buildQueryParams(params: DeleteOneParams) { + return {}; + }, + async mapResponse( + response: KyResponse, + params: DeleteOneParams, + ) { + return await response.json(); + }, + }, + custom: { + async buildQueryParams(params: CustomParams) { + return params.query; + }, + async buildBodyParams(params: CustomParams) { + return params.payload ?? {}; + }, + async buildHeaders(params: CustomParams) { + return params.headers ?? {}; + }, + async mapResponse( + response: KyResponse, + params: CustomParams, + ) { + return await response.json(); + }, + }, + getAuthHeader: async () => { + return { + headerName: "Authorization", + headerValue: `Bearer ${localStorage.getItem("token")}`, + }; + }, + refreshToken: { + getEndpoint: (apiURL: string) => "/refresh-token", + async persistTokens(mapResponseResult: Record) { + localStorage.setItem("token", mapResponseResult.token); + localStorage.setItem("refreshToken", mapResponseResult.refreshToken); + }, + async mapResponse(response: KyResponse) { + return await response.json(); + }, + }, +}; diff --git a/packages/rest/src/data-providers/simple-rest/specs/create/index.mock.ts b/packages/rest/src/data-providers/simple-rest/specs/create/index.mock.ts new file mode 100644 index 000000000000..138c7a728b6b --- /dev/null +++ b/packages/rest/src/data-providers/simple-rest/specs/create/index.mock.ts @@ -0,0 +1,36 @@ +import nock from "nock"; + +nock("https://api.fake-rest.refine.dev:443", { encodedQueryParams: true }) + .post("/posts", { id: 1001, title: "foo", content: "bar" }) + .reply(201, { id: 1001, title: "foo", content: "bar" }, [ + "Server", + "nginx/1.17.10", + "Date", + "Tue, 30 Mar 2021 11:33:00 GMT", + "Content-Type", + "application/json; charset=utf-8", + "Content-Length", + "54", + "Connection", + "close", + "X-Powered-By", + "Express", + "Vary", + "Origin, X-HTTP-Method-Override, Accept-Encoding", + "Access-Control-Allow-Credentials", + "true", + "Cache-Control", + "no-cache", + "Pragma", + "no-cache", + "Expires", + "-1", + "Access-Control-Expose-Headers", + "Location", + "Location", + "http://api.fake-rest.refine.dev/posts/1001", + "X-Content-Type-Options", + "nosniff", + "ETag", + 'W/"36-aUpCEyKGcTsfoD+czkB83vdqSGs"', + ]); diff --git a/packages/rest/src/data-providers/simple-rest/specs/create/index.spec.ts b/packages/rest/src/data-providers/simple-rest/specs/create/index.spec.ts new file mode 100644 index 000000000000..09e8e9ed3fe5 --- /dev/null +++ b/packages/rest/src/data-providers/simple-rest/specs/create/index.spec.ts @@ -0,0 +1,17 @@ +import { simpleRestDataProvider } from ".."; +import "./index.mock"; + +describe("create", () => { + it("correct response", async () => { + const response = await simpleRestDataProvider.create({ + resource: "posts", + variables: { id: 1001, title: "foo", content: "bar" }, + }); + + const { data } = response; + + expect(data["id"]).toBe(1001); + expect(data["title"]).toBe("foo"); + expect(data["content"]).toBe("bar"); + }); +}); diff --git a/packages/rest/src/data-providers/simple-rest/specs/custom/index.mock.ts b/packages/rest/src/data-providers/simple-rest/specs/custom/index.mock.ts new file mode 100644 index 000000000000..adb5f31912b4 --- /dev/null +++ b/packages/rest/src/data-providers/simple-rest/specs/custom/index.mock.ts @@ -0,0 +1,2103 @@ +import nock from "nock"; + +nock("https://api.fake-rest.refine.dev:443", { + encodedQueryParams: true, +}) + .get("/users") + .query({ _order: "asc", _sort: "id" }) + .reply( + 200, + [ + { + id: 1, + firstName: "Camilla", + email: "russel_medhurst82@gmail.com", + lastName: "Reilly", + status: false, + birthday: "2020-10-18T15:07:56.404Z", + avatar: [ + { + name: "Camilla.jpg", + percent: 100, + size: 40088, + status: "done", + type: "image/jpeg", + uid: "rc-upload-1621595902154", + url: "http://www.gravatar.com/avatar/e8a34e6383aa65933b5ad4587b116d0a?s=300", + }, + ], + }, + { + id: 2, + firstName: "Jacynthe", + email: "jamel_ondricka95@gmail.com", + lastName: "Waelchi", + status: true, + birthday: "2020-10-17T13:45:13.314Z", + avatar: [ + { + name: "Jacynthe.jpg", + percent: 100, + size: 40088, + status: "done", + type: "image/jpeg", + uid: "rc-upload-1621595902163", + url: "http://www.gravatar.com/avatar/421cd73b438f4efe1c48dc5beaa7ee25?s=300", + }, + ], + }, + { + id: 3, + firstName: "Alexanne", + email: "emmy.fisher@hotmail.com", + lastName: "Farrell", + status: true, + birthday: "2021-05-11T14:56:17.106Z", + avatar: [ + { + name: "Alexanne.jpg", + percent: 100, + size: 40088, + status: "done", + type: "image/jpeg", + uid: "rc-upload-1621595902163", + url: "http://www.gravatar.com/avatar/0f5ee1f86d5198a28fc25c932326c91f?s=300", + }, + ], + }, + { + id: 4, + firstName: "Judd", + email: "berneice7@hotmail.com", + lastName: "Kessler", + status: true, + birthday: "2020-09-25T04:47:38.690Z", + avatar: [ + { + name: "Judd.jpg", + percent: 100, + size: 40088, + status: "done", + type: "image/jpeg", + uid: "rc-upload-1621595902163", + url: "http://www.gravatar.com/avatar/a54f466c8fc417424172963f365c8d17?s=300", + }, + ], + }, + { + id: 5, + firstName: "Kelly", + email: "donald_quitzon@yahoo.com", + lastName: "Feil", + status: false, + birthday: "2020-11-03T05:19:05.979Z", + avatar: [ + { + name: "Kelly.jpg", + percent: 100, + size: 40088, + status: "done", + type: "image/jpeg", + uid: "rc-upload-1621595902163", + url: "http://www.gravatar.com/avatar/6513771a92fb224d8527c6b5d5943619?s=300", + }, + ], + }, + { + id: 6, + firstName: "Remington", + email: "amely_sanford@yahoo.com", + lastName: "Mohr", + status: false, + birthday: "2020-10-19T14:01:25.511Z", + avatar: [ + { + name: "Remington.jpg", + percent: 100, + size: 40088, + status: "done", + type: "image/jpeg", + uid: "rc-upload-1621595902163", + url: "http://www.gravatar.com/avatar/8b4e222e397ed7ce70d22194384e221e?s=300", + }, + ], + }, + { + id: 7, + firstName: "Delaney", + email: "noelia77@gmail.com", + lastName: "Funk", + status: true, + birthday: "2021-01-06T15:31:30.377Z", + avatar: [ + { + name: "Delaney.jpg", + percent: 100, + size: 40088, + status: "done", + type: "image/jpeg", + uid: "rc-upload-1621595902164", + url: "http://www.gravatar.com/avatar/a54c750e8f6db6d35c493988f255d79a?s=300", + }, + ], + }, + { + id: 8, + firstName: "Demarco", + email: "zakary_parker@gmail.com", + lastName: "Davis", + status: true, + birthday: "2020-11-19T10:24:48.316Z", + avatar: [ + { + name: "Demarco.jpg", + percent: 100, + size: 40088, + status: "done", + type: "image/jpeg", + uid: "rc-upload-1621595902164", + url: "http://www.gravatar.com/avatar/27e5fe54e2cfe16fae38c5185887e6ff?s=300", + }, + ], + }, + { + id: 9, + firstName: "Greta", + email: "halie30@gmail.com", + lastName: "O'Reilly", + status: false, + birthday: "2021-02-21T13:09:39.669Z", + avatar: [ + { + name: "Greta.jpg", + percent: 100, + size: 40088, + status: "done", + type: "image/jpeg", + uid: "rc-upload-1621595902164", + url: "http://www.gravatar.com/avatar/8c76f628ab83a9d188c1c0c3aa238e8b?s=300", + }, + ], + }, + { + id: 10, + firstName: "Felicia", + email: "cloyd_mckenzie@yahoo.com", + lastName: "Veum", + status: false, + birthday: "2020-09-19T04:51:10.923Z", + avatar: [ + { + name: "Felicia.jpg", + percent: 100, + size: 40088, + status: "done", + type: "image/jpeg", + uid: "rc-upload-1621595902164", + url: "http://www.gravatar.com/avatar/40b00cefdfc699a9cce8dd27b24a2501?s=300", + }, + ], + }, + { + id: 11, + firstName: "Fidel", + email: "dereck.predovic25@gmail.com", + lastName: "Hahn", + status: true, + birthday: "2020-11-02T07:16:44.305Z", + avatar: [ + { + name: "Fidel.jpg", + percent: 100, + size: 40088, + status: "done", + type: "image/jpeg", + uid: "rc-upload-1621595902164", + url: "http://www.gravatar.com/avatar/707f3742ad088234b67dff8b9c71cec4?s=300", + }, + ], + }, + { + id: 12, + firstName: "Roderick", + email: "javonte.blick23@gmail.com", + lastName: "Cremin", + status: true, + birthday: "2021-01-01T05:01:09.191Z", + avatar: [ + { + name: "Roderick.jpg", + percent: 100, + size: 40088, + status: "done", + type: "image/jpeg", + uid: "rc-upload-1621595902164", + url: "http://www.gravatar.com/avatar/151501da9ce62922ac54390dc67c7fb6?s=300", + }, + ], + }, + { + id: 13, + firstName: "Jayme", + email: "august_armstrong@yahoo.com", + lastName: "Predovic", + status: false, + birthday: "2021-03-30T18:43:00.405Z", + avatar: [ + { + name: "Jayme.jpg", + percent: 100, + size: 40088, + status: "done", + type: "image/jpeg", + uid: "rc-upload-1621595902164", + url: "http://www.gravatar.com/avatar/b6a05bc5d1b1c26023910b7a6c7a6f3f?s=300", + }, + ], + }, + { + id: 14, + firstName: "Narciso", + email: "mertie_toy@gmail.com", + lastName: "Flatley", + status: true, + birthday: "2021-03-22T21:23:34.168Z", + avatar: [ + { + name: "Narciso.jpg", + percent: 100, + size: 40088, + status: "done", + type: "image/jpeg", + uid: "rc-upload-1621595902164", + url: "http://www.gravatar.com/avatar/234e94d874cf6ac8141218bd1b80e5a2?s=300", + }, + ], + }, + { + id: 15, + firstName: "Christy", + email: "zechariah.vonrueden@gmail.com", + lastName: "Robel", + status: true, + birthday: "2021-03-05T01:11:43.467Z", + avatar: [ + { + name: "Christy.jpg", + percent: 100, + size: 40088, + status: "done", + type: "image/jpeg", + uid: "rc-upload-1621595902164", + url: "http://www.gravatar.com/avatar/6fd104a5b6c336c8a94d014e962ca74f?s=300", + }, + ], + }, + { + id: 16, + firstName: "Marlee", + email: "tanya.casper@gmail.com", + lastName: "McLaughlin", + status: true, + birthday: "2021-03-01T19:57:35.502Z", + avatar: [ + { + name: "Marlee.jpg", + percent: 100, + size: 40088, + status: "done", + type: "image/jpeg", + uid: "rc-upload-1621595902164", + url: "http://www.gravatar.com/avatar/4ae191dc84be401952e693834513b11a?s=300", + }, + ], + }, + { + id: 17, + firstName: "Gudrun", + email: "ressie35@hotmail.com", + lastName: "O'Hara", + status: false, + birthday: "2021-01-27T09:20:49.675Z", + avatar: [ + { + name: "Gudrun.jpg", + percent: 100, + size: 40088, + status: "done", + type: "image/jpeg", + uid: "rc-upload-1621595902164", + url: "http://www.gravatar.com/avatar/0a9dabe445777d0679cbe1bbfeadb128?s=300", + }, + ], + }, + { + id: 18, + firstName: "Gia", + email: "rogelio.schuppe47@hotmail.com", + lastName: "Wiza", + status: true, + birthday: "2020-12-11T23:03:57.591Z", + avatar: [ + { + name: "Gia.jpg", + percent: 100, + size: 40088, + status: "done", + type: "image/jpeg", + uid: "rc-upload-1621595902164", + url: "http://www.gravatar.com/avatar/8cde85fdb968b67a102ff11344997731?s=300", + }, + ], + }, + { + id: 19, + firstName: "Brielle", + email: "audreanne_king@gmail.com", + lastName: "Goodwin", + status: true, + birthday: "2021-05-20T22:01:31.169Z", + avatar: [ + { + name: "Brielle.jpg", + percent: 100, + size: 40088, + status: "done", + type: "image/jpeg", + uid: "rc-upload-1621595902164", + url: "http://www.gravatar.com/avatar/192c4704980eba2508a39436cd1b2c62?s=300", + }, + ], + }, + { + id: 20, + firstName: "Jermaine", + email: "yazmin48@gmail.com", + lastName: "Macejkovic", + status: true, + birthday: "2021-01-11T12:33:09.024Z", + avatar: [ + { + name: "Jermaine.jpg", + percent: 100, + size: 40088, + status: "done", + type: "image/jpeg", + uid: "rc-upload-1621595902164", + url: "http://www.gravatar.com/avatar/35579d77a7cc7d0bbd65b7603ac7d410?s=300", + }, + ], + }, + { + id: 21, + firstName: "Ruthe", + email: "joesph.tillman@hotmail.com", + lastName: "Jast", + status: true, + birthday: "2020-10-11T07:19:41.277Z", + avatar: [ + { + name: "Ruthe.jpg", + percent: 100, + size: 40088, + status: "done", + type: "image/jpeg", + uid: "rc-upload-1621595902164", + url: "http://www.gravatar.com/avatar/8116f550d8d66fe7b2cd30112df89263?s=300", + }, + ], + }, + { + id: 22, + firstName: "Hailee", + email: "brenden.raynor89@hotmail.com", + lastName: "Bergstrom", + status: false, + birthday: "2020-08-04T11:59:47.700Z", + avatar: [ + { + name: "Hailee.jpg", + percent: 100, + size: 40088, + status: "done", + type: "image/jpeg", + uid: "rc-upload-1621595902164", + url: "http://www.gravatar.com/avatar/183fe523c7619079c8f2054ce90114dd?s=300", + }, + ], + }, + { + id: 23, + firstName: "Vincent", + email: "edmund_hackett@hotmail.com", + lastName: "Nitzsche", + status: true, + birthday: "2021-02-01T15:15:15.253Z", + avatar: [ + { + name: "Vincent.jpg", + percent: 100, + size: 40088, + status: "done", + type: "image/jpeg", + uid: "rc-upload-1621595902164", + url: "http://www.gravatar.com/avatar/b535bc7eff03f2ba26fd6ae2bee84d45?s=300", + }, + ], + }, + { + id: 24, + firstName: "Layla", + email: "adaline4@gmail.com", + lastName: "Rippin", + status: false, + birthday: "2021-04-12T12:43:46.184Z", + avatar: [ + { + name: "Layla.jpg", + percent: 100, + size: 40088, + status: "done", + type: "image/jpeg", + uid: "rc-upload-1621595902164", + url: "http://www.gravatar.com/avatar/88aa8e1438d1cd3146d33dc11154276a?s=300", + }, + ], + }, + { + id: 25, + firstName: "Joel", + email: "marcellus.connelly@gmail.com", + lastName: "Kautzer", + status: true, + birthday: "2021-03-25T07:21:59.009Z", + avatar: [ + { + name: "Joel.jpg", + percent: 100, + size: 40088, + status: "done", + type: "image/jpeg", + uid: "rc-upload-1621595902164", + url: "http://www.gravatar.com/avatar/1390de1dca31b92e27de74157c4940db?s=300", + }, + ], + }, + { + id: 26, + firstName: "Jolie", + email: "haskell_emard70@hotmail.com", + lastName: "Grant", + status: false, + birthday: "2020-11-27T05:13:22.904Z", + avatar: [ + { + name: "Jolie.jpg", + percent: 100, + size: 40088, + status: "done", + type: "image/jpeg", + uid: "rc-upload-1621595902164", + url: "http://www.gravatar.com/avatar/aef9a3303f99b235364f9b96271d8507?s=300", + }, + ], + }, + { + id: 27, + firstName: "Claudia", + email: "glennie.grant7@gmail.com", + lastName: "Ruecker", + status: false, + birthday: "2020-09-09T00:39:53.169Z", + avatar: [ + { + name: "Claudia.jpg", + percent: 100, + size: 40088, + status: "done", + type: "image/jpeg", + uid: "rc-upload-1621595902164", + url: "http://www.gravatar.com/avatar/0ea5ec952c455af5bfff5a53f4c07f28?s=300", + }, + ], + }, + { + id: 28, + firstName: "Rasheed", + email: "norberto_kunde@hotmail.com", + lastName: "Armstrong", + status: true, + birthday: "2021-05-02T00:47:18.955Z", + avatar: [ + { + name: "Rasheed.jpg", + percent: 100, + size: 40088, + status: "done", + type: "image/jpeg", + uid: "rc-upload-1621595902165", + url: "http://www.gravatar.com/avatar/c8a6d08bf72b32b524e2522353acabd2?s=300", + }, + ], + }, + { + id: 29, + firstName: "Tianna", + email: "darron66@gmail.com", + lastName: "Torphy", + status: false, + birthday: "2020-08-03T12:08:52.026Z", + avatar: [ + { + name: "Tianna.jpg", + percent: 100, + size: 40088, + status: "done", + type: "image/jpeg", + uid: "rc-upload-1621595902165", + url: "http://www.gravatar.com/avatar/9d240c9bb208d6e5571753c62efe37fa?s=300", + }, + ], + }, + { + id: 30, + firstName: "Alvena", + email: "blake_hauck74@hotmail.com", + lastName: "Heathcote", + status: true, + birthday: "2021-03-11T12:01:07.013Z", + avatar: [ + { + name: "Alvena.jpg", + percent: 100, + size: 40088, + status: "done", + type: "image/jpeg", + uid: "rc-upload-1621595902165", + url: "http://www.gravatar.com/avatar/2a58501ac239470777c68859063b521d?s=300", + }, + ], + }, + { + id: 31, + firstName: "Neal", + email: "art.homenick51@yahoo.com", + lastName: "Donnelly", + status: false, + birthday: "2021-02-21T07:28:07.312Z", + avatar: [ + { + name: "Neal.jpg", + percent: 100, + size: 40088, + status: "done", + type: "image/jpeg", + uid: "rc-upload-1621595902165", + url: "http://www.gravatar.com/avatar/62271034e565b3b20e748a1f8a72d145?s=300", + }, + ], + }, + { + id: 32, + firstName: "Kristy", + email: "hermina_braun74@gmail.com", + lastName: "Conn", + status: false, + birthday: "2020-07-12T16:55:23.038Z", + avatar: [ + { + name: "Kristy.jpg", + percent: 100, + size: 40088, + status: "done", + type: "image/jpeg", + uid: "rc-upload-1621595902165", + url: "http://www.gravatar.com/avatar/919790033f0e32f6b5f3bb7d94f88e44?s=300", + }, + ], + }, + { + id: 33, + firstName: "Vivien", + email: "fletcher_ondricka@yahoo.com", + lastName: "Maggio", + status: true, + birthday: "2021-03-16T03:12:41.357Z", + avatar: [ + { + name: "Vivien.jpg", + percent: 100, + size: 40088, + status: "done", + type: "image/jpeg", + uid: "rc-upload-1621595902165", + url: "http://www.gravatar.com/avatar/114a641308b1ee3ae17186cbb49cd5d9?s=300", + }, + ], + }, + { + id: 34, + firstName: "Christopher", + email: "ova50@yahoo.com", + lastName: "Haley", + status: true, + birthday: "2020-07-31T04:16:34.180Z", + avatar: [ + { + name: "Christopher.jpg", + percent: 100, + size: 40088, + status: "done", + type: "image/jpeg", + uid: "rc-upload-1621595902165", + url: "http://www.gravatar.com/avatar/5802e68a71ba47365114e0541099e2a1?s=300", + }, + ], + }, + { + id: 35, + firstName: "Rosanna", + email: "zander.rice@gmail.com", + lastName: "Harvey", + status: false, + birthday: "2020-12-24T05:50:15.430Z", + avatar: [ + { + name: "Rosanna.jpg", + percent: 100, + size: 40088, + status: "done", + type: "image/jpeg", + uid: "rc-upload-1621595902165", + url: "http://www.gravatar.com/avatar/8ec0d97bb36688e019d09a31db42d331?s=300", + }, + ], + }, + { + id: 36, + firstName: "Raegan", + email: "hallie84@hotmail.com", + lastName: "Sipes", + status: true, + birthday: "2020-07-14T00:35:18.018Z", + avatar: [ + { + name: "Raegan.jpg", + percent: 100, + size: 40088, + status: "done", + type: "image/jpeg", + uid: "rc-upload-1621595902165", + url: "http://www.gravatar.com/avatar/c865551aa5a6d03b9a4ff3ae53302fdc?s=300", + }, + ], + }, + { + id: 37, + firstName: "Braxton", + email: "rosie51@gmail.com", + lastName: "Schultz", + status: false, + birthday: "2020-07-03T04:20:45.542Z", + avatar: [ + { + name: "Braxton.jpg", + percent: 100, + size: 40088, + status: "done", + type: "image/jpeg", + uid: "rc-upload-1621595902165", + url: "http://www.gravatar.com/avatar/a040a3b18fbd2e2c4dbe1013e03ed738?s=300", + }, + ], + }, + { + id: 38, + firstName: "Selena", + email: "oswaldo.lehner39@yahoo.com", + lastName: "Harber", + status: true, + birthday: "2020-12-16T13:42:28.214Z", + avatar: [ + { + name: "Selena.jpg", + percent: 100, + size: 40088, + status: "done", + type: "image/jpeg", + uid: "rc-upload-1621595902165", + url: "http://www.gravatar.com/avatar/6755fc9535dc2eec01fc979d7b2d5f0a?s=300", + }, + ], + }, + { + id: 39, + firstName: "Carleton", + email: "kiarra.yundt@yahoo.com", + lastName: "Funk", + status: false, + birthday: "2021-01-29T04:21:04.635Z", + avatar: [ + { + name: "Carleton.jpg", + percent: 100, + size: 40088, + status: "done", + type: "image/jpeg", + uid: "rc-upload-1621595902165", + url: "http://www.gravatar.com/avatar/25ec66f243247df404cb71aa559fc56c?s=300", + }, + ], + }, + { + id: 40, + firstName: "Rosalyn", + email: "craig_ullrich@gmail.com", + lastName: "Jerde", + status: true, + birthday: "2020-08-02T18:43:05.102Z", + avatar: [ + { + name: "Rosalyn.jpg", + percent: 100, + size: 40088, + status: "done", + type: "image/jpeg", + uid: "rc-upload-1621595902165", + url: "http://www.gravatar.com/avatar/da9528699710c2687cd948037cf00498?s=300", + }, + ], + }, + { + id: 41, + firstName: "Araceli", + email: "eden_koss@gmail.com", + lastName: "Ankunding", + status: false, + birthday: "2021-03-23T13:12:26.611Z", + avatar: [ + { + name: "Araceli.jpg", + percent: 100, + size: 40088, + status: "done", + type: "image/jpeg", + uid: "rc-upload-1621595902165", + url: "http://www.gravatar.com/avatar/7b3024ae9cd6a7250117a95359bf43bc?s=300", + }, + ], + }, + { + id: 42, + firstName: "Michele", + email: "emil_west60@hotmail.com", + lastName: "Towne", + status: false, + birthday: "2020-09-17T06:26:51.746Z", + avatar: [ + { + name: "Michele.jpg", + percent: 100, + size: 40088, + status: "done", + type: "image/jpeg", + uid: "rc-upload-1621595902165", + url: "http://www.gravatar.com/avatar/c5b02ed347e4b3ab6d3dc9932b1fbeef?s=300", + }, + ], + }, + { + id: 43, + firstName: "Lue", + email: "beverly.hansen54@hotmail.com", + lastName: "Flatley", + status: true, + birthday: "2020-08-30T15:25:14.041Z", + avatar: [ + { + name: "Lue.jpg", + percent: 100, + size: 40088, + status: "done", + type: "image/jpeg", + uid: "rc-upload-1621595902165", + url: "http://www.gravatar.com/avatar/5e1173a26f4a76740220719def6f9ab0?s=300", + }, + ], + }, + { + id: 44, + firstName: "Eliane", + email: "richie85@hotmail.com", + lastName: "Prosacco", + status: false, + birthday: "2021-04-15T02:29:38.508Z", + avatar: [ + { + name: "Eliane.jpg", + percent: 100, + size: 40088, + status: "done", + type: "image/jpeg", + uid: "rc-upload-1621595902165", + url: "http://www.gravatar.com/avatar/7c65b19b968806dfbba7e44d0bad0494?s=300", + }, + ], + }, + { + id: 45, + firstName: "Dale", + email: "katrine_stamm@gmail.com", + lastName: "Kuphal", + status: true, + birthday: "2021-01-25T09:51:49.691Z", + avatar: [ + { + name: "Dale.jpg", + percent: 100, + size: 40088, + status: "done", + type: "image/jpeg", + uid: "rc-upload-1621595902165", + url: "http://www.gravatar.com/avatar/d5b9d32f63315715a2ee7eec3537eefd?s=300", + }, + ], + }, + { + id: 46, + firstName: "Gerard", + email: "virginie94@hotmail.com", + lastName: "Wunsch", + status: true, + birthday: "2021-01-13T19:52:35.023Z", + avatar: [ + { + name: "Gerard.jpg", + percent: 100, + size: 40088, + status: "done", + type: "image/jpeg", + uid: "rc-upload-1621595902165", + url: "http://www.gravatar.com/avatar/6c0a671f425be190610aaa5d9ee6b751?s=300", + }, + ], + }, + { + id: 47, + firstName: "Greta", + email: "gabriella_jacobson69@hotmail.com", + lastName: "Shields", + status: true, + birthday: "2021-01-31T12:37:35.124Z", + avatar: [ + { + name: "Greta.jpg", + percent: 100, + size: 40088, + status: "done", + type: "image/jpeg", + uid: "rc-upload-1621595902165", + url: "http://www.gravatar.com/avatar/96e99dd6d42919a5bbaa523e039ed505?s=300", + }, + ], + }, + { + id: 48, + firstName: "Elinore", + email: "kyleigh_powlowski47@yahoo.com", + lastName: "Padberg", + status: true, + birthday: "2021-03-16T12:56:08.925Z", + avatar: [ + { + name: "Elinore.jpg", + percent: 100, + size: 40088, + status: "done", + type: "image/jpeg", + uid: "rc-upload-1621595902165", + url: "http://www.gravatar.com/avatar/8a3aff921b1286caef37c3a6ab8922ed?s=300", + }, + ], + }, + { + id: 49, + firstName: "Vita", + email: "bernice_reichel@gmail.com", + lastName: "Gottlieb", + status: true, + birthday: "2020-07-14T04:25:13.203Z", + avatar: [ + { + name: "Vita.jpg", + percent: 100, + size: 40088, + status: "done", + type: "image/jpeg", + uid: "rc-upload-1621595902165", + url: "http://www.gravatar.com/avatar/65c2a79bd56b843604b7d1edace158f7?s=300", + }, + ], + }, + { + id: 50, + firstName: "Cortez", + email: "rahul.damore39@yahoo.com", + lastName: "Crooks", + status: false, + birthday: "2021-03-06T13:48:02.030Z", + avatar: [ + { + name: "Cortez.jpg", + percent: 100, + size: 40088, + status: "done", + type: "image/jpeg", + uid: "rc-upload-1621595902165", + url: "http://www.gravatar.com/avatar/ff556a7e4fcbc159b55bde887e4669a1?s=300", + }, + ], + }, + ], + [ + "Server", + "nginx/1.17.10", + "Date", + "Fri, 21 May 2021 12:27:49 GMT", + "Content-Type", + "application/json; charset=utf-8", + "Content-Length", + "24079", + "Connection", + "close", + "Vary", + "Accept-Encoding", + "X-Powered-By", + "Express", + "Vary", + "Origin, Accept-Encoding", + "Access-Control-Allow-Credentials", + "true", + "Cache-Control", + "no-cache", + "Pragma", + "no-cache", + "Expires", + "-1", + "Access-Control-Allow-Origin", + "*", + "X-Content-Type-Options", + "nosniff", + "ETag", + 'W/"5e0f-Mda+l0GWKw7XFWneFgP2RUrwx7c"', + ], + ); + +nock("https://api.fake-rest.refine.dev:443", { + encodedQueryParams: true, +}) + .post("/users", { + firstName: "test", + lastName: "test", + email: "test@mail.com", + status: true, + }) + .reply( + 201, + { + firstName: "test", + lastName: "test", + email: "test@mail.com", + status: true, + id: 51, + }, + [ + "Server", + "nginx/1.17.10", + "Date", + "Fri, 21 May 2021 12:30:07 GMT", + "Content-Type", + "application/json; charset=utf-8", + "Content-Length", + "105", + "Connection", + "close", + "X-Powered-By", + "Express", + "Vary", + "Origin, X-HTTP-Method-Override, Accept-Encoding", + "Access-Control-Allow-Credentials", + "true", + "Cache-Control", + "no-cache", + "Pragma", + "no-cache", + "Expires", + "-1", + "Access-Control-Allow-Origin", + "*", + "Access-Control-Expose-Headers", + "Location", + "Location", + "http://api.fake-rest.refine.dev/users/51", + "X-Content-Type-Options", + "nosniff", + "ETag", + 'W/"69-ZOo+LCFMeEdi+N0u0NisuFgdFO4"', + ], + ); + +nock("https://api.fake-rest.refine.dev:443", { + encodedQueryParams: true, +}) + .get("/users") + .query({}) + .reply( + 200, + [ + { + id: 1, + firstName: "Kyla", + email: "tiana_gleason63@hotmail.com", + lastName: "Romaguera", + status: false, + birthday: "2020-10-12T09:00:23.833Z", + avatar: [ + { + name: "Kyla.jpg", + percent: 100, + size: 40088, + status: "done", + type: "image/jpeg", + uid: "rc-upload-1621879096181", + url: "http://www.gravatar.com/avatar/86d13aa71a6653086e50c8b72c2b5818?s=300", + }, + ], + }, + { + id: 2, + firstName: "Julien", + email: "jonas_pfannerstill89@gmail.com", + lastName: "Thompson", + status: true, + birthday: "2020-09-08T21:08:10.790Z", + avatar: [ + { + name: "Julien.jpg", + percent: 100, + size: 40088, + status: "done", + type: "image/jpeg", + uid: "rc-upload-1621879096182", + url: "http://www.gravatar.com/avatar/1e922375d17c7d8627ec1b451df8aac4?s=300", + }, + ], + }, + { + id: 3, + firstName: "Bernhard", + email: "alexandre.nicolas@yahoo.com", + lastName: "Lueilwitz", + status: false, + birthday: "2020-09-18T10:37:23.892Z", + avatar: [ + { + name: "Bernhard.jpg", + percent: 100, + size: 40088, + status: "done", + type: "image/jpeg", + uid: "rc-upload-1621879096182", + url: "http://www.gravatar.com/avatar/fec06a4715862bd21c9ba067bdd7ed65?s=300", + }, + ], + }, + { + id: 4, + firstName: "Genesis", + email: "margret_davis75@hotmail.com", + lastName: "Bode", + status: true, + birthday: "2020-12-04T20:11:53.547Z", + avatar: [ + { + name: "Genesis.jpg", + percent: 100, + size: 40088, + status: "done", + type: "image/jpeg", + uid: "rc-upload-1621879096182", + url: "http://www.gravatar.com/avatar/9f950d6fc4b4968597b9d2a2155085af?s=300", + }, + ], + }, + { + id: 5, + firstName: "Greyson", + email: "gabriella_stiedemann69@gmail.com", + lastName: "Legros", + status: false, + birthday: "2020-06-14T11:58:52.780Z", + avatar: [ + { + name: "Greyson.jpg", + percent: 100, + size: 40088, + status: "done", + type: "image/jpeg", + uid: "rc-upload-1621879096183", + url: "http://www.gravatar.com/avatar/9310f6d7b6bb5d63fdee44c9477cf342?s=300", + }, + ], + }, + { + id: 6, + firstName: "Jamaal", + email: "pauline.pouros2@gmail.com", + lastName: "Olson", + status: false, + birthday: "2020-07-19T09:31:16.478Z", + avatar: [ + { + name: "Jamaal.jpg", + percent: 100, + size: 40088, + status: "done", + type: "image/jpeg", + uid: "rc-upload-1621879096183", + url: "http://www.gravatar.com/avatar/40c325efbfcc2863a8d48cb3282e2ba7?s=300", + }, + ], + }, + { + id: 7, + firstName: "Alexis", + email: "muhammad.veum22@gmail.com", + lastName: "Homenick", + status: true, + birthday: "2021-01-03T03:09:59.670Z", + avatar: [ + { + name: "Alexis.jpg", + percent: 100, + size: 40088, + status: "done", + type: "image/jpeg", + uid: "rc-upload-1621879096183", + url: "http://www.gravatar.com/avatar/40722a4a441d91214e3765b3a732f41d?s=300", + }, + ], + }, + { + id: 8, + firstName: "Rosalia", + email: "arlie19@hotmail.com", + lastName: "Stamm", + status: false, + birthday: "2020-06-07T23:55:47.045Z", + avatar: [ + { + name: "Rosalia.jpg", + percent: 100, + size: 40088, + status: "done", + type: "image/jpeg", + uid: "rc-upload-1621879096183", + url: "http://www.gravatar.com/avatar/2cd9bb750b9e75908a9f5a950e75cd0b?s=300", + }, + ], + }, + { + id: 9, + firstName: "Bella", + email: "favian57@gmail.com", + lastName: "Jenkins", + status: true, + birthday: "2020-10-24T15:07:44.349Z", + avatar: [ + { + name: "Bella.jpg", + percent: 100, + size: 40088, + status: "done", + type: "image/jpeg", + uid: "rc-upload-1621879096183", + url: "http://www.gravatar.com/avatar/acc331824c8f89e49b09cf68a3547bfb?s=300", + }, + ], + }, + { + id: 10, + firstName: "Douglas", + email: "wilburn48@yahoo.com", + lastName: "Raynor", + status: true, + birthday: "2021-03-23T02:36:24.532Z", + avatar: [ + { + name: "Douglas.jpg", + percent: 100, + size: 40088, + status: "done", + type: "image/jpeg", + uid: "rc-upload-1621879096183", + url: "http://www.gravatar.com/avatar/13ced187c09d3229f394c54d8f2e4e07?s=300", + }, + ], + }, + { + id: 11, + firstName: "Josephine", + email: "brice18@yahoo.com", + lastName: "Hahn", + status: true, + birthday: "2020-08-29T19:22:08.978Z", + avatar: [ + { + name: "Josephine.jpg", + percent: 100, + size: 40088, + status: "done", + type: "image/jpeg", + uid: "rc-upload-1621879096183", + url: "http://www.gravatar.com/avatar/824fde0bdca7a3116ccb06cb45ed4601?s=300", + }, + ], + }, + { + id: 12, + firstName: "Jaquelin", + email: "alexanne.connelly60@hotmail.com", + lastName: "Collins", + status: false, + birthday: "2021-02-18T17:16:17.297Z", + avatar: [ + { + name: "Jaquelin.jpg", + percent: 100, + size: 40088, + status: "done", + type: "image/jpeg", + uid: "rc-upload-1621879096183", + url: "http://www.gravatar.com/avatar/07e281cae06a7a33774f96aa4bad2a4a?s=300", + }, + ], + }, + { + id: 13, + firstName: "Margarett", + email: "jaron_collins26@gmail.com", + lastName: "Krajcik", + status: false, + birthday: "2020-12-22T13:18:50.983Z", + avatar: [ + { + name: "Margarett.jpg", + percent: 100, + size: 40088, + status: "done", + type: "image/jpeg", + uid: "rc-upload-1621879096183", + url: "http://www.gravatar.com/avatar/27949d4bb1075e8ecc5aaf1d1a104324?s=300", + }, + ], + }, + { + id: 14, + firstName: "Cheyanne", + email: "kiana_cummings49@hotmail.com", + lastName: "Mante", + status: false, + birthday: "2021-02-04T05:07:53.275Z", + avatar: [ + { + name: "Cheyanne.jpg", + percent: 100, + size: 40088, + status: "done", + type: "image/jpeg", + uid: "rc-upload-1621879096183", + url: "http://www.gravatar.com/avatar/b4cb6f682cdbbea46ae429ae40b7a9b4?s=300", + }, + ], + }, + { + id: 15, + firstName: "Delores", + email: "darion.doyle@hotmail.com", + lastName: "Dickinson", + status: false, + birthday: "2020-08-13T01:22:43.455Z", + avatar: [ + { + name: "Delores.jpg", + percent: 100, + size: 40088, + status: "done", + type: "image/jpeg", + uid: "rc-upload-1621879096183", + url: "http://www.gravatar.com/avatar/556014bdad0097985613ed1b8bd9c87b?s=300", + }, + ], + }, + { + id: 16, + firstName: "Frida", + email: "idell.shanahan@yahoo.com", + lastName: "Feeney", + status: true, + birthday: "2021-04-20T01:10:47.497Z", + avatar: [ + { + name: "Frida.jpg", + percent: 100, + size: 40088, + status: "done", + type: "image/jpeg", + uid: "rc-upload-1621879096183", + url: "http://www.gravatar.com/avatar/be1939d55c424134f9dee1c116bf84dd?s=300", + }, + ], + }, + { + id: 17, + firstName: "Breanna", + email: "hipolito.block12@gmail.com", + lastName: "Heathcote", + status: false, + birthday: "2020-09-29T06:33:09.567Z", + avatar: [ + { + name: "Breanna.jpg", + percent: 100, + size: 40088, + status: "done", + type: "image/jpeg", + uid: "rc-upload-1621879096183", + url: "http://www.gravatar.com/avatar/fb17bfd91b4e8b609805c7176797f3fe?s=300", + }, + ], + }, + { + id: 18, + firstName: "Malachi", + email: "genoveva.klein@yahoo.com", + lastName: "Schiller", + status: false, + birthday: "2020-09-01T16:12:14.666Z", + avatar: [ + { + name: "Malachi.jpg", + percent: 100, + size: 40088, + status: "done", + type: "image/jpeg", + uid: "rc-upload-1621879096183", + url: "http://www.gravatar.com/avatar/f1586b4e0e98408cb4b382495a04a4ee?s=300", + }, + ], + }, + { + id: 19, + firstName: "Jefferey", + email: "llewellyn.farrell73@yahoo.com", + lastName: "Crona", + status: false, + birthday: "2020-06-14T22:28:17.327Z", + avatar: [ + { + name: "Jefferey.jpg", + percent: 100, + size: 40088, + status: "done", + type: "image/jpeg", + uid: "rc-upload-1621879096183", + url: "http://www.gravatar.com/avatar/443a97a667cb3a0497bf2e9beb8e9874?s=300", + }, + ], + }, + { + id: 20, + firstName: "Shanon", + email: "kayla65@yahoo.com", + lastName: "Pouros", + status: false, + birthday: "2020-06-03T18:13:23.078Z", + avatar: [ + { + name: "Shanon.jpg", + percent: 100, + size: 40088, + status: "done", + type: "image/jpeg", + uid: "rc-upload-1621879096183", + url: "http://www.gravatar.com/avatar/6dfaf3a6ce20c47b0c75f76901ade310?s=300", + }, + ], + }, + { + id: 21, + firstName: "Braeden", + email: "cullen.gleason@gmail.com", + lastName: "Hermann", + status: false, + birthday: "2021-01-05T07:09:28.446Z", + avatar: [ + { + name: "Braeden.jpg", + percent: 100, + size: 40088, + status: "done", + type: "image/jpeg", + uid: "rc-upload-1621879096183", + url: "http://www.gravatar.com/avatar/a3c3c9d9166714d2a5d0e9a3d5411d4f?s=300", + }, + ], + }, + { + id: 22, + firstName: "Henriette", + email: "simone_bruen@gmail.com", + lastName: "Padberg", + status: false, + birthday: "2021-02-18T11:34:24.982Z", + avatar: [ + { + name: "Henriette.jpg", + percent: 100, + size: 40088, + status: "done", + type: "image/jpeg", + uid: "rc-upload-1621879096183", + url: "http://www.gravatar.com/avatar/2b39f72f7fd999c088ce4f4d4f0eeef3?s=300", + }, + ], + }, + { + id: 23, + firstName: "Jaleel", + email: "gerhard3@hotmail.com", + lastName: "Hartmann", + status: false, + birthday: "2020-10-03T18:46:04.864Z", + avatar: [ + { + name: "Jaleel.jpg", + percent: 100, + size: 40088, + status: "done", + type: "image/jpeg", + uid: "rc-upload-1621879096183", + url: "http://www.gravatar.com/avatar/429673c5d892241731a98b7ef9be66f1?s=300", + }, + ], + }, + { + id: 24, + firstName: "Abbie", + email: "felicity.lang38@hotmail.com", + lastName: "Harris", + status: false, + birthday: "2020-07-14T06:27:59.269Z", + avatar: [ + { + name: "Abbie.jpg", + percent: 100, + size: 40088, + status: "done", + type: "image/jpeg", + uid: "rc-upload-1621879096184", + url: "http://www.gravatar.com/avatar/d697222fcc70e4699a5aef4092fd4960?s=300", + }, + ], + }, + { + id: 25, + firstName: "Adelle", + email: "maxwell26@gmail.com", + lastName: "Pouros", + status: true, + birthday: "2021-03-24T01:48:48.384Z", + avatar: [ + { + name: "Adelle.jpg", + percent: 100, + size: 40088, + status: "done", + type: "image/jpeg", + uid: "rc-upload-1621879096184", + url: "http://www.gravatar.com/avatar/5734baae4fd9224ecf46599a7f728657?s=300", + }, + ], + }, + { + id: 26, + firstName: "Madalyn", + email: "harmony.osinski6@hotmail.com", + lastName: "Green", + status: true, + birthday: "2020-06-14T03:04:17.723Z", + avatar: [ + { + name: "Madalyn.jpg", + percent: 100, + size: 40088, + status: "done", + type: "image/jpeg", + uid: "rc-upload-1621879096184", + url: "http://www.gravatar.com/avatar/c66cd3f75ddccce6c6b0de482afc905f?s=300", + }, + ], + }, + { + id: 27, + firstName: "Dannie", + email: "tod_rohan@gmail.com", + lastName: "Kilback", + status: false, + birthday: "2020-12-16T03:57:53.581Z", + avatar: [ + { + name: "Dannie.jpg", + percent: 100, + size: 40088, + status: "done", + type: "image/jpeg", + uid: "rc-upload-1621879096184", + url: "http://www.gravatar.com/avatar/c7bba5ced38b23732056592d6934a896?s=300", + }, + ], + }, + { + id: 28, + firstName: "Alford", + email: "jayme79@hotmail.com", + lastName: "Fadel", + status: false, + birthday: "2020-12-10T18:37:47.297Z", + avatar: [ + { + name: "Alford.jpg", + percent: 100, + size: 40088, + status: "done", + type: "image/jpeg", + uid: "rc-upload-1621879096184", + url: "http://www.gravatar.com/avatar/1fe3443ec365478153ab3f1da7d381c9?s=300", + }, + ], + }, + { + id: 29, + firstName: "Zander", + email: "myrtis77@yahoo.com", + lastName: "Kuhn", + status: false, + birthday: "2021-04-19T06:32:12.236Z", + avatar: [ + { + name: "Zander.jpg", + percent: 100, + size: 40088, + status: "done", + type: "image/jpeg", + uid: "rc-upload-1621879096184", + url: "http://www.gravatar.com/avatar/40ba136ac7db00a32ccc891bd6acd434?s=300", + }, + ], + }, + { + id: 30, + firstName: "Ewell", + email: "fausto24@gmail.com", + lastName: "Mayert", + status: true, + birthday: "2020-12-29T13:09:12.689Z", + avatar: [ + { + name: "Ewell.jpg", + percent: 100, + size: 40088, + status: "done", + type: "image/jpeg", + uid: "rc-upload-1621879096184", + url: "http://www.gravatar.com/avatar/0162b59bccdcbb4c5af22d364c17a78b?s=300", + }, + ], + }, + { + id: 31, + firstName: "Orie", + email: "jacynthe_vonrueden@gmail.com", + lastName: "Dare", + status: false, + birthday: "2021-02-11T05:26:29.934Z", + avatar: [ + { + name: "Orie.jpg", + percent: 100, + size: 40088, + status: "done", + type: "image/jpeg", + uid: "rc-upload-1621879096184", + url: "http://www.gravatar.com/avatar/cc8f9646e1fe768891a6166abea6bcf5?s=300", + }, + ], + }, + { + id: 32, + firstName: "Orrin", + email: "leonard.miller@hotmail.com", + lastName: "Kertzmann", + status: true, + birthday: "2021-03-12T22:11:22.174Z", + avatar: [ + { + name: "Orrin.jpg", + percent: 100, + size: 40088, + status: "done", + type: "image/jpeg", + uid: "rc-upload-1621879096184", + url: "http://www.gravatar.com/avatar/bcebc7267b320488d0e00cc47e9acacd?s=300", + }, + ], + }, + { + id: 33, + firstName: "Abdul", + email: "lavonne.shanahan90@gmail.com", + lastName: "Lockman", + status: false, + birthday: "2020-11-17T13:58:18.061Z", + avatar: [ + { + name: "Abdul.jpg", + percent: 100, + size: 40088, + status: "done", + type: "image/jpeg", + uid: "rc-upload-1621879096184", + url: "http://www.gravatar.com/avatar/dd3de8bfc60efb277caeb9f6f73b1832?s=300", + }, + ], + }, + { + id: 34, + firstName: "Llewellyn", + email: "earl.beer51@gmail.com", + lastName: "Barton", + status: false, + birthday: "2020-08-31T07:05:19.048Z", + avatar: [ + { + name: "Llewellyn.jpg", + percent: 100, + size: 40088, + status: "done", + type: "image/jpeg", + uid: "rc-upload-1621879096184", + url: "http://www.gravatar.com/avatar/2db4c8759510c35800a1bc4f446d141d?s=300", + }, + ], + }, + { + id: 35, + firstName: "Ari", + email: "loren74@gmail.com", + lastName: "Gislason", + status: true, + birthday: "2020-07-22T16:10:10.515Z", + avatar: [ + { + name: "Ari.jpg", + percent: 100, + size: 40088, + status: "done", + type: "image/jpeg", + uid: "rc-upload-1621879096184", + url: "http://www.gravatar.com/avatar/a8353bd5c84d41385b6127b1103edade?s=300", + }, + ], + }, + { + id: 36, + firstName: "Fletcher", + email: "tamia40@gmail.com", + lastName: "Gulgowski", + status: true, + birthday: "2021-02-22T04:04:18.851Z", + avatar: [ + { + name: "Fletcher.jpg", + percent: 100, + size: 40088, + status: "done", + type: "image/jpeg", + uid: "rc-upload-1621879096184", + url: "http://www.gravatar.com/avatar/fe8944dc6c778431e59e95111960f998?s=300", + }, + ], + }, + { + id: 37, + firstName: "Clementine", + email: "riley.oconner7@hotmail.com", + lastName: "Romaguera", + status: true, + birthday: "2020-11-12T11:10:20.476Z", + avatar: [ + { + name: "Clementine.jpg", + percent: 100, + size: 40088, + status: "done", + type: "image/jpeg", + uid: "rc-upload-1621879096184", + url: "http://www.gravatar.com/avatar/00fa97f9d8daf4a44438c58413d4b489?s=300", + }, + ], + }, + { + id: 38, + firstName: "Mandy", + email: "nina_marquardt@gmail.com", + lastName: "Bayer", + status: false, + birthday: "2020-11-28T13:28:43.957Z", + avatar: [ + { + name: "Mandy.jpg", + percent: 100, + size: 40088, + status: "done", + type: "image/jpeg", + uid: "rc-upload-1621879096184", + url: "http://www.gravatar.com/avatar/fa140cd26c0953defa82fee122ce174c?s=300", + }, + ], + }, + { + id: 39, + firstName: "Nyah", + email: "audreanne_turcotte17@hotmail.com", + lastName: "Luettgen", + status: false, + birthday: "2021-04-22T11:21:27.467Z", + avatar: [ + { + name: "Nyah.jpg", + percent: 100, + size: 40088, + status: "done", + type: "image/jpeg", + uid: "rc-upload-1621879096184", + url: "http://www.gravatar.com/avatar/2c4edd92c9a9ba3c5c1ad4047eb97a0e?s=300", + }, + ], + }, + { + id: 40, + firstName: "Treva", + email: "braeden.hudson@gmail.com", + lastName: "Reichel", + status: false, + birthday: "2021-04-02T04:43:55.763Z", + avatar: [ + { + name: "Treva.jpg", + percent: 100, + size: 40088, + status: "done", + type: "image/jpeg", + uid: "rc-upload-1621879096184", + url: "http://www.gravatar.com/avatar/85892bdf84d24aed7a3eddc3ada02811?s=300", + }, + ], + }, + { + id: 41, + firstName: "Genevieve", + email: "dell31@gmail.com", + lastName: "Runte", + status: true, + birthday: "2020-06-12T05:06:37.672Z", + avatar: [ + { + name: "Genevieve.jpg", + percent: 100, + size: 40088, + status: "done", + type: "image/jpeg", + uid: "rc-upload-1621879096184", + url: "http://www.gravatar.com/avatar/1f833bb3c9781b5aefbc4b320d7b9196?s=300", + }, + ], + }, + { + id: 42, + firstName: "Andrew", + email: "desiree.torphy@hotmail.com", + lastName: "Smith", + status: false, + birthday: "2020-11-25T00:47:11.641Z", + avatar: [ + { + name: "Andrew.jpg", + percent: 100, + size: 40088, + status: "done", + type: "image/jpeg", + uid: "rc-upload-1621879096184", + url: "http://www.gravatar.com/avatar/e379d08fd05a3a40de8df3b27603dfdb?s=300", + }, + ], + }, + { + id: 43, + firstName: "Rick", + email: "lew65@hotmail.com", + lastName: "Bosco", + status: true, + birthday: "2021-05-17T11:46:51.343Z", + avatar: [ + { + name: "Rick.jpg", + percent: 100, + size: 40088, + status: "done", + type: "image/jpeg", + uid: "rc-upload-1621879096184", + url: "http://www.gravatar.com/avatar/f843c28611ade198d373ec756b5b7c95?s=300", + }, + ], + }, + { + id: 44, + firstName: "Faustino", + email: "albin.shanahan11@yahoo.com", + lastName: "Murphy", + status: true, + birthday: "2020-11-28T00:55:32.379Z", + avatar: [ + { + name: "Faustino.jpg", + percent: 100, + size: 40088, + status: "done", + type: "image/jpeg", + uid: "rc-upload-1621879096184", + url: "http://www.gravatar.com/avatar/2cfa047ffaf066c00445adffedc24f5d?s=300", + }, + ], + }, + { + id: 45, + firstName: "Warren", + email: "eliseo_stehr56@hotmail.com", + lastName: "Flatley", + status: true, + birthday: "2020-06-09T21:28:41.120Z", + avatar: [ + { + name: "Warren.jpg", + percent: 100, + size: 40088, + status: "done", + type: "image/jpeg", + uid: "rc-upload-1621879096184", + url: "http://www.gravatar.com/avatar/92416d6a5ef35ebaac420df658f8acc6?s=300", + }, + ], + }, + { + id: 46, + firstName: "Coy", + email: "rosalyn_hackett@yahoo.com", + lastName: "Kirlin", + status: true, + birthday: "2020-08-10T18:45:21.803Z", + avatar: [ + { + name: "Coy.jpg", + percent: 100, + size: 40088, + status: "done", + type: "image/jpeg", + uid: "rc-upload-1621879096184", + url: "http://www.gravatar.com/avatar/ca181d0649631c9daa13ba7b5a8196db?s=300", + }, + ], + }, + { + id: 47, + firstName: "Eleanora", + email: "susie.roob3@hotmail.com", + lastName: "Carter", + status: false, + birthday: "2020-08-14T11:02:33.390Z", + avatar: [ + { + name: "Eleanora.jpg", + percent: 100, + size: 40088, + status: "done", + type: "image/jpeg", + uid: "rc-upload-1621879096184", + url: "http://www.gravatar.com/avatar/9b0a9ef967a48c3c2231085c64c3419d?s=300", + }, + ], + }, + { + id: 48, + firstName: "Colten", + email: "ned_oconner12@hotmail.com", + lastName: "West", + status: false, + birthday: "2021-03-12T21:17:41.461Z", + avatar: [ + { + name: "Colten.jpg", + percent: 100, + size: 40088, + status: "done", + type: "image/jpeg", + uid: "rc-upload-1621879096184", + url: "http://www.gravatar.com/avatar/015c6616701542f56ce361e796a6aa58?s=300", + }, + ], + }, + { + id: 49, + firstName: "Elias", + email: "jace_ratke@gmail.com", + lastName: "Torphy", + status: false, + birthday: "2020-08-15T07:15:52.313Z", + avatar: [ + { + name: "Elias.jpg", + percent: 100, + size: 40088, + status: "done", + type: "image/jpeg", + uid: "rc-upload-1621879096184", + url: "http://www.gravatar.com/avatar/ffd320a3bd93fbe4b1b220c6d4934962?s=300", + }, + ], + }, + { + id: 50, + firstName: "Brandon", + email: "lester24@hotmail.com", + lastName: "Hoppe", + status: true, + birthday: "2021-05-10T03:34:23.484Z", + avatar: [ + { + name: "Brandon.jpg", + percent: 100, + size: 40088, + status: "done", + type: "image/jpeg", + uid: "rc-upload-1621879096184", + url: "http://www.gravatar.com/avatar/aa6ca36a2385db30cdd87d05b5f0aead?s=300", + }, + ], + }, + ], + [ + "Server", + "nginx/1.17.10", + "Date", + "Tue, 25 May 2021 07:12:40 GMT", + "Content-Type", + "application/json; charset=utf-8", + "Content-Length", + "24109", + "Connection", + "close", + "Vary", + "Accept-Encoding", + "X-Powered-By", + "Express", + "Vary", + "Origin, Accept-Encoding", + "Access-Control-Allow-Credentials", + "true", + "Cache-Control", + "no-cache", + "Pragma", + "no-cache", + "Expires", + "-1", + "Access-Control-Allow-Origin", + "*", + "X-Content-Type-Options", + "nosniff", + "ETag", + 'W/"5e2d-phKB0P7pdC91ACQZWKjQOOYZB90"', + ], + ); + +nock("https://api.fake-rest.refine.dev:443", { + encodedQueryParams: true, +}) + .get("/users") + .query({ email: "tiana_gleason63%40hotmail.com" }) + .reply( + 200, + [ + { + id: 1, + firstName: "Kyla", + email: "tiana_gleason63@hotmail.com", + lastName: "Romaguera", + status: false, + birthday: "2020-10-12T09:00:23.833Z", + avatar: [ + { + name: "Kyla.jpg", + percent: 100, + size: 40088, + status: "done", + type: "image/jpeg", + uid: "rc-upload-1621879096181", + url: "http://www.gravatar.com/avatar/86d13aa71a6653086e50c8b72c2b5818?s=300", + }, + ], + }, + ], + [ + "Server", + "nginx/1.17.10", + "Date", + "Tue, 25 May 2021 07:17:50 GMT", + "Content-Type", + "application/json; charset=utf-8", + "Content-Length", + "485", + "Connection", + "close", + "Vary", + "Accept-Encoding", + "X-Powered-By", + "Express", + "Vary", + "Origin, Accept-Encoding", + "Access-Control-Allow-Credentials", + "true", + "Cache-Control", + "no-cache", + "Pragma", + "no-cache", + "Expires", + "-1", + "Access-Control-Allow-Origin", + "*", + "X-Content-Type-Options", + "nosniff", + "ETag", + 'W/"1e5-eKJLrCvV7sta2h3LMct6tDe1DnU"', + ], + ); diff --git a/packages/rest/src/data-providers/simple-rest/specs/custom/index.spec.ts b/packages/rest/src/data-providers/simple-rest/specs/custom/index.spec.ts new file mode 100644 index 000000000000..0cd2790001a0 --- /dev/null +++ b/packages/rest/src/data-providers/simple-rest/specs/custom/index.spec.ts @@ -0,0 +1,63 @@ +import { simpleRestDataProvider } from ".."; +import "./index.mock"; + +describe("custom", () => { + const API_URL = "https://api.fake-rest.refine.dev"; + + it("correct get response", async () => { + const response = await simpleRestDataProvider.custom!({ + url: `${API_URL}/users`, + method: "get", + }); + + expect(response.data[0]["id"]).toBe(1); + expect(response.data[0]["email"]).toBe("tiana_gleason63@hotmail.com"); + }); + + it("correct filter response", async () => { + const response = await simpleRestDataProvider.custom!({ + url: `${API_URL}/users`, + method: "get", + query: { + email: "tiana_gleason63@hotmail.com", + }, + }); + + expect(response.data[0]["id"]).toBe(1); + expect(response.data[0]["email"]).toBe("tiana_gleason63@hotmail.com"); + }); + + it("correct sort response", async () => { + const response = await simpleRestDataProvider.custom!({ + url: `${API_URL}/users`, + method: "get", + query: { + _order: "asc", + _sort: "id", + }, + }); + + expect(response.data[0]["id"]).toBe(1); + }); + + it("correct post request", async () => { + const response = await simpleRestDataProvider.custom!({ + url: `${API_URL}/users`, + method: "post", + payload: { + firstName: "test", + lastName: "test", + email: "test@mail.com", + status: true, + }, + }); + + expect(response.data).toEqual({ + email: "test@mail.com", + firstName: "test", + id: 51, + lastName: "test", + status: true, + }); + }); +}); diff --git a/packages/rest/src/data-providers/simple-rest/specs/deleteOne/index.mock.ts b/packages/rest/src/data-providers/simple-rest/specs/deleteOne/index.mock.ts new file mode 100644 index 000000000000..5df637cbf621 --- /dev/null +++ b/packages/rest/src/data-providers/simple-rest/specs/deleteOne/index.mock.ts @@ -0,0 +1,32 @@ +import nock from "nock"; + +nock("https://api.fake-rest.refine.dev:443", { encodedQueryParams: true }) + .delete("/posts/1") + .reply(200, {}, [ + "Server", + "nginx/1.17.10", + "Date", + "Tue, 30 Mar 2021 12:23:06 GMT", + "Content-Type", + "application/json; charset=utf-8", + "Content-Length", + "2", + "Connection", + "close", + "X-Powered-By", + "Express", + "Vary", + "Origin, Accept-Encoding", + "Access-Control-Allow-Credentials", + "true", + "Cache-Control", + "no-cache", + "Pragma", + "no-cache", + "Expires", + "-1", + "X-Content-Type-Options", + "nosniff", + "ETag", + 'W/"2-vyGp6PvFo4RvsFtPoIWeCReyIC8"', + ]); diff --git a/packages/rest/src/data-providers/simple-rest/specs/deleteOne/index.spec.ts b/packages/rest/src/data-providers/simple-rest/specs/deleteOne/index.spec.ts new file mode 100644 index 000000000000..92b73294fbda --- /dev/null +++ b/packages/rest/src/data-providers/simple-rest/specs/deleteOne/index.spec.ts @@ -0,0 +1,15 @@ +import { simpleRestDataProvider } from ".."; +import "./index.mock"; + +describe("deleteOne", () => { + it("correct response", async () => { + const response = await simpleRestDataProvider.deleteOne({ + resource: "posts", + id: "1", + }); + + const { data } = response; + + expect(data).toEqual({}); + }); +}); diff --git a/packages/rest/src/data-providers/simple-rest/specs/getList/index.mock.ts b/packages/rest/src/data-providers/simple-rest/specs/getList/index.mock.ts new file mode 100644 index 000000000000..958c81267cc2 --- /dev/null +++ b/packages/rest/src/data-providers/simple-rest/specs/getList/index.mock.ts @@ -0,0 +1,1172 @@ +import nock from "nock"; + +nock("https://api.fake-rest.refine.dev:443", { encodedQueryParams: true }) + .get("/posts") + .query({ _end: "10", _start: "0" }) + .reply( + 200, + [ + { + id: 1, + title: + "Mollitia ipsam nisi in porro velit asperiores et quaerat dolorem.", + slug: "vel-qui-dolorem", + content: + "Quam ducimus soluta voluptas qui illum recusandae occaecati. Inventore voluptate labore non. Perferendis dolorem cupiditate nemo iusto ut qui iure et. Iusto sunt ipsam et quia placeat minima odio. Et doloremque quis similique nulla vel omnis et vel ut. Dolorem totam similique est dignissimos fugit minima. Occaecati veniam suscipit quae quasi occaecati non illum incidunt omnis. Qui at fugiat non voluptatum quis. Autem odio voluptates vero qui temporibus. Repellendus et voluptatum.", + hit: 858512, + category: { id: 44 }, + user: { id: 14 }, + status: "rejected", + createdAt: "2021-04-28T19:43:05.203Z", + image: [ + { + url: "http://placeimg.com/640/480", + name: "9144d5cd-977a-42fe-bfee-bcce61c567e8", + status: "done", + type: "image/jpeg", + uid: "da9b6491-5820-4347-90a0-cb735a21d787", + }, + ], + tags: [7, 5, 6], + language: 3, + }, + { + id: 2, + title: "Eos est est accusamus.", + slug: "laborum-ducimus-suscipit", + content: + "Cum nemo optio odit officiis consequatur dolor necessitatibus. Et dolorem incidunt tenetur. Enim laboriosam pariatur omnis culpa et accusantium esse dolorum. Quas iste voluptatem impedit enim dolorem impedit aut. Quibusdam magni similique quis beatae vero rem. Qui qui officiis repudiandae soluta veritatis. Unde corrupti ut voluptas error quasi modi exercitationem. Et laborum qui vitae ut porro ex. Quod voluptatibus facilis quas nobis. Aut fugiat eveniet sit necessitatibus sapiente sunt eius.", + hit: 197123, + category: { id: 20 }, + user: { id: 8 }, + status: "draft", + createdAt: "2019-09-17T11:04:27.561Z", + image: [ + { + url: "http://placeimg.com/640/480", + name: "b39124e2-a068-4744-a472-b903eb95338d", + status: "done", + type: "image/jpeg", + uid: "f00c870d-05ef-4a9f-9be8-fdb971ffb7b4", + }, + ], + tags: [8], + language: 2, + }, + { + id: 3, + title: "Aut dolorum saepe et qui consequatur sed rem incidunt.", + slug: "in-architecto-deleniti", + content: + "Dolor aut impedit voluptates qui. In officiis voluptas repudiandae exercitationem. A commodi accusamus commodi modi quod. Suscipit assumenda iusto aut aperiam quasi eum est eos. Ipsum voluptatum earum. Sit doloremque voluptas sunt minima aut doloremque molestiae natus. Pariatur similique repellendus. Ipsum officiis ratione enim culpa ipsam voluptas et. Nulla aut eos eaque nulla sit. Sit possimus sed quia et.", + hit: 375876, + category: { id: 8 }, + user: { id: 31 }, + status: "draft", + createdAt: "2020-07-20T17:52:57.899Z", + image: [ + { + url: "http://placeimg.com/640/480", + name: "31af30fb-80d4-4678-987c-39e3f64403bb", + status: "done", + type: "image/jpeg", + uid: "3b673d36-45d0-43f5-a56a-beee004fbda5", + }, + ], + tags: [6, 4], + language: 3, + }, + { + id: 4, + title: "Nostrum suscipit incidunt expedita quo rerum.", + slug: "ullam-recusandae-accusamus", + content: + "Et dolorem dolor et et voluptas inventore ad ut. Voluptatem tempore minus et illum similique quas. Quis magnam id blanditiis voluptatem est ipsum qui illum quam. Quibusdam inventore ipsa labore amet ut. Praesentium possimus nihil beatae voluptates aperiam soluta magni. Vitae rerum et quod. Ipsam perspiciatis alias facilis ut aut maiores consequatur id quidem. Rem distinctio aut doloremque quia ex inventore repudiandae. Inventore dolorum dolorem maiores omnis minus voluptatum quae fugiat aut. Natus sit ut a.", + hit: 267503, + category: { id: 41 }, + user: { id: 1 }, + status: "rejected", + createdAt: "2020-01-21T17:27:22.434Z", + image: [ + { + url: "http://placeimg.com/640/480", + name: "6eebfd0d-5d4e-42e8-9083-1b4e698cad35", + status: "done", + type: "image/jpeg", + uid: "079135f7-e5f5-4e55-bf53-34c125168b9f", + }, + ], + tags: [1, 7], + language: 1, + }, + { + id: 5, + title: + "Dolorem non autem voluptatum consequuntur excepturi quis quis corrupti magnam.", + slug: "mollitia-quam-magnam", + content: + "Cum nulla distinctio deserunt voluptatum. Et voluptas sapiente accusamus impedit aspernatur deleniti nesciunt optio quo. Cumque assumenda nobis corrupti est. Sunt qui recusandae autem dolorem nisi in vero nihil magni. Corrupti corrupti labore unde nemo. Deserunt earum itaque illum velit. Rem architecto vel cupiditate ipsam veritatis officia et consequatur. Beatae quos voluptate facere porro accusamus natus. Non rem accusantium a provident ipsum est blanditiis ab. Voluptate atque error officiis officiis.", + hit: 267926, + category: { id: 42 }, + user: { id: 13 }, + status: "published", + createdAt: "2020-07-25T17:30:39.357Z", + image: [ + { + url: "http://placeimg.com/640/480", + name: "bf6e5c58-501d-4b75-bd35-8421ecb44706", + status: "done", + type: "image/jpeg", + uid: "c95295c5-d160-4ecb-a760-1679c6105840", + }, + ], + tags: [1], + language: 2, + }, + { + id: 6, + title: + "Architecto corporis magnam dolore quidem enim facere est beatae doloribus.", + slug: "inventore-repellendus-est", + content: + "Ad ea voluptatem praesentium sunt adipisci. Voluptatibus in et natus. Commodi dolores asperiores earum ex in incidunt. Maiores nihil eos consequuntur. Molestiae mollitia consectetur. Aut totam quia aut officiis a ut aut. Sint nisi sed quia eveniet praesentium reiciendis consectetur expedita beatae. Vero fugiat explicabo minima nobis consectetur ipsa. Explicabo est enim error. Molestiae exercitationem beatae id iste officiis temporibus est ut.", + hit: 646111, + category: { id: 12 }, + user: { id: 27 }, + status: "rejected", + createdAt: "2019-06-28T04:33:38.651Z", + image: [ + { + url: "http://placeimg.com/640/480", + name: "c78bfa5b-84b6-41a3-8c3c-1e9bde40c28e", + status: "done", + type: "image/jpeg", + uid: "23163fa8-6fa8-467a-8ac0-77e58b958da9", + }, + ], + tags: [9], + language: 1, + }, + { + id: 7, + title: "Quis nobis repellat.", + slug: "impedit-sed-voluptatem", + content: + "Temporibus nulla omnis non sed voluptatem consequatur et est qui. Ut deleniti non qui maiores consequatur et recusandae. Distinctio consequatur non nihil. Non sed reiciendis perspiciatis. Est hic similique. Exercitationem delectus esse ducimus. Sint numquam ipsum. Non ducimus sapiente eum omnis ex eligendi. Quod aut expedita fugiat voluptates eos. Incidunt laborum et odit sint qui vel facilis aut.", + hit: 295012, + category: { id: 45 }, + user: { id: 29 }, + status: "draft", + createdAt: "2021-04-15T12:23:53.759Z", + image: [ + { + url: "http://placeimg.com/640/480", + name: "077c6f80-c15f-4873-a60e-4463a12d10f2", + status: "done", + type: "image/jpeg", + uid: "c5cf5b73-f62f-43f6-9724-9d889a60d754", + }, + ], + tags: [3], + language: 2, + }, + { + id: 8, + title: "Tenetur illum modi qui et asperiores maiores deserunt.", + slug: "quos-rem-voluptas", + content: + "Repellat eum ut. Blanditiis ut aliquam et reiciendis magnam recusandae nesciunt. Magni possimus qui blanditiis. Voluptatibus velit rerum. Eligendi expedita ab cum amet sunt. Ut aliquid provident sed. Est earum numquam ut nostrum sint et recusandae quia maxime. Ut distinctio quibusdam. Velit magnam dolores vero labore et id magni est quis. Veniam dolores voluptates.", + hit: 823688, + category: { id: 23 }, + user: { id: 5 }, + status: "rejected", + createdAt: "2020-12-06T01:21:01.191Z", + image: [ + { + url: "http://placeimg.com/640/480", + name: "9aa408e2-601f-474d-9832-626071cb077e", + status: "done", + type: "image/jpeg", + uid: "1e08e18e-4aeb-499f-9eac-3063182a6f00", + }, + ], + tags: [7, 8, 5], + language: 3, + }, + { + id: 9, + title: + "Mollitia voluptatem molestiae magnam dolores vero deserunt culpa alias voluptatem.", + slug: "veniam-dolores-veniam", + content: + "Accusantium fugit odit voluptatem in odit. Impedit pariatur eos vel neque. Hic tempora magnam quo quis ut et dolores. Provident sint nostrum voluptatum quaerat illum aperiam provident sed earum. Nulla maxime magni sunt dolorem repellendus iure. Dignissimos et itaque ut quaerat est sunt iusto. Itaque voluptatem totam quia at qui. Laboriosam itaque aut. Dolor doloribus consequatur aut. Qui repellat vero quas natus et vero dolores sequi exercitationem.", + hit: 595493, + category: { id: 15 }, + user: { id: 50 }, + status: "rejected", + createdAt: "2021-04-15T15:55:00.138Z", + image: [ + { + url: "http://placeimg.com/640/480", + name: "938fe7f2-4ce1-40be-ba51-58f4b93de987", + status: "done", + type: "image/jpeg", + uid: "a7025a23-e1a1-4b77-a33a-e7cefa437338", + }, + ], + tags: [1, 4], + language: 3, + }, + { + id: 10, + title: "Consequuntur est tenetur sed impedit.", + slug: "soluta-a-sed", + content: + "Iusto magnam eos. Id quidem expedita quae sit est eos reiciendis voluptatem dolor. Vel et ratione error aut nostrum qui et temporibus. Ex ipsam qui est porro voluptatem doloribus. Reiciendis nesciunt quia voluptate. Vero quaerat aut voluptatem nam laudantium illo incidunt rerum ut. Minima cum tempore cumque non. Nobis voluptates voluptatum fugiat qui. Nihil molestias et pariatur est commodi est mollitia magnam. Totam aut sit quia ducimus placeat.", + hit: 27452, + category: { id: 31 }, + user: { id: 31 }, + status: "published", + createdAt: "2020-05-06T10:55:50.258Z", + image: [ + { + url: "http://placeimg.com/640/480", + name: "db179793-f9bb-4c65-b513-c82dc8288d8f", + status: "done", + type: "image/jpeg", + uid: "0e1fa473-bd3e-4a09-a9de-e8a7f5b8620c", + }, + ], + tags: [6, 4], + language: 1, + }, + ], + [ + "Server", + "nginx/1.17.10", + "Date", + "Mon, 21 Jun 2021 11:21:39 GMT", + "Content-Type", + "application/json; charset=utf-8", + "Content-Length", + "10695", + "Connection", + "close", + "Vary", + "Accept-Encoding", + "X-Powered-By", + "Express", + "Vary", + "Origin, Accept-Encoding", + "Access-Control-Allow-Credentials", + "true", + "Cache-Control", + "no-cache", + "Pragma", + "no-cache", + "Expires", + "-1", + "Access-Control-Allow-Origin", + "*", + "X-Total-Count", + "1000", + "Access-Control-Expose-Headers", + "X-Total-Count", + "X-Content-Type-Options", + "nosniff", + "ETag", + 'W/"29c7-GLPmKvMkCcw+mw07kCbVySmgE/o"', + ], + ); + +nock("https://api.fake-rest.refine.dev:443", { encodedQueryParams: true }) + .get("/posts") + .query({ _end: "10", _order: "asc", _sort: "id", _start: "0" }) + .reply( + 200, + [ + { + id: 1, + title: + "Mollitia ipsam nisi in porro velit asperiores et quaerat dolorem.", + slug: "vel-qui-dolorem", + content: + "Quam ducimus soluta voluptas qui illum recusandae occaecati. Inventore voluptate labore non. Perferendis dolorem cupiditate nemo iusto ut qui iure et. Iusto sunt ipsam et quia placeat minima odio. Et doloremque quis similique nulla vel omnis et vel ut. Dolorem totam similique est dignissimos fugit minima. Occaecati veniam suscipit quae quasi occaecati non illum incidunt omnis. Qui at fugiat non voluptatum quis. Autem odio voluptates vero qui temporibus. Repellendus et voluptatum.", + hit: 858512, + category: { id: 44 }, + user: { id: 14 }, + status: "rejected", + createdAt: "2021-04-28T19:43:05.203Z", + image: [ + { + url: "http://placeimg.com/640/480", + name: "9144d5cd-977a-42fe-bfee-bcce61c567e8", + status: "done", + type: "image/jpeg", + uid: "da9b6491-5820-4347-90a0-cb735a21d787", + }, + ], + tags: [7, 5, 6], + language: 3, + }, + { + id: 2, + title: "Eos est est accusamus.", + slug: "laborum-ducimus-suscipit", + content: + "Cum nemo optio odit officiis consequatur dolor necessitatibus. Et dolorem incidunt tenetur. Enim laboriosam pariatur omnis culpa et accusantium esse dolorum. Quas iste voluptatem impedit enim dolorem impedit aut. Quibusdam magni similique quis beatae vero rem. Qui qui officiis repudiandae soluta veritatis. Unde corrupti ut voluptas error quasi modi exercitationem. Et laborum qui vitae ut porro ex. Quod voluptatibus facilis quas nobis. Aut fugiat eveniet sit necessitatibus sapiente sunt eius.", + hit: 197123, + category: { id: 20 }, + user: { id: 8 }, + status: "draft", + createdAt: "2019-09-17T11:04:27.561Z", + image: [ + { + url: "http://placeimg.com/640/480", + name: "b39124e2-a068-4744-a472-b903eb95338d", + status: "done", + type: "image/jpeg", + uid: "f00c870d-05ef-4a9f-9be8-fdb971ffb7b4", + }, + ], + tags: [8], + language: 2, + }, + { + id: 3, + title: "Aut dolorum saepe et qui consequatur sed rem incidunt.", + slug: "in-architecto-deleniti", + content: + "Dolor aut impedit voluptates qui. In officiis voluptas repudiandae exercitationem. A commodi accusamus commodi modi quod. Suscipit assumenda iusto aut aperiam quasi eum est eos. Ipsum voluptatum earum. Sit doloremque voluptas sunt minima aut doloremque molestiae natus. Pariatur similique repellendus. Ipsum officiis ratione enim culpa ipsam voluptas et. Nulla aut eos eaque nulla sit. Sit possimus sed quia et.", + hit: 375876, + category: { id: 8 }, + user: { id: 31 }, + status: "draft", + createdAt: "2020-07-20T17:52:57.899Z", + image: [ + { + url: "http://placeimg.com/640/480", + name: "31af30fb-80d4-4678-987c-39e3f64403bb", + status: "done", + type: "image/jpeg", + uid: "3b673d36-45d0-43f5-a56a-beee004fbda5", + }, + ], + tags: [6, 4], + language: 3, + }, + { + id: 4, + title: "Nostrum suscipit incidunt expedita quo rerum.", + slug: "ullam-recusandae-accusamus", + content: + "Et dolorem dolor et et voluptas inventore ad ut. Voluptatem tempore minus et illum similique quas. Quis magnam id blanditiis voluptatem est ipsum qui illum quam. Quibusdam inventore ipsa labore amet ut. Praesentium possimus nihil beatae voluptates aperiam soluta magni. Vitae rerum et quod. Ipsam perspiciatis alias facilis ut aut maiores consequatur id quidem. Rem distinctio aut doloremque quia ex inventore repudiandae. Inventore dolorum dolorem maiores omnis minus voluptatum quae fugiat aut. Natus sit ut a.", + hit: 267503, + category: { id: 41 }, + user: { id: 1 }, + status: "rejected", + createdAt: "2020-01-21T17:27:22.434Z", + image: [ + { + url: "http://placeimg.com/640/480", + name: "6eebfd0d-5d4e-42e8-9083-1b4e698cad35", + status: "done", + type: "image/jpeg", + uid: "079135f7-e5f5-4e55-bf53-34c125168b9f", + }, + ], + tags: [1, 7], + language: 1, + }, + { + id: 5, + title: + "Dolorem non autem voluptatum consequuntur excepturi quis quis corrupti magnam.", + slug: "mollitia-quam-magnam", + content: + "Cum nulla distinctio deserunt voluptatum. Et voluptas sapiente accusamus impedit aspernatur deleniti nesciunt optio quo. Cumque assumenda nobis corrupti est. Sunt qui recusandae autem dolorem nisi in vero nihil magni. Corrupti corrupti labore unde nemo. Deserunt earum itaque illum velit. Rem architecto vel cupiditate ipsam veritatis officia et consequatur. Beatae quos voluptate facere porro accusamus natus. Non rem accusantium a provident ipsum est blanditiis ab. Voluptate atque error officiis officiis.", + hit: 267926, + category: { id: 42 }, + user: { id: 13 }, + status: "published", + createdAt: "2020-07-25T17:30:39.357Z", + image: [ + { + url: "http://placeimg.com/640/480", + name: "bf6e5c58-501d-4b75-bd35-8421ecb44706", + status: "done", + type: "image/jpeg", + uid: "c95295c5-d160-4ecb-a760-1679c6105840", + }, + ], + tags: [1], + language: 2, + }, + { + id: 6, + title: + "Architecto corporis magnam dolore quidem enim facere est beatae doloribus.", + slug: "inventore-repellendus-est", + content: + "Ad ea voluptatem praesentium sunt adipisci. Voluptatibus in et natus. Commodi dolores asperiores earum ex in incidunt. Maiores nihil eos consequuntur. Molestiae mollitia consectetur. Aut totam quia aut officiis a ut aut. Sint nisi sed quia eveniet praesentium reiciendis consectetur expedita beatae. Vero fugiat explicabo minima nobis consectetur ipsa. Explicabo est enim error. Molestiae exercitationem beatae id iste officiis temporibus est ut.", + hit: 646111, + category: { id: 12 }, + user: { id: 27 }, + status: "rejected", + createdAt: "2019-06-28T04:33:38.651Z", + image: [ + { + url: "http://placeimg.com/640/480", + name: "c78bfa5b-84b6-41a3-8c3c-1e9bde40c28e", + status: "done", + type: "image/jpeg", + uid: "23163fa8-6fa8-467a-8ac0-77e58b958da9", + }, + ], + tags: [9], + language: 1, + }, + { + id: 7, + title: "Quis nobis repellat.", + slug: "impedit-sed-voluptatem", + content: + "Temporibus nulla omnis non sed voluptatem consequatur et est qui. Ut deleniti non qui maiores consequatur et recusandae. Distinctio consequatur non nihil. Non sed reiciendis perspiciatis. Est hic similique. Exercitationem delectus esse ducimus. Sint numquam ipsum. Non ducimus sapiente eum omnis ex eligendi. Quod aut expedita fugiat voluptates eos. Incidunt laborum et odit sint qui vel facilis aut.", + hit: 295012, + category: { id: 45 }, + user: { id: 29 }, + status: "draft", + createdAt: "2021-04-15T12:23:53.759Z", + image: [ + { + url: "http://placeimg.com/640/480", + name: "077c6f80-c15f-4873-a60e-4463a12d10f2", + status: "done", + type: "image/jpeg", + uid: "c5cf5b73-f62f-43f6-9724-9d889a60d754", + }, + ], + tags: [3], + language: 2, + }, + { + id: 8, + title: "Tenetur illum modi qui et asperiores maiores deserunt.", + slug: "quos-rem-voluptas", + content: + "Repellat eum ut. Blanditiis ut aliquam et reiciendis magnam recusandae nesciunt. Magni possimus qui blanditiis. Voluptatibus velit rerum. Eligendi expedita ab cum amet sunt. Ut aliquid provident sed. Est earum numquam ut nostrum sint et recusandae quia maxime. Ut distinctio quibusdam. Velit magnam dolores vero labore et id magni est quis. Veniam dolores voluptates.", + hit: 823688, + category: { id: 23 }, + user: { id: 5 }, + status: "rejected", + createdAt: "2020-12-06T01:21:01.191Z", + image: [ + { + url: "http://placeimg.com/640/480", + name: "9aa408e2-601f-474d-9832-626071cb077e", + status: "done", + type: "image/jpeg", + uid: "1e08e18e-4aeb-499f-9eac-3063182a6f00", + }, + ], + tags: [7, 8, 5], + language: 3, + }, + { + id: 9, + title: + "Mollitia voluptatem molestiae magnam dolores vero deserunt culpa alias voluptatem.", + slug: "veniam-dolores-veniam", + content: + "Accusantium fugit odit voluptatem in odit. Impedit pariatur eos vel neque. Hic tempora magnam quo quis ut et dolores. Provident sint nostrum voluptatum quaerat illum aperiam provident sed earum. Nulla maxime magni sunt dolorem repellendus iure. Dignissimos et itaque ut quaerat est sunt iusto. Itaque voluptatem totam quia at qui. Laboriosam itaque aut. Dolor doloribus consequatur aut. Qui repellat vero quas natus et vero dolores sequi exercitationem.", + hit: 595493, + category: { id: 15 }, + user: { id: 50 }, + status: "rejected", + createdAt: "2021-04-15T15:55:00.138Z", + image: [ + { + url: "http://placeimg.com/640/480", + name: "938fe7f2-4ce1-40be-ba51-58f4b93de987", + status: "done", + type: "image/jpeg", + uid: "a7025a23-e1a1-4b77-a33a-e7cefa437338", + }, + ], + tags: [1, 4], + language: 3, + }, + { + id: 10, + title: "Consequuntur est tenetur sed impedit.", + slug: "soluta-a-sed", + content: + "Iusto magnam eos. Id quidem expedita quae sit est eos reiciendis voluptatem dolor. Vel et ratione error aut nostrum qui et temporibus. Ex ipsam qui est porro voluptatem doloribus. Reiciendis nesciunt quia voluptate. Vero quaerat aut voluptatem nam laudantium illo incidunt rerum ut. Minima cum tempore cumque non. Nobis voluptates voluptatum fugiat qui. Nihil molestias et pariatur est commodi est mollitia magnam. Totam aut sit quia ducimus placeat.", + hit: 27452, + category: { id: 31 }, + user: { id: 31 }, + status: "published", + createdAt: "2020-05-06T10:55:50.258Z", + image: [ + { + url: "http://placeimg.com/640/480", + name: "db179793-f9bb-4c65-b513-c82dc8288d8f", + status: "done", + type: "image/jpeg", + uid: "0e1fa473-bd3e-4a09-a9de-e8a7f5b8620c", + }, + ], + tags: [6, 4], + language: 1, + }, + ], + [ + "Server", + "nginx/1.17.10", + "Date", + "Mon, 21 Jun 2021 11:21:39 GMT", + "Content-Type", + "application/json; charset=utf-8", + "Content-Length", + "10695", + "Connection", + "close", + "Vary", + "Accept-Encoding", + "X-Powered-By", + "Express", + "Vary", + "Origin, Accept-Encoding", + "Access-Control-Allow-Credentials", + "true", + "Cache-Control", + "no-cache", + "Pragma", + "no-cache", + "Expires", + "-1", + "Access-Control-Allow-Origin", + "*", + "X-Total-Count", + "1000", + "Access-Control-Expose-Headers", + "X-Total-Count", + "X-Content-Type-Options", + "nosniff", + "ETag", + 'W/"29c7-GLPmKvMkCcw+mw07kCbVySmgE/o"', + ], + ); + +nock("https://api.fake-rest.refine.dev:443", { encodedQueryParams: true }) + .get("/posts") + .query({ _end: "10", _start: "0", "category.id": "1" }) + .reply( + 200, + [ + { + id: 44, + title: "Et eum fugit aliquam est et ducimus cum et veniam.", + slug: "labore-modi-unde", + content: + "Accusamus iure eos eos qui id nesciunt. Nisi magnam omnis quia enim. Aut veritatis voluptatem voluptas ex adipisci modi error mollitia. Quidem voluptates qui et perferendis explicabo id. Vitae perferendis maxime. Illo possimus molestiae odio sed fugiat et eos. Perferendis earum recusandae debitis illum et vel. Vel illum consequuntur amet. Nihil consequatur accusantium maxime omnis maxime. Voluptatem minus est doloribus.", + hit: 84661, + category: { id: 1 }, + user: { id: 4 }, + status: "draft", + createdAt: "2019-09-17T18:55:38.232Z", + image: [ + { + url: "http://placeimg.com/640/480", + name: "eb5ef88c-61f9-4861-9c0a-e2a98bf13b61", + status: "done", + type: "image/jpeg", + uid: "1447eba2-8fa8-4292-97bb-0d320d3587f0", + }, + ], + tags: [5, 7], + language: 3, + }, + { + id: 75, + title: "Et modi id.", + slug: "voluptatem-quis-rerum", + content: + "Commodi dolorem expedita corporis occaecati minima tempora cum voluptatem. Nihil sunt tempora neque. Mollitia minus totam autem sunt soluta cum voluptas. Occaecati in atque architecto eligendi quae autem in vel quaerat. Quo quisquam qui adipisci non libero. Sit quasi qui odio nesciunt. Eum est qui at velit. Deserunt est nulla nobis amet officia nulla vero explicabo quaerat. Dolorem earum sunt voluptatem soluta. A eveniet officiis ut impedit velit quia fuga quia voluptas.", + hit: 444728, + category: { id: 1 }, + user: { id: 50 }, + status: "rejected", + createdAt: "2020-12-25T13:23:41.827Z", + image: [ + { + url: "http://placeimg.com/640/480", + name: "ebbaf4de-54cb-4189-88df-5caffb78dc37", + status: "done", + type: "image/jpeg", + uid: "d6d8e449-c79f-4ffd-9b43-937e2a1d2f3b", + }, + ], + tags: [3], + language: 3, + }, + { + id: 115, + title: + "In dignissimos dolor doloremque laborum possimus corrupti ut similique minus.", + slug: "nihil-cupiditate-officiis", + content: + "Tempore est commodi a ut. Eos ex ullam et exercitationem aut magnam ut sapiente. Qui dolorem officiis voluptas sed ut qui quod vel. Error quis fugit. Est ut quidem id repudiandae. Officia quis ab rerum unde dignissimos aperiam. Assumenda veniam qui qui fugiat autem quis. Aut eos saepe doloribus unde tenetur. Exercitationem quas et quidem minus vel et. Ea officia et totam et sed.", + hit: 977447, + category: { id: 1 }, + user: { id: 31 }, + status: "published", + createdAt: "2019-11-23T22:55:29.848Z", + image: [ + { + url: "http://placeimg.com/640/480", + name: "292c6a96-2ceb-4ed1-96b2-21f0299fd849", + status: "done", + type: "image/jpeg", + uid: "2a1fd735-648f-4768-b509-922838986f8f", + }, + ], + tags: [8, 9, 3], + language: 2, + }, + { + id: 127, + title: "Debitis sed ut voluptate at temporibus soluta.", + slug: "optio-qui-tenetur", + content: + "Quaerat earum est nihil consectetur aut debitis recusandae esse. Est ut eius et provident et iusto excepturi. Totam hic optio aliquid voluptas quae eius. Molestiae quos harum alias rerum quia doloremque commodi quia. Libero repellendus voluptatibus aliquam optio cum. Est dolor id reiciendis necessitatibus dolores ex qui sint. Et laborum modi reprehenderit laborum magni iure consequatur. Minima dolorum facere voluptatem laudantium veritatis iusto assumenda asperiores. Quia quo sit qui sequi unde voluptatum similique. Tempora delectus quos.", + hit: 56553, + category: { id: 1 }, + user: { id: 11 }, + status: "published", + createdAt: "2019-10-14T04:02:21.496Z", + image: [ + { + url: "http://placeimg.com/640/480", + name: "d60bd008-665b-4ec4-ad01-7d5fe02d5c39", + status: "done", + type: "image/jpeg", + uid: "1c9ff9a4-7e53-4c6f-ba75-fcd0671a4df1", + }, + ], + tags: [4], + language: 2, + }, + { + id: 151, + title: "Neque qui id quia.", + slug: "consequatur-dolorem-reiciendis", + content: + "Debitis sapiente qui blanditiis temporibus ad dolorem enim deserunt officiis. Unde omnis voluptates rerum. Aut in consectetur quo molestiae magni asperiores sint omnis qui. Quia ex reiciendis. Dolorem maiores molestias voluptas molestiae modi et. Qui qui maxime. Nesciunt voluptas neque. Cupiditate iure et sed commodi consectetur. Et enim dolores necessitatibus voluptatem aliquam. Soluta porro accusamus et velit omnis.", + hit: 47687, + category: { id: 1 }, + user: { id: 19 }, + status: "published", + createdAt: "2020-07-05T23:07:51.125Z", + image: [ + { + url: "http://placeimg.com/640/480", + name: "80ecf4d3-f8f4-4e05-bd9d-50bb156529d3", + status: "done", + type: "image/jpeg", + uid: "c4a5f2f2-373f-4422-bcea-94c9d23f6ee7", + }, + ], + tags: [7, 5, 3], + language: 1, + }, + { + id: 462, + title: "Iusto doloribus velit aut ut.", + slug: "qui-delectus-placeat", + content: + "Aspernatur ipsa mollitia ad odio laudantium possimus sed et. Non voluptatem maxime aut quisquam sit. Qui magnam explicabo culpa repellendus in eum repellendus excepturi. Occaecati recusandae et esse consequatur quo at aut quia. Adipisci earum quod numquam voluptatem commodi. Temporibus delectus ut tempore nisi. Accusantium illo beatae similique. Dolorum assumenda voluptatem. Et sunt ullam dicta voluptas. Esse aut iste.", + hit: 155374, + category: { id: 1 }, + user: { id: 33 }, + status: "draft", + createdAt: "2020-11-28T18:55:11.352Z", + image: [ + { + url: "http://placeimg.com/640/480", + name: "ee969863-5854-4570-b474-369436c3d861", + status: "done", + type: "image/jpeg", + uid: "b840bc3e-4d0e-40b2-8835-b0137679946d", + }, + ], + tags: [4], + language: 1, + }, + { + id: 483, + title: "Sequi ab neque maiores dolor dolores ut.", + slug: "iure-quibusdam-minus", + content: + "Quod numquam quam accusamus. Modi reiciendis omnis et aut accusantium perspiciatis et adipisci consequuntur. Similique et reiciendis asperiores sequi placeat laudantium voluptatem quasi ut. Quia qui laborum placeat totam atque explicabo qui. Tempore in nesciunt in omnis culpa magni animi cumque atque. In in a repudiandae rerum. Sit earum corrupti quos mollitia vel amet quia aut. Dolorum voluptatem voluptatem qui et. Quisquam omnis fuga est excepturi expedita placeat sed. Enim quia velit.", + hit: 47216, + category: { id: 1 }, + user: { id: 43 }, + status: "rejected", + createdAt: "2020-01-15T18:42:02.469Z", + image: [ + { + url: "http://placeimg.com/640/480", + name: "160a465a-2eca-4c62-9f66-e39802113cf8", + status: "done", + type: "image/jpeg", + uid: "5c8e2d38-c40e-4031-9a91-1c24afd1309d", + }, + ], + tags: [3], + language: 1, + }, + { + id: 617, + title: "Aperiam non et.", + slug: "perspiciatis-ea-maxime", + content: + "Accusantium et ea nihil sit. Quia minima qui esse repudiandae molestiae. Nam perspiciatis cumque et quis doloribus et et non. Aliquam minima a explicabo. Quod reprehenderit nobis sit dolorem. Molestias qui aspernatur quod expedita quas. Laudantium necessitatibus porro aliquam cupiditate aperiam. Recusandae natus alias deserunt dicta animi facilis. Cumque tempore iste eum ut id a quae minima molestiae. Consequuntur quia aut earum.", + hit: 512, + category: { id: 1 }, + user: { id: 47 }, + status: "published", + createdAt: "2021-06-13T12:52:08.459Z", + image: [ + { + url: "http://placeimg.com/640/480", + name: "00777f91-b587-4e57-b2a2-c9c9be5c1904", + status: "done", + type: "image/jpeg", + uid: "fa5cb4a7-99ff-41e1-a441-34f16136898e", + }, + ], + tags: [8], + language: 3, + }, + { + id: 622, + title: + "Maxime est autem cupiditate culpa voluptatem natus quidem doloremque.", + slug: "earum-voluptas-corporis", + content: + "Nulla voluptatem quo impedit voluptate quibusdam facilis et quasi dolorem. Officia in ratione ex libero rerum. Voluptas nobis et ullam veniam natus error. Et facere soluta voluptatem neque neque voluptatem voluptatum maiores. Repellendus error nostrum at expedita consectetur quae tempora ipsum. Placeat sed et ea porro vero. Eos accusantium assumenda aliquid rerum. Aut libero et voluptatem vero sint. Molestiae non amet et quis eaque. Sunt hic adipisci harum aut vel explicabo consectetur.", + hit: 913434, + category: { id: 1 }, + user: { id: 49 }, + status: "rejected", + createdAt: "2019-06-22T19:24:19.494Z", + image: [ + { + url: "http://placeimg.com/640/480", + name: "4fda0ad1-2c51-4e44-818a-35ae61e4e2b4", + status: "done", + type: "image/jpeg", + uid: "aea68791-7db6-497e-8cbb-087561263483", + }, + ], + tags: [9, 2], + language: 2, + }, + { + id: 644, + title: "Dolorem laboriosam et est modi rerum placeat iusto.", + slug: "vero-aut-neque", + content: + "Velit quo harum assumenda. Consectetur quidem mollitia ex inventore. Iste itaque similique quod velit facere quaerat. Nihil sint qui vel rerum libero. Maiores est quo. Voluptatem blanditiis sunt similique voluptatibus porro odit. In odit delectus perspiciatis. Consequatur omnis dolorem dignissimos consectetur quasi delectus dolores id. Omnis quia in. Sit voluptas esse non dolore quas molestias molestias est enim.", + hit: 272834, + category: { id: 1 }, + user: { id: 33 }, + status: "draft", + createdAt: "2019-08-15T12:27:39.919Z", + image: [ + { + url: "http://placeimg.com/640/480", + name: "86c5bd92-438c-4f4b-8b30-c4de9eeb9fd1", + status: "done", + type: "image/jpeg", + uid: "c70deb9d-bae1-41c6-b71c-ee352c2721ac", + }, + ], + tags: [2, 4], + language: 3, + }, + ], + [ + "Server", + "nginx/1.17.10", + "Date", + "Mon, 21 Jun 2021 11:21:39 GMT", + "Content-Type", + "application/json; charset=utf-8", + "Content-Length", + "10542", + "Connection", + "close", + "Vary", + "Accept-Encoding", + "X-Powered-By", + "Express", + "Vary", + "Origin, Accept-Encoding", + "Access-Control-Allow-Credentials", + "true", + "Cache-Control", + "no-cache", + "Pragma", + "no-cache", + "Expires", + "-1", + "Access-Control-Allow-Origin", + "*", + "X-Total-Count", + "17", + "Access-Control-Expose-Headers", + "X-Total-Count", + "X-Content-Type-Options", + "nosniff", + "ETag", + 'W/"292e-QyaIdJgeENcIUIgHmY1COHjk5N0"', + ], + ); + +nock("https://api.fake-rest.refine.dev:443", { encodedQueryParams: true }) + .get("/posts") + .query({ + _end: "10", + _order: "asc", + _sort: "id", + _start: "0", + "category.id": "1", + }) + .reply( + 200, + [ + { + id: 44, + title: "Et eum fugit aliquam est et ducimus cum et veniam.", + slug: "labore-modi-unde", + content: + "Accusamus iure eos eos qui id nesciunt. Nisi magnam omnis quia enim. Aut veritatis voluptatem voluptas ex adipisci modi error mollitia. Quidem voluptates qui et perferendis explicabo id. Vitae perferendis maxime. Illo possimus molestiae odio sed fugiat et eos. Perferendis earum recusandae debitis illum et vel. Vel illum consequuntur amet. Nihil consequatur accusantium maxime omnis maxime. Voluptatem minus est doloribus.", + hit: 84661, + category: { id: 1 }, + user: { id: 4 }, + status: "draft", + createdAt: "2019-09-17T18:55:38.232Z", + image: [ + { + url: "http://placeimg.com/640/480", + name: "eb5ef88c-61f9-4861-9c0a-e2a98bf13b61", + status: "done", + type: "image/jpeg", + uid: "1447eba2-8fa8-4292-97bb-0d320d3587f0", + }, + ], + tags: [5, 7], + language: 3, + }, + { + id: 75, + title: "Et modi id.", + slug: "voluptatem-quis-rerum", + content: + "Commodi dolorem expedita corporis occaecati minima tempora cum voluptatem. Nihil sunt tempora neque. Mollitia minus totam autem sunt soluta cum voluptas. Occaecati in atque architecto eligendi quae autem in vel quaerat. Quo quisquam qui adipisci non libero. Sit quasi qui odio nesciunt. Eum est qui at velit. Deserunt est nulla nobis amet officia nulla vero explicabo quaerat. Dolorem earum sunt voluptatem soluta. A eveniet officiis ut impedit velit quia fuga quia voluptas.", + hit: 444728, + category: { id: 1 }, + user: { id: 50 }, + status: "rejected", + createdAt: "2020-12-25T13:23:41.827Z", + image: [ + { + url: "http://placeimg.com/640/480", + name: "ebbaf4de-54cb-4189-88df-5caffb78dc37", + status: "done", + type: "image/jpeg", + uid: "d6d8e449-c79f-4ffd-9b43-937e2a1d2f3b", + }, + ], + tags: [3], + language: 3, + }, + { + id: 115, + title: + "In dignissimos dolor doloremque laborum possimus corrupti ut similique minus.", + slug: "nihil-cupiditate-officiis", + content: + "Tempore est commodi a ut. Eos ex ullam et exercitationem aut magnam ut sapiente. Qui dolorem officiis voluptas sed ut qui quod vel. Error quis fugit. Est ut quidem id repudiandae. Officia quis ab rerum unde dignissimos aperiam. Assumenda veniam qui qui fugiat autem quis. Aut eos saepe doloribus unde tenetur. Exercitationem quas et quidem minus vel et. Ea officia et totam et sed.", + hit: 977447, + category: { id: 1 }, + user: { id: 31 }, + status: "published", + createdAt: "2019-11-23T22:55:29.848Z", + image: [ + { + url: "http://placeimg.com/640/480", + name: "292c6a96-2ceb-4ed1-96b2-21f0299fd849", + status: "done", + type: "image/jpeg", + uid: "2a1fd735-648f-4768-b509-922838986f8f", + }, + ], + tags: [8, 9, 3], + language: 2, + }, + { + id: 127, + title: "Debitis sed ut voluptate at temporibus soluta.", + slug: "optio-qui-tenetur", + content: + "Quaerat earum est nihil consectetur aut debitis recusandae esse. Est ut eius et provident et iusto excepturi. Totam hic optio aliquid voluptas quae eius. Molestiae quos harum alias rerum quia doloremque commodi quia. Libero repellendus voluptatibus aliquam optio cum. Est dolor id reiciendis necessitatibus dolores ex qui sint. Et laborum modi reprehenderit laborum magni iure consequatur. Minima dolorum facere voluptatem laudantium veritatis iusto assumenda asperiores. Quia quo sit qui sequi unde voluptatum similique. Tempora delectus quos.", + hit: 56553, + category: { id: 1 }, + user: { id: 11 }, + status: "published", + createdAt: "2019-10-14T04:02:21.496Z", + image: [ + { + url: "http://placeimg.com/640/480", + name: "d60bd008-665b-4ec4-ad01-7d5fe02d5c39", + status: "done", + type: "image/jpeg", + uid: "1c9ff9a4-7e53-4c6f-ba75-fcd0671a4df1", + }, + ], + tags: [4], + language: 2, + }, + { + id: 151, + title: "Neque qui id quia.", + slug: "consequatur-dolorem-reiciendis", + content: + "Debitis sapiente qui blanditiis temporibus ad dolorem enim deserunt officiis. Unde omnis voluptates rerum. Aut in consectetur quo molestiae magni asperiores sint omnis qui. Quia ex reiciendis. Dolorem maiores molestias voluptas molestiae modi et. Qui qui maxime. Nesciunt voluptas neque. Cupiditate iure et sed commodi consectetur. Et enim dolores necessitatibus voluptatem aliquam. Soluta porro accusamus et velit omnis.", + hit: 47687, + category: { id: 1 }, + user: { id: 19 }, + status: "published", + createdAt: "2020-07-05T23:07:51.125Z", + image: [ + { + url: "http://placeimg.com/640/480", + name: "80ecf4d3-f8f4-4e05-bd9d-50bb156529d3", + status: "done", + type: "image/jpeg", + uid: "c4a5f2f2-373f-4422-bcea-94c9d23f6ee7", + }, + ], + tags: [7, 5, 3], + language: 1, + }, + { + id: 462, + title: "Iusto doloribus velit aut ut.", + slug: "qui-delectus-placeat", + content: + "Aspernatur ipsa mollitia ad odio laudantium possimus sed et. Non voluptatem maxime aut quisquam sit. Qui magnam explicabo culpa repellendus in eum repellendus excepturi. Occaecati recusandae et esse consequatur quo at aut quia. Adipisci earum quod numquam voluptatem commodi. Temporibus delectus ut tempore nisi. Accusantium illo beatae similique. Dolorum assumenda voluptatem. Et sunt ullam dicta voluptas. Esse aut iste.", + hit: 155374, + category: { id: 1 }, + user: { id: 33 }, + status: "draft", + createdAt: "2020-11-28T18:55:11.352Z", + image: [ + { + url: "http://placeimg.com/640/480", + name: "ee969863-5854-4570-b474-369436c3d861", + status: "done", + type: "image/jpeg", + uid: "b840bc3e-4d0e-40b2-8835-b0137679946d", + }, + ], + tags: [4], + language: 1, + }, + { + id: 483, + title: "Sequi ab neque maiores dolor dolores ut.", + slug: "iure-quibusdam-minus", + content: + "Quod numquam quam accusamus. Modi reiciendis omnis et aut accusantium perspiciatis et adipisci consequuntur. Similique et reiciendis asperiores sequi placeat laudantium voluptatem quasi ut. Quia qui laborum placeat totam atque explicabo qui. Tempore in nesciunt in omnis culpa magni animi cumque atque. In in a repudiandae rerum. Sit earum corrupti quos mollitia vel amet quia aut. Dolorum voluptatem voluptatem qui et. Quisquam omnis fuga est excepturi expedita placeat sed. Enim quia velit.", + hit: 47216, + category: { id: 1 }, + user: { id: 43 }, + status: "rejected", + createdAt: "2020-01-15T18:42:02.469Z", + image: [ + { + url: "http://placeimg.com/640/480", + name: "160a465a-2eca-4c62-9f66-e39802113cf8", + status: "done", + type: "image/jpeg", + uid: "5c8e2d38-c40e-4031-9a91-1c24afd1309d", + }, + ], + tags: [3], + language: 1, + }, + { + id: 617, + title: "Aperiam non et.", + slug: "perspiciatis-ea-maxime", + content: + "Accusantium et ea nihil sit. Quia minima qui esse repudiandae molestiae. Nam perspiciatis cumque et quis doloribus et et non. Aliquam minima a explicabo. Quod reprehenderit nobis sit dolorem. Molestias qui aspernatur quod expedita quas. Laudantium necessitatibus porro aliquam cupiditate aperiam. Recusandae natus alias deserunt dicta animi facilis. Cumque tempore iste eum ut id a quae minima molestiae. Consequuntur quia aut earum.", + hit: 512, + category: { id: 1 }, + user: { id: 47 }, + status: "published", + createdAt: "2021-06-13T12:52:08.459Z", + image: [ + { + url: "http://placeimg.com/640/480", + name: "00777f91-b587-4e57-b2a2-c9c9be5c1904", + status: "done", + type: "image/jpeg", + uid: "fa5cb4a7-99ff-41e1-a441-34f16136898e", + }, + ], + tags: [8], + language: 3, + }, + { + id: 622, + title: + "Maxime est autem cupiditate culpa voluptatem natus quidem doloremque.", + slug: "earum-voluptas-corporis", + content: + "Nulla voluptatem quo impedit voluptate quibusdam facilis et quasi dolorem. Officia in ratione ex libero rerum. Voluptas nobis et ullam veniam natus error. Et facere soluta voluptatem neque neque voluptatem voluptatum maiores. Repellendus error nostrum at expedita consectetur quae tempora ipsum. Placeat sed et ea porro vero. Eos accusantium assumenda aliquid rerum. Aut libero et voluptatem vero sint. Molestiae non amet et quis eaque. Sunt hic adipisci harum aut vel explicabo consectetur.", + hit: 913434, + category: { id: 1 }, + user: { id: 49 }, + status: "rejected", + createdAt: "2019-06-22T19:24:19.494Z", + image: [ + { + url: "http://placeimg.com/640/480", + name: "4fda0ad1-2c51-4e44-818a-35ae61e4e2b4", + status: "done", + type: "image/jpeg", + uid: "aea68791-7db6-497e-8cbb-087561263483", + }, + ], + tags: [9, 2], + language: 2, + }, + { + id: 644, + title: "Dolorem laboriosam et est modi rerum placeat iusto.", + slug: "vero-aut-neque", + content: + "Velit quo harum assumenda. Consectetur quidem mollitia ex inventore. Iste itaque similique quod velit facere quaerat. Nihil sint qui vel rerum libero. Maiores est quo. Voluptatem blanditiis sunt similique voluptatibus porro odit. In odit delectus perspiciatis. Consequatur omnis dolorem dignissimos consectetur quasi delectus dolores id. Omnis quia in. Sit voluptas esse non dolore quas molestias molestias est enim.", + hit: 272834, + category: { id: 1 }, + user: { id: 33 }, + status: "draft", + createdAt: "2019-08-15T12:27:39.919Z", + image: [ + { + url: "http://placeimg.com/640/480", + name: "86c5bd92-438c-4f4b-8b30-c4de9eeb9fd1", + status: "done", + type: "image/jpeg", + uid: "c70deb9d-bae1-41c6-b71c-ee352c2721ac", + }, + ], + tags: [2, 4], + language: 3, + }, + ], + [ + "Server", + "nginx/1.17.10", + "Date", + "Mon, 21 Jun 2021 11:24:47 GMT", + "Content-Type", + "application/json; charset=utf-8", + "Content-Length", + "10542", + "Connection", + "close", + "Vary", + "Accept-Encoding", + "X-Powered-By", + "Express", + "Vary", + "Origin, Accept-Encoding", + "Access-Control-Allow-Credentials", + "true", + "Cache-Control", + "no-cache", + "Pragma", + "no-cache", + "Expires", + "-1", + "Access-Control-Allow-Origin", + "*", + "X-Total-Count", + "17", + "Access-Control-Expose-Headers", + "X-Total-Count", + "X-Content-Type-Options", + "nosniff", + "ETag", + 'W/"292e-QyaIdJgeENcIUIgHmY1COHjk5N0"', + ], + ); + +nock("https://api.fake-rest.refine.dev:443", { encodedQueryParams: true }) + .get("/categories") + .reply( + 200, + [ + { id: 1, title: "Laptops & Desktops" }, + { id: 2, title: "Smartphones & Tablets" }, + { id: 3, title: "Audio & Video" }, + { id: 4, title: "Gaming" }, + { id: 5, title: "Home & Kitchen" }, + { id: 6, title: "Office & Supplies" }, + { id: 7, title: "Books & Magazines" }, + { id: 8, title: "Music & Instruments" }, + { id: 9, title: "Health & Fitness" }, + { id: 10, title: "Clothing & Accessories" }, + { id: 11, title: "Travel & Outdoor" }, + { id: 12, title: "Toys & Games" }, + { id: 13, title: "Beauty & Personal Care" }, + { id: 14, title: "Sports" }, + { id: 15, title: "Automotive & Industrial" }, + ], + [ + "Date", + "Wed, 24 Jan 2024 08:06:19 GMT", + "Content-Type", + "application/json; charset=utf-8", + "Content-Length", + "820", + "Connection", + "close", + "X-Powered-By", + "Express", + "Vary", + "Origin, Accept-Encoding", + "Access-Control-Allow-Credentials", + "true", + "Cache-Control", + "no-cache", + "Pragma", + "no-cache", + "Expires", + "-1", + "Access-Control-Allow-Origin", + "*", + "X-Content-Type-Options", + "nosniff", + "ETag", + 'W/"334-PMx7J5Aaf4zcErqTDmjqbkr7IAU"', + "Strict-Transport-Security", + "max-age=15724800; includeSubDomains", + ], + ); diff --git a/packages/rest/src/data-providers/simple-rest/specs/getList/index.spec.ts b/packages/rest/src/data-providers/simple-rest/specs/getList/index.spec.ts new file mode 100644 index 000000000000..f7d5cf19addf --- /dev/null +++ b/packages/rest/src/data-providers/simple-rest/specs/getList/index.spec.ts @@ -0,0 +1,72 @@ +import { simpleRestDataProvider } from ".."; +import "./index.mock"; + +describe("getList", () => { + it("correct response", async () => { + const response = await simpleRestDataProvider.getList({ + resource: "posts", + }); + + expect(response.data[0]["id"]).toBe(1); + expect(response.data[0]["title"]).toBe( + "Mollitia ipsam nisi in porro velit asperiores et quaerat dolorem.", + ); + expect(response.total).toBe(1000); + }); + + it("correct sorting response", async () => { + const response = await simpleRestDataProvider.getList({ + resource: "posts", + sorters: [ + { + field: "id", + order: "asc", + }, + ], + }); + + expect(response.data[0]["id"]).toBe(1); + expect(response.data[0]["title"]).toBe( + "Mollitia ipsam nisi in porro velit asperiores et quaerat dolorem.", + ); + expect(response.total).toBe(1000); + }); + + it("correct filter response", async () => { + const response = await simpleRestDataProvider.getList({ + resource: "posts", + filters: [ + { + field: "category.id", + operator: "eq", + value: "1", + }, + ], + }); + + expect(response.data[0]["category"]["id"]).toBe(1); + expect(response.total).toBe(17); + }); + + it("correct filter and sort response", async () => { + const response = await simpleRestDataProvider.getList({ + resource: "posts", + filters: [ + { + field: "category.id", + operator: "eq", + value: "1", + }, + ], + sorters: [ + { + field: "id", + order: "asc", + }, + ], + }); + + expect(response.data[0]["id"]).toBe(44); + expect(response.total).toBe(17); + }); +}); diff --git a/packages/rest/src/data-providers/simple-rest/specs/getOne/index.mock.ts b/packages/rest/src/data-providers/simple-rest/specs/getOne/index.mock.ts new file mode 100644 index 000000000000..7406cf542446 --- /dev/null +++ b/packages/rest/src/data-providers/simple-rest/specs/getOne/index.mock.ts @@ -0,0 +1,49 @@ +import nock from "nock"; + +nock("https://api.fake-rest.refine.dev:443", { encodedQueryParams: true }) + .get("/posts/1") + .reply( + 200, + { + id: 1, + title: "Deleniti et quasi architecto hic quam et tempora vero quo.", + slug: "nobis-aut-eligendi", + content: + "Accusantium sed nam odio ut non qui. Maxime quaerat sed ducimus corrupti consequatur. Facere numquam ut reprehenderit quaerat quia. Recusandae quibusdam asperiores atque architecto quod praesentium sit non. Aut neque repellat veniam veritatis qui et vel alias debitis. Amet eius omnis dolores. Sint sed magni. Dolor eius maiores asperiores et. Et modi illum eius quisquam maxime at vel qui. Sit dolore officiis aliquid quia labore.", + categoryId: 20, + status: "active", + userId: 16, + tags: [15, 36, 46], + image: [], + }, + [ + "Server", + "nginx/1.17.10", + "Date", + "Tue, 30 Mar 2021 12:00:24 GMT", + "Content-Type", + "application/json; charset=utf-8", + "Content-Length", + "679", + "Connection", + "close", + "Vary", + "Accept-Encoding", + "X-Powered-By", + "Express", + "Vary", + "Origin, Accept-Encoding", + "Access-Control-Allow-Credentials", + "true", + "Cache-Control", + "no-cache", + "Pragma", + "no-cache", + "Expires", + "-1", + "X-Content-Type-Options", + "nosniff", + "ETag", + 'W/"2a7-a0rGaWRcFw0EdW6V+OnQR4/JLnk"', + ], + ); diff --git a/packages/rest/src/data-providers/simple-rest/specs/getOne/index.spec.ts b/packages/rest/src/data-providers/simple-rest/specs/getOne/index.spec.ts new file mode 100644 index 000000000000..07d49032d67d --- /dev/null +++ b/packages/rest/src/data-providers/simple-rest/specs/getOne/index.spec.ts @@ -0,0 +1,18 @@ +import { simpleRestDataProvider } from ".."; +import "./index.mock"; + +describe("getOne", () => { + it("correct response", async () => { + const response = await simpleRestDataProvider.getOne({ + resource: "posts", + id: "1", + }); + + const { data } = response; + + expect(data.id).toBe(1); + expect(data.title).toBe( + "Deleniti et quasi architecto hic quam et tempora vero quo.", + ); + }); +}); diff --git a/packages/rest/src/data-providers/simple-rest/specs/index.ts b/packages/rest/src/data-providers/simple-rest/specs/index.ts new file mode 100644 index 000000000000..5491e1b8a3c5 --- /dev/null +++ b/packages/rest/src/data-providers/simple-rest/specs/index.ts @@ -0,0 +1,6 @@ +import { createSimpleRestDataProvider } from ".."; + +export const { dataProvider: simpleRestDataProvider } = + createSimpleRestDataProvider({ + apiURL: "https://api.fake-rest.refine.dev", + }); diff --git a/packages/rest/src/data-providers/simple-rest/specs/update/index.mock.ts b/packages/rest/src/data-providers/simple-rest/specs/update/index.mock.ts new file mode 100644 index 000000000000..08349d02c3be --- /dev/null +++ b/packages/rest/src/data-providers/simple-rest/specs/update/index.mock.ts @@ -0,0 +1,32 @@ +import nock from "nock"; + +nock("https://api.fake-rest.refine.dev:443", { encodedQueryParams: true }) + .patch("/posts/1000", { id: 1001, title: "foo", content: "bar" }) + .reply(200, { id: 1000, title: "foo", content: "bar" }, [ + "Server", + "nginx/1.17.10", + "Date", + "Tue, 30 Mar 2021 11:36:34 GMT", + "Content-Type", + "application/json; charset=utf-8", + "Content-Length", + "54", + "Connection", + "close", + "X-Powered-By", + "Express", + "Vary", + "Origin, Accept-Encoding", + "Access-Control-Allow-Credentials", + "true", + "Cache-Control", + "no-cache", + "Pragma", + "no-cache", + "Expires", + "-1", + "X-Content-Type-Options", + "nosniff", + "ETag", + 'W/"36-Um2MA2uF6w7rTIWkUlx1wVaEBWQ"', + ]); diff --git a/packages/rest/src/data-providers/simple-rest/specs/update/index.spec.ts b/packages/rest/src/data-providers/simple-rest/specs/update/index.spec.ts new file mode 100644 index 000000000000..5aa0594cdef1 --- /dev/null +++ b/packages/rest/src/data-providers/simple-rest/specs/update/index.spec.ts @@ -0,0 +1,23 @@ +import "./index.mock"; + +import { simpleRestDataProvider } from ".."; + +describe("update", () => { + it("correct response", async () => { + const response = await simpleRestDataProvider.update({ + resource: "posts", + id: "1000", + variables: { + id: 1001, + title: "foo", + content: "bar", + }, + }); + + const { data } = response; + + expect(data["id"]).toBe(1000); + expect(data["title"]).toBe("foo"); + expect(data["content"]).toBe("bar"); + }); +}); diff --git a/packages/rest/src/data-providers/strapi-v4/index.ts b/packages/rest/src/data-providers/strapi-v4/index.ts new file mode 100644 index 000000000000..9715244535f6 --- /dev/null +++ b/packages/rest/src/data-providers/strapi-v4/index.ts @@ -0,0 +1,19 @@ +import type { Options as KyOptions } from "ky"; + +import { createDataProvider } from "../../create-data-provider.js"; +import { strapiV4DataProviderOptions } from "./strapi-v4.options.js"; + +type CreateStrapiV4DataProviderParams = { + apiURL: string; + kyOptions?: KyOptions; +}; + +const createStrapiV4DataProvider = ( + params: CreateStrapiV4DataProviderParams, +) => { + const { apiURL, kyOptions } = params; + + return createDataProvider(apiURL, strapiV4DataProviderOptions, kyOptions); +}; + +export { createStrapiV4DataProvider }; diff --git a/packages/rest/src/data-providers/strapi-v4/specs/index.mock.ts b/packages/rest/src/data-providers/strapi-v4/specs/index.mock.ts new file mode 100644 index 000000000000..b6259ea5c7f5 --- /dev/null +++ b/packages/rest/src/data-providers/strapi-v4/specs/index.mock.ts @@ -0,0 +1,1604 @@ +import nock from "nock"; + +// create +nock("http://localhost:1337", { encodedQueryParams: true }) + .post("/api/posts", { + data: { title: "foo", content: "bar", cover: ["32"] }, + }) + .reply( + 200, + { + data: { + id: 20, + attributes: { + title: "foo", + content: "bar", + createdAt: "2021-12-14T11:45:49.631Z", + updatedAt: "2021-12-14T11:45:49.631Z", + publishedAt: "2021-12-14T11:45:49.621Z", + locale: "en", + }, + }, + meta: {}, + }, + [ + "Content-Security-Policy", + "connect-src 'self' https:;img-src 'self' data: blob:;media-src 'self' data: blob:;default-src 'self';base-uri 'self';block-all-mixed-content;font-src 'self' https: data:;frame-ancestors 'self';object-src 'none';script-src 'self';script-src-attr 'none';style-src 'self' https: 'unsafe-inline'", + "X-DNS-Prefetch-Control", + "off", + "Expect-CT", + "max-age=0", + "X-Frame-Options", + "SAMEORIGIN", + "Strict-Transport-Security", + "max-age=31536000; includeSubDomains", + "X-Download-Options", + "noopen", + "X-Content-Type-Options", + "nosniff", + "X-Permitted-Cross-Domain-Policies", + "none", + "Referrer-Policy", + "no-referrer", + "Vary", + "Origin", + "Content-Type", + "application/json; charset=utf-8", + "X-Powered-By", + "Strapi ", + "Content-Length", + "206", + "Date", + "Tue, 14 Dec 2021 11:45:49 GMT", + "Connection", + "close", + ], + ); + +// deleteOne +nock("http://localhost:1337", { encodedQueryParams: true }) + .delete("/api/posts/18") + .reply( + 200, + { + data: { + id: 18, + attributes: { + title: "foo", + content: "bar", + createdAt: "2021-12-14T11:43:40.712Z", + updatedAt: "2021-12-14T11:43:40.712Z", + publishedAt: "2021-12-14T11:43:40.704Z", + locale: "en", + }, + }, + meta: {}, + }, + [ + "Content-Security-Policy", + "connect-src 'self' https:;img-src 'self' data: blob:;media-src 'self' data: blob:;default-src 'self';base-uri 'self';block-all-mixed-content;font-src 'self' https: data:;frame-ancestors 'self';object-src 'none';script-src 'self';script-src-attr 'none';style-src 'self' https: 'unsafe-inline'", + "X-DNS-Prefetch-Control", + "off", + "Expect-CT", + "max-age=0", + "X-Frame-Options", + "SAMEORIGIN", + "Strict-Transport-Security", + "max-age=31536000; includeSubDomains", + "X-Download-Options", + "noopen", + "X-Content-Type-Options", + "nosniff", + "X-Permitted-Cross-Domain-Policies", + "none", + "Referrer-Policy", + "no-referrer", + "Vary", + "Origin", + "Content-Type", + "application/json; charset=utf-8", + "X-Powered-By", + "Strapi ", + "Content-Length", + "206", + "Date", + "Tue, 14 Dec 2021 11:58:06 GMT", + "Connection", + "close", + ], + ); + +// getList +nock("http://localhost:1337", { encodedQueryParams: true }) + .get("/api/posts") + .query({ "pagination%5Bpage%5D": "1", "pagination%5BpageSize%5D": "10" }) + .reply( + 200, + { + data: [ + { + id: 5, + attributes: { + title: "Lorem ipsum began as scrambled", + content: + "Lorem ipsum, or lipsum as it is sometimes known, is dummy text used in laying out print, graphic or web designs. The passage is attributed to an unknown typesetter in the 15th century who is thought to have scrambled parts of Cicero's De Finibus Bonorum et Malorum for use in a type specimen book. It usually begins with:\n\n\n_**“Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.”**_\n\n\nThe purpose of lorem ipsum is to create a natural looking block of text (sentence, paragraph, page, etc.) that doesn't distract from the layout. A practice not without controversy, laying out pages with meaningless filler text can be very useful when the focus is meant to be on design, not content.\n\nThe passage experienced a surge in popularity during the 1960s when Letraset used it on their dry-transfer sheets, and again during the 90s as desktop publishers bundled the text with their software. Today it's seen all around the web; on templates, websites, and stock designs. Use our generator to get your own, or read on for the authoritative history of lorem ipsum.", + createdAt: "2021-12-09T08:53:47.226Z", + updatedAt: "2021-12-13T11:51:43.224Z", + publishedAt: "2021-12-09T13:45:40.088Z", + locale: "en", + }, + }, + { + id: 3, + attributes: { + title: "Cras iaculis ultricies nulla", + content: + "**Morbi in sem quis dui placerat ornare**. Pellentesque odio nisi, euismod in, pharetra a, ultricies in, diam. Sed arcu. Cras consequat.\n\nPraesent dapibus, neque id cursus faucibus, tortor neque egestas auguae, eu vulputate magna eros eu erat. Aliquam erat volutpat. Nam dui mi, tincidunt quis, accumsan porttitor, facilisis luctus, metus.", + createdAt: "2021-12-08T14:36:57.102Z", + updatedAt: "2021-12-08T14:37:47.584Z", + publishedAt: "2021-12-08T14:37:02.174Z", + locale: "en", + }, + }, + { + id: 17, + attributes: { + title: "foo", + content: "bar", + createdAt: "2021-12-14T11:42:37.945Z", + updatedAt: "2021-12-14T11:42:37.945Z", + publishedAt: "2021-12-14T11:42:37.914Z", + locale: "en", + }, + }, + { + id: 19, + attributes: { + title: "foo", + content: "bar", + createdAt: "2021-12-14T11:45:30.710Z", + updatedAt: "2021-12-14T11:45:30.710Z", + publishedAt: "2021-12-14T11:45:30.700Z", + locale: "en", + }, + }, + { + id: 21, + attributes: { + title: "foo", + content: "bar", + createdAt: "2021-12-14T11:47:01.493Z", + updatedAt: "2021-12-14T11:47:01.493Z", + publishedAt: "2021-12-14T11:47:01.482Z", + locale: "en", + }, + }, + { + id: 8, + attributes: { + title: "Hello", + content: "New post content", + createdAt: "2021-12-10T13:10:58.478Z", + updatedAt: "2021-12-13T13:56:11.613Z", + publishedAt: "2021-12-10T13:10:58.328Z", + locale: "en", + }, + }, + ], + meta: { + pagination: { page: 1, pageSize: 10, pageCount: 1, total: 6 }, + }, + }, + [ + "Content-Security-Policy", + "connect-src 'self' https:;img-src 'self' data: blob:;media-src 'self' data: blob:;default-src 'self';base-uri 'self';block-all-mixed-content;font-src 'self' https: data:;frame-ancestors 'self';object-src 'none';script-src 'self';script-src-attr 'none';style-src 'self' https: 'unsafe-inline'", + "X-DNS-Prefetch-Control", + "off", + "Expect-CT", + "max-age=0", + "X-Frame-Options", + "SAMEORIGIN", + "Strict-Transport-Security", + "max-age=31536000; includeSubDomains", + "X-Download-Options", + "noopen", + "X-Content-Type-Options", + "nosniff", + "X-Permitted-Cross-Domain-Policies", + "none", + "Referrer-Policy", + "no-referrer", + "Vary", + "Origin", + "Content-Type", + "application/json; charset=utf-8", + "X-Powered-By", + "Strapi ", + "Content-Length", + "2749", + "Date", + "Tue, 14 Dec 2021 12:00:07 GMT", + "Connection", + "close", + ], + ); + +nock("http://localhost:1337", { encodedQueryParams: true }) + .get("/api/posts") + .query({ + "pagination%5Bpage%5D": "1", + "pagination%5BpageSize%5D": "10", + sort: "id%3Adesc", + }) + .reply( + 200, + { + data: [ + { + id: 21, + attributes: { + title: "foo", + content: "bar", + createdAt: "2021-12-14T11:47:01.493Z", + updatedAt: "2021-12-14T11:47:01.493Z", + publishedAt: "2021-12-14T11:47:01.482Z", + locale: "en", + }, + }, + { + id: 19, + attributes: { + title: "foo", + content: "bar", + createdAt: "2021-12-14T11:45:30.710Z", + updatedAt: "2021-12-14T11:45:30.710Z", + publishedAt: "2021-12-14T11:45:30.700Z", + locale: "en", + }, + }, + { + id: 17, + attributes: { + title: "foo", + content: "bar", + createdAt: "2021-12-14T11:42:37.945Z", + updatedAt: "2021-12-14T11:42:37.945Z", + publishedAt: "2021-12-14T11:42:37.914Z", + locale: "en", + }, + }, + { + id: 8, + attributes: { + title: "Hello", + content: "New post content", + createdAt: "2021-12-10T13:10:58.478Z", + updatedAt: "2021-12-13T13:56:11.613Z", + publishedAt: "2021-12-10T13:10:58.328Z", + locale: "en", + }, + }, + { + id: 5, + attributes: { + title: "Lorem ipsum began as scrambled", + content: + "Lorem ipsum, or lipsum as it is sometimes known, is dummy text used in laying out print, graphic or web designs. The passage is attributed to an unknown typesetter in the 15th century who is thought to have scrambled parts of Cicero's De Finibus Bonorum et Malorum for use in a type specimen book. It usually begins with:\n\n\n_**“Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.”**_\n\n\nThe purpose of lorem ipsum is to create a natural looking block of text (sentence, paragraph, page, etc.) that doesn't distract from the layout. A practice not without controversy, laying out pages with meaningless filler text can be very useful when the focus is meant to be on design, not content.\n\nThe passage experienced a surge in popularity during the 1960s when Letraset used it on their dry-transfer sheets, and again during the 90s as desktop publishers bundled the text with their software. Today it's seen all around the web; on templates, websites, and stock designs. Use our generator to get your own, or read on for the authoritative history of lorem ipsum.", + createdAt: "2021-12-09T08:53:47.226Z", + updatedAt: "2021-12-13T11:51:43.224Z", + publishedAt: "2021-12-09T13:45:40.088Z", + locale: "en", + }, + }, + { + id: 3, + attributes: { + title: "Cras iaculis ultricies nulla", + content: + "**Morbi in sem quis dui placerat ornare**. Pellentesque odio nisi, euismod in, pharetra a, ultricies in, diam. Sed arcu. Cras consequat.\n\nPraesent dapibus, neque id cursus faucibus, tortor neque egestas auguae, eu vulputate magna eros eu erat. Aliquam erat volutpat. Nam dui mi, tincidunt quis, accumsan porttitor, facilisis luctus, metus.", + createdAt: "2021-12-08T14:36:57.102Z", + updatedAt: "2021-12-08T14:37:47.584Z", + publishedAt: "2021-12-08T14:37:02.174Z", + locale: "en", + }, + }, + ], + meta: { + pagination: { page: 1, pageSize: 10, pageCount: 1, total: 6 }, + }, + }, + [ + "Content-Security-Policy", + "connect-src 'self' https:;img-src 'self' data: blob:;media-src 'self' data: blob:;default-src 'self';base-uri 'self';block-all-mixed-content;font-src 'self' https: data:;frame-ancestors 'self';object-src 'none';script-src 'self';script-src-attr 'none';style-src 'self' https: 'unsafe-inline'", + "X-DNS-Prefetch-Control", + "off", + "Expect-CT", + "max-age=0", + "X-Frame-Options", + "SAMEORIGIN", + "Strict-Transport-Security", + "max-age=31536000; includeSubDomains", + "X-Download-Options", + "noopen", + "X-Content-Type-Options", + "nosniff", + "X-Permitted-Cross-Domain-Policies", + "none", + "Referrer-Policy", + "no-referrer", + "Vary", + "Origin", + "Content-Type", + "application/json; charset=utf-8", + "X-Powered-By", + "Strapi ", + "Content-Length", + "2749", + "Date", + "Tue, 14 Dec 2021 12:22:42 GMT", + "Connection", + "close", + ], + ); + +nock("http://localhost:1337", { encodedQueryParams: true }) + .get("/api/posts") + .query({ + "pagination%5Bpage%5D": "1", + "pagination%5BpageSize%5D": "10", + "filters%5Btitle%5D%5B%24eq%5D": "foo", + }) + .reply( + 200, + { + data: [ + { + id: 17, + attributes: { + title: "foo", + content: "bar", + createdAt: "2021-12-14T11:42:37.945Z", + updatedAt: "2021-12-14T11:42:37.945Z", + publishedAt: "2021-12-14T11:42:37.914Z", + locale: "en", + }, + }, + { + id: 19, + attributes: { + title: "foo", + content: "bar", + createdAt: "2021-12-14T11:45:30.710Z", + updatedAt: "2021-12-14T11:45:30.710Z", + publishedAt: "2021-12-14T11:45:30.700Z", + locale: "en", + }, + }, + { + id: 21, + attributes: { + title: "foo", + content: "bar", + createdAt: "2021-12-14T11:47:01.493Z", + updatedAt: "2021-12-14T11:47:01.493Z", + publishedAt: "2021-12-14T11:47:01.482Z", + locale: "en", + }, + }, + ], + meta: { + pagination: { page: 1, pageSize: 10, pageCount: 1, total: 3 }, + }, + }, + [ + "Content-Security-Policy", + "connect-src 'self' https:;img-src 'self' data: blob:;media-src 'self' data: blob:;default-src 'self';base-uri 'self';block-all-mixed-content;font-src 'self' https: data:;frame-ancestors 'self';object-src 'none';script-src 'self';script-src-attr 'none';style-src 'self' https: 'unsafe-inline'", + "X-DNS-Prefetch-Control", + "off", + "Expect-CT", + "max-age=0", + "X-Frame-Options", + "SAMEORIGIN", + "Strict-Transport-Security", + "max-age=31536000; includeSubDomains", + "X-Download-Options", + "noopen", + "X-Content-Type-Options", + "nosniff", + "X-Permitted-Cross-Domain-Policies", + "none", + "Referrer-Policy", + "no-referrer", + "Vary", + "Origin", + "Content-Type", + "application/json; charset=utf-8", + "X-Powered-By", + "Strapi ", + "Content-Length", + "645", + "Date", + "Tue, 14 Dec 2021 12:39:33 GMT", + "Connection", + "close", + ], + ); + +nock("http://localhost:1337", { encodedQueryParams: true }) + .get("/api/posts") + .query({ + "pagination%5Bpage%5D": "1", + "pagination%5BpageSize%5D": "10", + sort: "id%3Adesc", + "filters%5Btitle%5D%5B%24eq%5D": "foo", + }) + .reply( + 200, + { + data: [ + { + id: 21, + attributes: { + title: "foo", + content: "bar", + createdAt: "2021-12-14T11:47:01.493Z", + updatedAt: "2021-12-14T11:47:01.493Z", + publishedAt: "2021-12-14T11:47:01.482Z", + locale: "en", + }, + }, + { + id: 19, + attributes: { + title: "foo", + content: "bar", + createdAt: "2021-12-14T11:45:30.710Z", + updatedAt: "2021-12-14T11:45:30.710Z", + publishedAt: "2021-12-14T11:45:30.700Z", + locale: "en", + }, + }, + { + id: 17, + attributes: { + title: "foo", + content: "bar", + createdAt: "2021-12-14T11:42:37.945Z", + updatedAt: "2021-12-14T11:42:37.945Z", + publishedAt: "2021-12-14T11:42:37.914Z", + locale: "en", + }, + }, + ], + meta: { + pagination: { page: 1, pageSize: 10, pageCount: 1, total: 3 }, + }, + }, + [ + "Content-Security-Policy", + "connect-src 'self' https:;img-src 'self' data: blob:;media-src 'self' data: blob:;default-src 'self';base-uri 'self';block-all-mixed-content;font-src 'self' https: data:;frame-ancestors 'self';object-src 'none';script-src 'self';script-src-attr 'none';style-src 'self' https: 'unsafe-inline'", + "X-DNS-Prefetch-Control", + "off", + "Expect-CT", + "max-age=0", + "X-Frame-Options", + "SAMEORIGIN", + "Strict-Transport-Security", + "max-age=31536000; includeSubDomains", + "X-Download-Options", + "noopen", + "X-Content-Type-Options", + "nosniff", + "X-Permitted-Cross-Domain-Policies", + "none", + "Referrer-Policy", + "no-referrer", + "Vary", + "Origin", + "Content-Type", + "application/json; charset=utf-8", + "X-Powered-By", + "Strapi ", + "Content-Length", + "645", + "Date", + "Tue, 14 Dec 2021 12:40:28 GMT", + "Connection", + "close", + ], + ); + +nock("http://localhost:1337", { encodedQueryParams: true }) + .get("/api/posts") + .query({ + "pagination%5Bpage%5D": "1", + "pagination%5BpageSize%5D": "10", + locale: "de", + }) + .reply( + 200, + { + data: [ + { + id: 15, + attributes: { + title: "Hello", + content: "Hello", + createdAt: "2021-12-13T13:43:37.061Z", + updatedAt: "2021-12-13T13:43:37.061Z", + publishedAt: "2021-12-13T13:43:37.044Z", + locale: "de", + }, + }, + { + id: 4, + attributes: { + title: "Exerci Hockenheim eu per", + content: + "**Fernweh sadipscing per at**, Faust mei ullum gloriatur. Fußballweltmeisterschaft inermis recteque accommodare Frau Professor Id nec assum Welt te melius erroribus Polizei Nec ut amet Ritter Sport iriure, prodesset gloriatur Bahnhof ut. Dicunt virtute schnell per no. At Fußball scaevola eum. An Gesundheit malorum efficiendi ius\n\nmeliore Lebkuchen et mel. Te Mertesacker utamur vix. Exerci Hockenheim eu per. Principes Bratwurst eos no. His Rubin auf Schienen moderatius ut, at Frohsinn omnis minim epicurei, was machst du feugait mel ei. Bier purto singulis te", + createdAt: "2021-12-08T14:37:47.408Z", + updatedAt: "2021-12-08T14:37:48.604Z", + publishedAt: "2021-12-08T14:37:48.599Z", + locale: "de", + }, + }, + { + id: 2, + attributes: { + title: "Te oratio Schneewittchen vix.", + content: + "**Sit amet**, Zauberer adipiscing elit, sed Joachim Löw eiusmod tempor incididunt Mertesacker labore et dolore Frau Professor aliqua. Ut enim Siebentausendzweihundertvierundfünfzig minim veniam, quis Currywurst exercitation ullamco laboris Honigkuchenpferd ut aliquip ex Reise commodo consequat. Duis Nackenheim irure dolor in Heisenberg in voluptate velit Schmetterling cillum dolore eu Frohsinn nulla pariatur. Excepteur Projektplanung occaecat cupidatat non Milchreis sunt in culpa Schwarzwälder Kirschtorte officia deserunt mollit Schwarzwälder Kirschtorte id est laborum", + createdAt: "2021-12-08T14:35:24.455Z", + updatedAt: "2021-12-13T12:18:13.439Z", + publishedAt: "2021-12-08T14:35:26.233Z", + locale: "de", + }, + }, + ], + meta: { + pagination: { page: 1, pageSize: 10, pageCount: 1, total: 3 }, + }, + }, + [ + "Content-Security-Policy", + "connect-src 'self' https:;img-src 'self' data: blob:;media-src 'self' data: blob:;default-src 'self';base-uri 'self';block-all-mixed-content;font-src 'self' https: data:;frame-ancestors 'self';object-src 'none';script-src 'self';script-src-attr 'none';style-src 'self' https: 'unsafe-inline'", + "X-DNS-Prefetch-Control", + "off", + "Expect-CT", + "max-age=0", + "X-Frame-Options", + "SAMEORIGIN", + "Strict-Transport-Security", + "max-age=31536000; includeSubDomains", + "X-Download-Options", + "noopen", + "X-Content-Type-Options", + "nosniff", + "X-Permitted-Cross-Domain-Policies", + "none", + "Referrer-Policy", + "no-referrer", + "Vary", + "Origin", + "Content-Type", + "application/json; charset=utf-8", + "X-Powered-By", + "Strapi ", + "Content-Length", + "1834", + "Date", + "Wed, 15 Dec 2021 08:53:01 GMT", + "Connection", + "close", + ], + ); + +nock("http://localhost:1337", { encodedQueryParams: true }) + .get("/api/posts") + .query({ + "pagination%5Bpage%5D": "1", + "pagination%5BpageSize%5D": "10", + "fields%5B0%5D": "title", + "fields%5B1%5D": "content", + }) + .reply( + 200, + { + data: [ + { + id: 5, + attributes: { + title: "Lorem ipsum began as scrambled", + content: + "Lorem ipsum, or lipsum as it is sometimes known, is dummy text used in laying out print, graphic or web designs. The passage is attributed to an unknown typesetter in the 15th century who is thought to have scrambled parts of Cicero's De Finibus Bonorum et Malorum for use in a type specimen book. It usually begins with:\n\n\n_**“Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.”**_\n\n\nThe purpose of lorem ipsum is to create a natural looking block of text (sentence, paragraph, page, etc.) that doesn't distract from the layout. A practice not without controversy, laying out pages with meaningless filler text can be very useful when the focus is meant to be on design, not content.\n\nThe passage experienced a surge in popularity during the 1960s when Letraset used it on their dry-transfer sheets, and again during the 90s as desktop publishers bundled the text with their software. Today it's seen all around the web; on templates, websites, and stock designs. Use our generator to get your own, or read on for the authoritative history of lorem ipsum.", + }, + }, + { + id: 3, + attributes: { + title: "Cras iaculis ultricies nulla", + content: + "**Morbi in sem quis dui placerat ornare**. Pellentesque odio nisi, euismod in, pharetra a, ultricies in, diam. Sed arcu. Cras consequat.\n\nPraesent dapibus, neque id cursus faucibus, tortor neque egestas auguae, eu vulputate magna eros eu erat. Aliquam erat volutpat. Nam dui mi, tincidunt quis, accumsan porttitor, facilisis luctus, metus.", + }, + }, + { + id: 37, + attributes: { + title: "New Post Two", + content: "New Content Two", + }, + }, + { id: 39, attributes: { title: "test", content: "test" } }, + ], + meta: { + pagination: { page: 1, pageSize: 10, pageCount: 1, total: 4 }, + }, + }, + [ + "Content-Security-Policy", + "connect-src 'self' https:;img-src 'self' data: blob:;media-src 'self' data: blob:;default-src 'self';base-uri 'self';block-all-mixed-content;font-src 'self' https: data:;frame-ancestors 'self';object-src 'none';script-src 'self';script-src-attr 'none';style-src 'self' https: 'unsafe-inline'", + "X-DNS-Prefetch-Control", + "off", + "Expect-CT", + "max-age=0", + "X-Frame-Options", + "SAMEORIGIN", + "Strict-Transport-Security", + "max-age=31536000; includeSubDomains", + "X-Download-Options", + "noopen", + "X-Content-Type-Options", + "nosniff", + "X-Permitted-Cross-Domain-Policies", + "none", + "Referrer-Policy", + "no-referrer", + "Vary", + "Origin", + "Content-Type", + "application/json; charset=utf-8", + "X-Powered-By", + "Strapi ", + "Content-Length", + "1850", + "Date", + "Wed, 15 Dec 2021 09:07:17 GMT", + "Connection", + "close", + ], + ); + +nock("http://localhost:1337", { encodedQueryParams: true }) + .get("/api/posts") + .query({ + "pagination%5Bpage%5D": "1", + "pagination%5BpageSize%5D": "10", + "populate%5B0%5D": "category", + }) + .reply( + 200, + { + data: [ + { + id: 5, + attributes: { + title: "Lorem ipsum began as scrambled", + content: + "Lorem ipsum, or lipsum as it is sometimes known, is dummy text used in laying out print, graphic or web designs. The passage is attributed to an unknown typesetter in the 15th century who is thought to have scrambled parts of Cicero's De Finibus Bonorum et Malorum for use in a type specimen book. It usually begins with:\n\n\n_**“Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.”**_\n\n\nThe purpose of lorem ipsum is to create a natural looking block of text (sentence, paragraph, page, etc.) that doesn't distract from the layout. A practice not without controversy, laying out pages with meaningless filler text can be very useful when the focus is meant to be on design, not content.\n\nThe passage experienced a surge in popularity during the 1960s when Letraset used it on their dry-transfer sheets, and again during the 90s as desktop publishers bundled the text with their software. Today it's seen all around the web; on templates, websites, and stock designs. Use our generator to get your own, or read on for the authoritative history of lorem ipsum.", + createdAt: "2021-12-09T08:53:47.226Z", + updatedAt: "2021-12-13T11:51:43.224Z", + publishedAt: "2021-12-09T13:45:40.088Z", + locale: "en", + category: { + data: { + id: 1, + attributes: { + title: "Vestibulum auctor", + createdAt: "2021-12-08T14:32:02.871Z", + updatedAt: "2021-12-08T14:34:14.289Z", + publishedAt: "2021-12-08T14:32:04.465Z", + locale: "en", + }, + }, + }, + }, + }, + { + id: 3, + attributes: { + title: "Cras iaculis ultricies nulla", + content: + "**Morbi in sem quis dui placerat ornare**. Pellentesque odio nisi, euismod in, pharetra a, ultricies in, diam. Sed arcu. Cras consequat.\n\nPraesent dapibus, neque id cursus faucibus, tortor neque egestas auguae, eu vulputate magna eros eu erat. Aliquam erat volutpat. Nam dui mi, tincidunt quis, accumsan porttitor, facilisis luctus, metus.", + createdAt: "2021-12-08T14:36:57.102Z", + updatedAt: "2021-12-08T14:37:47.584Z", + publishedAt: "2021-12-08T14:37:02.174Z", + locale: "en", + category: { + data: { + id: 1, + attributes: { + title: "Vestibulum auctor", + createdAt: "2021-12-08T14:32:02.871Z", + updatedAt: "2021-12-08T14:34:14.289Z", + publishedAt: "2021-12-08T14:32:04.465Z", + locale: "en", + }, + }, + }, + }, + }, + { + id: 37, + attributes: { + title: "New Post Two", + content: "New Content Two", + createdAt: "2021-12-14T13:35:35.095Z", + updatedAt: "2021-12-15T08:07:42.272Z", + publishedAt: "2021-12-14T13:35:35.082Z", + locale: "en", + category: { + data: { + id: 1, + attributes: { + title: "Vestibulum auctor", + createdAt: "2021-12-08T14:32:02.871Z", + updatedAt: "2021-12-08T14:34:14.289Z", + publishedAt: "2021-12-08T14:32:04.465Z", + locale: "en", + }, + }, + }, + }, + }, + { + id: 39, + attributes: { + title: "test", + content: "test", + createdAt: "2021-12-15T08:28:20.103Z", + updatedAt: "2021-12-15T08:28:20.103Z", + publishedAt: "2021-12-15T08:28:20.085Z", + locale: "en", + category: { data: null }, + }, + }, + ], + meta: { + pagination: { page: 1, pageSize: 10, pageCount: 1, total: 4 }, + }, + }, + [ + "Content-Security-Policy", + "connect-src 'self' https:;img-src 'self' data: blob:;media-src 'self' data: blob:;default-src 'self';base-uri 'self';block-all-mixed-content;font-src 'self' https: data:;frame-ancestors 'self';object-src 'none';script-src 'self';script-src-attr 'none';style-src 'self' https: 'unsafe-inline'", + "X-DNS-Prefetch-Control", + "off", + "Expect-CT", + "max-age=0", + "X-Frame-Options", + "SAMEORIGIN", + "Strict-Transport-Security", + "max-age=31536000; includeSubDomains", + "X-Download-Options", + "noopen", + "X-Content-Type-Options", + "nosniff", + "X-Permitted-Cross-Domain-Policies", + "none", + "Referrer-Policy", + "no-referrer", + "Vary", + "Origin", + "Content-Type", + "application/json; charset=utf-8", + "X-Powered-By", + "Strapi ", + "Content-Length", + "3022", + "Date", + "Wed, 15 Dec 2021 09:15:48 GMT", + "Connection", + "close", + ], + ); + +//getMany +nock("http://localhost:1337", { encodedQueryParams: true }) + .get("/api/posts") + .query({ + "populate%5B0%5D": "category", + "filters%5Bid%5D%5B%24in%5D%5B0%5D": "30", + "filters%5Bid%5D%5B%24in%5D%5B1%5D": "29", + "pagination%5BpageSize%5D": "2", + }) + .reply( + 200, + { + data: [ + { + id: 29, + attributes: { + title: "Hello", + content: "Testtt ", + createdAt: "2022-05-31T09:26:26.195Z", + updatedAt: "2022-05-31T15:51:02.594Z", + publishedAt: "2022-05-31T09:26:26.179Z", + locale: "en", + category: { + data: { + id: 11, + attributes: { + title: "Test1ree", + createdAt: "2022-02-21T04:32:23.688Z", + updatedAt: "2022-03-29T15:12:29.279Z", + publishedAt: "2022-02-21T04:32:23.679Z", + locale: "en", + }, + }, + }, + }, + }, + { + id: 30, + attributes: { + title: "test", + content: "test", + createdAt: "2022-06-01T09:07:37.786Z", + updatedAt: "2022-06-01T09:07:37.786Z", + publishedAt: "2022-06-01T09:07:37.776Z", + locale: "en", + category: { + data: { + id: 12, + attributes: { + title: "sdasd", + createdAt: "2022-03-08T06:50:11.851Z", + updatedAt: "2022-03-08T06:50:11.851Z", + publishedAt: "2022-03-08T06:50:11.834Z", + locale: "en", + }, + }, + }, + }, + }, + ], + meta: { + pagination: { page: 1, pageSize: 25, pageCount: 1, total: 2 }, + }, + }, + [ + "Date", + "Wed, 15 Jun 2022 07:09:12 GMT", + "Content-Type", + "application/json; charset=utf-8", + "Content-Length", + "856", + "Connection", + "close", + "Content-Security-Policy", + "connect-src 'self' https:;img-src 'self' data: blob:;media-src 'self' data: blob:;default-src 'self';base-uri 'self';block-all-mixed-content;font-src 'self' https: data:;frame-ancestors 'self';object-src 'none';script-src 'self';script-src-attr 'none';style-src 'self' https: 'unsafe-inline'", + "X-DNS-Prefetch-Control", + "off", + "Expect-CT", + "max-age=0", + "X-Frame-Options", + "SAMEORIGIN", + "Strict-Transport-Security", + "max-age=15724800; includeSubDomains", + "X-Download-Options", + "noopen", + "X-Content-Type-Options", + "nosniff", + "X-Permitted-Cross-Domain-Policies", + "none", + "Referrer-Policy", + "no-referrer", + "Vary", + "Origin", + "X-Powered-By", + "Strapi ", + ], + ); +//getOne +nock("http://localhost:1337", { encodedQueryParams: true }) + .get("/api/posts/8") + .query({}) + .reply( + 200, + { + data: { + id: 8, + attributes: { + title: "Hello", + content: "New post content", + createdAt: "2021-12-10T13:10:58.478Z", + updatedAt: "2021-12-13T13:56:11.613Z", + publishedAt: "2021-12-10T13:10:58.328Z", + locale: "en", + }, + }, + meta: {}, + }, + [ + "Content-Security-Policy", + "connect-src 'self' https:;img-src 'self' data: blob:;media-src 'self' data: blob:;default-src 'self';base-uri 'self';block-all-mixed-content;font-src 'self' https: data:;frame-ancestors 'self';object-src 'none';script-src 'self';script-src-attr 'none';style-src 'self' https: 'unsafe-inline'", + "X-DNS-Prefetch-Control", + "off", + "Expect-CT", + "max-age=0", + "X-Frame-Options", + "SAMEORIGIN", + "Strict-Transport-Security", + "max-age=31536000; includeSubDomains", + "X-Download-Options", + "noopen", + "X-Content-Type-Options", + "nosniff", + "X-Permitted-Cross-Domain-Policies", + "none", + "Referrer-Policy", + "no-referrer", + "Vary", + "Origin", + "Content-Type", + "application/json; charset=utf-8", + "X-Powered-By", + "Strapi ", + "Content-Length", + "220", + "Date", + "Tue, 14 Dec 2021 12:43:33 GMT", + "Connection", + "close", + ], + ); + +// updateOne +nock("http://localhost:1337", { encodedQueryParams: true }) + .put("/api/posts/8", { data: { title: "Updated Title" } }) + .reply( + 200, + { + data: { + id: 8, + attributes: { + title: "Updated Title", + content: "New post content", + createdAt: "2021-12-10T13:10:58.478Z", + updatedAt: "2021-12-14T13:21:10.916Z", + publishedAt: "2021-12-10T13:10:58.328Z", + locale: "en", + }, + }, + meta: {}, + }, + [ + "Content-Security-Policy", + "connect-src 'self' https:;img-src 'self' data: blob:;media-src 'self' data: blob:;default-src 'self';base-uri 'self';block-all-mixed-content;font-src 'self' https: data:;frame-ancestors 'self';object-src 'none';script-src 'self';script-src-attr 'none';style-src 'self' https: 'unsafe-inline'", + "X-DNS-Prefetch-Control", + "off", + "Expect-CT", + "max-age=0", + "X-Frame-Options", + "SAMEORIGIN", + "Strict-Transport-Security", + "max-age=31536000; includeSubDomains", + "X-Download-Options", + "noopen", + "X-Content-Type-Options", + "nosniff", + "X-Permitted-Cross-Domain-Policies", + "none", + "Referrer-Policy", + "no-referrer", + "Vary", + "Origin", + "Content-Type", + "application/json; charset=utf-8", + "X-Powered-By", + "Strapi ", + "Content-Length", + "228", + "Date", + "Tue, 14 Dec 2021 13:21:10 GMT", + "Connection", + "close", + ], + ); + +nock("http://localhost:1337", { encodedQueryParams: true }) + .put("/api/posts/17", { data: { title: "Updated titles" } }) + .reply( + 200, + { + data: { + id: 17, + attributes: { + title: "Updated titles", + content: "bar", + createdAt: "2021-12-14T11:42:37.945Z", + updatedAt: "2021-12-14T13:27:30.270Z", + publishedAt: "2021-12-14T11:42:37.914Z", + locale: "en", + }, + }, + meta: {}, + }, + [ + "Content-Security-Policy", + "connect-src 'self' https:;img-src 'self' data: blob:;media-src 'self' data: blob:;default-src 'self';base-uri 'self';block-all-mixed-content;font-src 'self' https: data:;frame-ancestors 'self';object-src 'none';script-src 'self';script-src-attr 'none';style-src 'self' https: 'unsafe-inline'", + "X-DNS-Prefetch-Control", + "off", + "Expect-CT", + "max-age=0", + "X-Frame-Options", + "SAMEORIGIN", + "Strict-Transport-Security", + "max-age=31536000; includeSubDomains", + "X-Download-Options", + "noopen", + "X-Content-Type-Options", + "nosniff", + "X-Permitted-Cross-Domain-Policies", + "none", + "Referrer-Policy", + "no-referrer", + "Vary", + "Origin", + "Content-Type", + "application/json; charset=utf-8", + "X-Powered-By", + "Strapi ", + "Content-Length", + "217", + "Date", + "Tue, 14 Dec 2021 13:27:30 GMT", + "Connection", + "close", + ], + ); + +nock("http://localhost:1337", { encodedQueryParams: true }) + .post("/api/posts", { + data: { title: "New Post Two", content: "New Content Two" }, + }) + .reply( + 200, + { + data: { + id: 30, + attributes: { + title: "New Post Two", + content: "New Content Two", + createdAt: "2021-12-14T13:35:16.771Z", + updatedAt: "2021-12-14T13:35:16.771Z", + publishedAt: "2021-12-14T13:35:16.693Z", + locale: "en", + }, + }, + meta: {}, + }, + [ + "Content-Security-Policy", + "connect-src 'self' https:;img-src 'self' data: blob:;media-src 'self' data: blob:;default-src 'self';base-uri 'self';block-all-mixed-content;font-src 'self' https: data:;frame-ancestors 'self';object-src 'none';script-src 'self';script-src-attr 'none';style-src 'self' https: 'unsafe-inline'", + "X-DNS-Prefetch-Control", + "off", + "Expect-CT", + "max-age=0", + "X-Frame-Options", + "SAMEORIGIN", + "Strict-Transport-Security", + "max-age=31536000; includeSubDomains", + "X-Download-Options", + "noopen", + "X-Content-Type-Options", + "nosniff", + "X-Permitted-Cross-Domain-Policies", + "none", + "Referrer-Policy", + "no-referrer", + "Vary", + "Origin", + "Content-Type", + "application/json; charset=utf-8", + "X-Powered-By", + "Strapi ", + "Content-Length", + "227", + "Date", + "Tue, 14 Dec 2021 13:35:16 GMT", + "Connection", + "close", + ], + ); + +// custom +nock("http://localhost:1337", { encodedQueryParams: true }) + .get("/api/posts") + .query({}) + .reply( + 200, + { + data: [ + { + id: 5, + attributes: { + title: "Lorem ipsum began as scrambled", + content: + "Lorem ipsum, or lipsum as it is sometimes known, is dummy text used in laying out print, graphic or web designs. The passage is attributed to an unknown typesetter in the 15th century who is thought to have scrambled parts of Cicero's De Finibus Bonorum et Malorum for use in a type specimen book. It usually begins with:\n\n\n_**“Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.”**_\n\n\nThe purpose of lorem ipsum is to create a natural looking block of text (sentence, paragraph, page, etc.) that doesn't distract from the layout. A practice not without controversy, laying out pages with meaningless filler text can be very useful when the focus is meant to be on design, not content.\n\nThe passage experienced a surge in popularity during the 1960s when Letraset used it on their dry-transfer sheets, and again during the 90s as desktop publishers bundled the text with their software. Today it's seen all around the web; on templates, websites, and stock designs. Use our generator to get your own, or read on for the authoritative history of lorem ipsum.", + createdAt: "2021-12-09T08:53:47.226Z", + updatedAt: "2021-12-13T11:51:43.224Z", + publishedAt: "2021-12-09T13:45:40.088Z", + locale: "en", + }, + }, + { + id: 3, + attributes: { + title: "Cras iaculis ultricies nulla", + content: + "**Morbi in sem quis dui placerat ornare**. Pellentesque odio nisi, euismod in, pharetra a, ultricies in, diam. Sed arcu. Cras consequat.\n\nPraesent dapibus, neque id cursus faucibus, tortor neque egestas auguae, eu vulputate magna eros eu erat. Aliquam erat volutpat. Nam dui mi, tincidunt quis, accumsan porttitor, facilisis luctus, metus.", + createdAt: "2021-12-08T14:36:57.102Z", + updatedAt: "2021-12-08T14:37:47.584Z", + publishedAt: "2021-12-08T14:37:02.174Z", + locale: "en", + }, + }, + { + id: 34, + attributes: { + title: "New Post One", + content: "New Content One", + createdAt: "2021-12-14T13:35:32.659Z", + updatedAt: "2021-12-14T13:35:32.659Z", + publishedAt: "2021-12-14T13:35:32.644Z", + locale: "en", + }, + }, + { + id: 35, + attributes: { + title: "New Post Two", + content: "New Content Two", + createdAt: "2021-12-14T13:35:32.666Z", + updatedAt: "2021-12-14T13:35:32.666Z", + publishedAt: "2021-12-14T13:35:32.650Z", + locale: "en", + }, + }, + { + id: 19, + attributes: { + title: "foo", + content: "bar", + createdAt: "2021-12-14T11:45:30.710Z", + updatedAt: "2021-12-14T11:45:30.710Z", + publishedAt: "2021-12-14T11:45:30.700Z", + locale: "en", + }, + }, + { + id: 21, + attributes: { + title: "foo", + content: "bar", + createdAt: "2021-12-14T11:47:01.493Z", + updatedAt: "2021-12-14T11:47:01.493Z", + publishedAt: "2021-12-14T11:47:01.482Z", + locale: "en", + }, + }, + { + id: 36, + attributes: { + title: "New Post One", + content: "New Content One", + createdAt: "2021-12-14T13:35:35.091Z", + updatedAt: "2021-12-14T13:35:35.091Z", + publishedAt: "2021-12-14T13:35:35.080Z", + locale: "en", + }, + }, + { + id: 37, + attributes: { + title: "New Post Two", + content: "New Content Two", + createdAt: "2021-12-14T13:35:35.095Z", + updatedAt: "2021-12-14T13:35:35.095Z", + publishedAt: "2021-12-14T13:35:35.082Z", + locale: "en", + }, + }, + { + id: 17, + attributes: { + title: "Updated titles", + content: "bar", + createdAt: "2021-12-14T11:42:37.945Z", + updatedAt: "2021-12-14T13:28:23.705Z", + publishedAt: "2021-12-14T11:42:37.914Z", + locale: "en", + }, + }, + { + id: 8, + attributes: { + title: "Updated titles", + content: "New post content", + createdAt: "2021-12-10T13:10:58.478Z", + updatedAt: "2021-12-14T13:28:23.706Z", + publishedAt: "2021-12-10T13:10:58.328Z", + locale: "en", + }, + }, + { + id: 22, + attributes: { + title: "New Post Two", + content: "New Content Two", + createdAt: "2021-12-14T13:34:51.436Z", + updatedAt: "2021-12-14T13:34:51.436Z", + publishedAt: "2021-12-14T13:34:51.428Z", + locale: "en", + }, + }, + { + id: 23, + attributes: { + title: "New Post One", + content: "New Content One", + createdAt: "2021-12-14T13:34:51.444Z", + updatedAt: "2021-12-14T13:34:51.444Z", + publishedAt: "2021-12-14T13:34:51.430Z", + locale: "en", + }, + }, + { + id: 24, + attributes: { + title: "New Post Two", + content: "New Content Two", + createdAt: "2021-12-14T13:34:56.024Z", + updatedAt: "2021-12-14T13:34:56.024Z", + publishedAt: "2021-12-14T13:34:56.003Z", + locale: "en", + }, + }, + { + id: 25, + attributes: { + title: "New Post One", + content: "New Content One", + createdAt: "2021-12-14T13:34:56.026Z", + updatedAt: "2021-12-14T13:34:56.026Z", + publishedAt: "2021-12-14T13:34:56.013Z", + locale: "en", + }, + }, + { + id: 26, + attributes: { + title: "New Post Two", + content: "New Content Two", + createdAt: "2021-12-14T13:35:01.373Z", + updatedAt: "2021-12-14T13:35:01.373Z", + publishedAt: "2021-12-14T13:35:01.330Z", + locale: "en", + }, + }, + { + id: 27, + attributes: { + title: "New Post One", + content: "New Content One", + createdAt: "2021-12-14T13:35:01.382Z", + updatedAt: "2021-12-14T13:35:01.382Z", + publishedAt: "2021-12-14T13:35:01.332Z", + locale: "en", + }, + }, + { + id: 28, + attributes: { + title: "New Post One", + content: "New Content One", + createdAt: "2021-12-14T13:35:03.957Z", + updatedAt: "2021-12-14T13:35:03.957Z", + publishedAt: "2021-12-14T13:35:03.945Z", + locale: "en", + }, + }, + { + id: 29, + attributes: { + title: "New Post Two", + content: "New Content Two", + createdAt: "2021-12-14T13:35:03.963Z", + updatedAt: "2021-12-14T13:35:03.963Z", + publishedAt: "2021-12-14T13:35:03.941Z", + locale: "en", + }, + }, + { + id: 30, + attributes: { + title: "New Post Two", + content: "New Content Two", + createdAt: "2021-12-14T13:35:16.771Z", + updatedAt: "2021-12-14T13:35:16.771Z", + publishedAt: "2021-12-14T13:35:16.693Z", + locale: "en", + }, + }, + { + id: 31, + attributes: { + title: "New Post One", + content: "New Content One", + createdAt: "2021-12-14T13:35:16.800Z", + updatedAt: "2021-12-14T13:35:16.800Z", + publishedAt: "2021-12-14T13:35:16.697Z", + locale: "en", + }, + }, + { + id: 32, + attributes: { + title: "New Post One", + content: "New Content One", + createdAt: "2021-12-14T13:35:25.884Z", + updatedAt: "2021-12-14T13:35:25.884Z", + publishedAt: "2021-12-14T13:35:25.875Z", + locale: "en", + }, + }, + { + id: 33, + attributes: { + title: "New Post Two", + content: "New Content Two", + createdAt: "2021-12-14T13:35:25.886Z", + updatedAt: "2021-12-14T13:35:25.886Z", + publishedAt: "2021-12-14T13:35:25.880Z", + locale: "en", + }, + }, + ], + meta: { + pagination: { page: 1, pageSize: 25, pageCount: 1, total: 22 }, + }, + }, + [ + "Content-Security-Policy", + "connect-src 'self' https:;img-src 'self' data: blob:;media-src 'self' data: blob:;default-src 'self';base-uri 'self';block-all-mixed-content;font-src 'self' https: data:;frame-ancestors 'self';object-src 'none';script-src 'self';script-src-attr 'none';style-src 'self' https: 'unsafe-inline'", + "X-DNS-Prefetch-Control", + "off", + "Expect-CT", + "max-age=0", + "X-Frame-Options", + "SAMEORIGIN", + "Strict-Transport-Security", + "max-age=31536000; includeSubDomains", + "X-Download-Options", + "noopen", + "X-Content-Type-Options", + "nosniff", + "X-Permitted-Cross-Domain-Policies", + "none", + "Referrer-Policy", + "no-referrer", + "Vary", + "Origin", + "Content-Type", + "application/json; charset=utf-8", + "X-Powered-By", + "Strapi ", + "Content-Length", + "6114", + "Date", + "Tue, 14 Dec 2021 13:39:21 GMT", + "Connection", + "close", + ], + ); + +nock("http://localhost:1337", { encodedQueryParams: true }) + .get("/api/posts") + .query({ "filters%5Btitle%5D%5B%24eq%5D": "foo" }) + .reply( + 200, + { + data: [ + { + id: 19, + attributes: { + title: "foo", + content: "bar", + createdAt: "2021-12-14T11:45:30.710Z", + updatedAt: "2021-12-14T11:45:30.710Z", + publishedAt: "2021-12-14T11:45:30.700Z", + locale: "en", + }, + }, + { + id: 21, + attributes: { + title: "foo", + content: "bar", + createdAt: "2021-12-14T11:47:01.493Z", + updatedAt: "2021-12-14T11:47:01.493Z", + publishedAt: "2021-12-14T11:47:01.482Z", + locale: "en", + }, + }, + ], + meta: { + pagination: { page: 1, pageSize: 25, pageCount: 1, total: 2 }, + }, + }, + [ + "Content-Security-Policy", + "connect-src 'self' https:;img-src 'self' data: blob:;media-src 'self' data: blob:;default-src 'self';base-uri 'self';block-all-mixed-content;font-src 'self' https: data:;frame-ancestors 'self';object-src 'none';script-src 'self';script-src-attr 'none';style-src 'self' https: 'unsafe-inline'", + "X-DNS-Prefetch-Control", + "off", + "Expect-CT", + "max-age=0", + "X-Frame-Options", + "SAMEORIGIN", + "Strict-Transport-Security", + "max-age=31536000; includeSubDomains", + "X-Download-Options", + "noopen", + "X-Content-Type-Options", + "nosniff", + "X-Permitted-Cross-Domain-Policies", + "none", + "Referrer-Policy", + "no-referrer", + "Vary", + "Origin", + "Content-Type", + "application/json; charset=utf-8", + "X-Powered-By", + "Strapi ", + "Content-Length", + "457", + "Date", + "Tue, 14 Dec 2021 13:46:41 GMT", + "Connection", + "close", + ], + ); + +nock("http://localhost:1337", { encodedQueryParams: true }) + .get("/api/posts") + .query({ sort: "id%3Adesc" }) + .reply( + 200, + { + data: [ + { + id: 37, + attributes: { + title: "New Post Two", + content: "New Content Two", + createdAt: "2021-12-14T13:35:35.095Z", + updatedAt: "2021-12-15T08:07:42.272Z", + publishedAt: "2021-12-14T13:35:35.082Z", + locale: "en", + }, + }, + { + id: 5, + attributes: { + title: "Lorem ipsum began as scrambled", + content: + "Lorem ipsum, or lipsum as it is sometimes known, is dummy text used in laying out print, graphic or web designs. The passage is attributed to an unknown typesetter in the 15th century who is thought to have scrambled parts of Cicero's De Finibus Bonorum et Malorum for use in a type specimen book. It usually begins with:\n\n\n_**“Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.”**_\n\n\nThe purpose of lorem ipsum is to create a natural looking block of text (sentence, paragraph, page, etc.) that doesn't distract from the layout. A practice not without controversy, laying out pages with meaningless filler text can be very useful when the focus is meant to be on design, not content.\n\nThe passage experienced a surge in popularity during the 1960s when Letraset used it on their dry-transfer sheets, and again during the 90s as desktop publishers bundled the text with their software. Today it's seen all around the web; on templates, websites, and stock designs. Use our generator to get your own, or read on for the authoritative history of lorem ipsum.", + createdAt: "2021-12-09T08:53:47.226Z", + updatedAt: "2021-12-13T11:51:43.224Z", + publishedAt: "2021-12-09T13:45:40.088Z", + locale: "en", + }, + }, + { + id: 3, + attributes: { + title: "Cras iaculis ultricies nulla", + content: + "**Morbi in sem quis dui placerat ornare**. Pellentesque odio nisi, euismod in, pharetra a, ultricies in, diam. Sed arcu. Cras consequat.\n\nPraesent dapibus, neque id cursus faucibus, tortor neque egestas auguae, eu vulputate magna eros eu erat. Aliquam erat volutpat. Nam dui mi, tincidunt quis, accumsan porttitor, facilisis luctus, metus.", + createdAt: "2021-12-08T14:36:57.102Z", + updatedAt: "2021-12-08T14:37:47.584Z", + publishedAt: "2021-12-08T14:37:02.174Z", + locale: "en", + }, + }, + ], + meta: { + pagination: { page: 1, pageSize: 25, pageCount: 1, total: 3 }, + }, + }, + [ + "Content-Security-Policy", + "connect-src 'self' https:;img-src 'self' data: blob:;media-src 'self' data: blob:;default-src 'self';base-uri 'self';block-all-mixed-content;font-src 'self' https: data:;frame-ancestors 'self';object-src 'none';script-src 'self';script-src-attr 'none';style-src 'self' https: 'unsafe-inline'", + "X-DNS-Prefetch-Control", + "off", + "Expect-CT", + "max-age=0", + "X-Frame-Options", + "SAMEORIGIN", + "Strict-Transport-Security", + "max-age=31536000; includeSubDomains", + "X-Download-Options", + "noopen", + "X-Content-Type-Options", + "nosniff", + "X-Permitted-Cross-Domain-Policies", + "none", + "Referrer-Policy", + "no-referrer", + "Vary", + "Origin", + "Content-Type", + "application/json; charset=utf-8", + "X-Powered-By", + "Strapi ", + "Content-Length", + "2192", + "Date", + "Wed, 15 Dec 2021 08:26:18 GMT", + "Connection", + "close", + ], + ); + +nock("http://localhost:1337", { encodedQueryParams: true }) + .post("/api/posts", { data: { title: "test", content: "test" } }) + .reply( + 200, + { + data: { + id: 39, + attributes: { + title: "test", + content: "test", + createdAt: "2021-12-15T08:28:20.103Z", + updatedAt: "2021-12-15T08:28:20.103Z", + publishedAt: "2021-12-15T08:28:20.085Z", + locale: "en", + }, + }, + meta: {}, + }, + [ + "Content-Security-Policy", + "connect-src 'self' https:;img-src 'self' data: blob:;media-src 'self' data: blob:;default-src 'self';base-uri 'self';block-all-mixed-content;font-src 'self' https: data:;frame-ancestors 'self';object-src 'none';script-src 'self';script-src-attr 'none';style-src 'self' https: 'unsafe-inline'", + "X-DNS-Prefetch-Control", + "off", + "Expect-CT", + "max-age=0", + "X-Frame-Options", + "SAMEORIGIN", + "Strict-Transport-Security", + "max-age=31536000; includeSubDomains", + "X-Download-Options", + "noopen", + "X-Content-Type-Options", + "nosniff", + "X-Permitted-Cross-Domain-Policies", + "none", + "Referrer-Policy", + "no-referrer", + "Vary", + "Origin", + "Content-Type", + "application/json; charset=utf-8", + "X-Powered-By", + "Strapi ", + "Content-Length", + "208", + "Date", + "Wed, 15 Dec 2021 08:28:20 GMT", + "Connection", + "close", + ], + ); diff --git a/packages/rest/src/data-providers/strapi-v4/specs/index.spec.ts b/packages/rest/src/data-providers/strapi-v4/specs/index.spec.ts new file mode 100644 index 000000000000..56a2b1e64c92 --- /dev/null +++ b/packages/rest/src/data-providers/strapi-v4/specs/index.spec.ts @@ -0,0 +1,277 @@ +import { createStrapiV4DataProvider } from "../"; +import "./index.mock"; +import { authHeaderBeforeRequestHook } from "src/hooks/auth-header.before-request.hook"; + +describe("dataProvider", () => { + const API_URL = "http://localhost:1337/api"; + const ACCESS_TOKEN_KEY = "accessToken"; + + const { dataProvider } = createStrapiV4DataProvider({ + apiURL: API_URL, + kyOptions: { + hooks: { + beforeRequest: [authHeaderBeforeRequestHook({ ACCESS_TOKEN_KEY })], + }, + }, + }); + + beforeAll(() => { + localStorage.setItem( + ACCESS_TOKEN_KEY, + "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6MSwiaWF0IjoxNjU1Mjc0MTIzLCJleHAiOjE2NTc4NjYxMjN9.Au8NsqnS2mjtKBHf1oRl-juEQ_l9JZrPk4Fsv4GsvVA", + ); + }); + + // create + describe("create", () => { + it("correct response", async () => { + const { data } = await dataProvider.create({ + resource: "posts", + variables: { title: "foo", content: "bar", cover: ["32"] }, + }); + + expect(data.data.id).toBe(20); + expect(data.data.attributes.title).toBe("foo"); + expect(data.data.attributes.content).toBe("bar"); + }); + }); + + // deleteOne + describe("deleteOne", () => { + it("correct response", async () => { + const { data } = await dataProvider.deleteOne({ + resource: "posts", + id: "18", + }); + + expect(data).toBeUndefined(); + }); + }); + + describe("getList", () => { + it("correct response", async () => { + const { data, total } = await dataProvider.getList({ resource: "posts" }); + + expect(data[2].id).toBe(17); + expect(data[2].title).toBe("foo"); + expect(data[2].content).toBe("bar"); + expect(total).toBe(6); + }); + + it("correct sorting response", async () => { + const { data, total } = await dataProvider.getList({ + resource: "posts", + sorters: [ + { + field: "id", + order: "desc", + }, + ], + }); + + expect(data[0].id).toBe(21); + expect(data[0].title).toBe("foo"); + expect(data[0].content).toBe("bar"); + expect(total).toBe(6); + }); + + it("correct filter response", async () => { + const { data } = await dataProvider.getList({ + resource: "posts", + filters: [ + { + field: "title", + operator: "eq", + value: "foo", + }, + ], + }); + + expect(data[0]["title"]).toBe("foo"); + expect(data.length).toBe(3); + }); + + it("correct filter and sort response", async () => { + const { data } = await dataProvider.getList({ + resource: "posts", + filters: [ + { + field: "title", + operator: "eq", + value: "foo", + }, + ], + sorters: [ + { + field: "id", + order: "desc", + }, + ], + }); + + expect(data[0].title).toBe("foo"); + expect(data.length).toBe(3); + }); + + it("correct locale response", async () => { + const { data, total } = await dataProvider.getList({ + resource: "posts", + meta: { + locale: "de", + }, + }); + + expect(data[0].title).toBe("Hello"); + expect(data[0].locale).toBe("de"); + expect(total).toBe(3); + }); + + it("correct fields response", async () => { + const { data, total } = await dataProvider.getList({ + resource: "posts", + meta: { + fields: ["title", "content"], + }, + }); + + expect(data[0]).toEqual({ + id: 5, + title: "Lorem ipsum began as scrambled", + content: + "Lorem ipsum, or lipsum as it is sometimes known, is dummy text used in laying out print, graphic or web designs. The passage is attributed to an unknown typesetter in the 15th century who is thought to have scrambled parts of Cicero's De Finibus Bonorum et Malorum for use in a type specimen book. It usually begins with:\n\n\n_**“Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.”**_\n\n\nThe purpose of lorem ipsum is to create a natural looking block of text (sentence, paragraph, page, etc.) that doesn't distract from the layout. A practice not without controversy, laying out pages with meaningless filler text can be very useful when the focus is meant to be on design, not content.\n\nThe passage experienced a surge in popularity during the 1960s when Letraset used it on their dry-transfer sheets, and again during the 90s as desktop publishers bundled the text with their software. Today it's seen all around the web; on templates, websites, and stock designs. Use our generator to get your own, or read on for the authoritative history of lorem ipsum.", + }); + expect(total).toBe(4); + }); + + it("correct populated response", async () => { + const { data, total } = await dataProvider.getList({ + resource: "posts", + meta: { + populate: ["category"], + }, + }); + + expect(data[0].category).toBeTruthy(); + expect(total).toBe(4); + }); + }); + + // getMany + describe("getMany", () => { + it("correct response", async () => { + const { data } = await dataProvider.getMany!({ + resource: "posts", + ids: ["30", "29"], + meta: { populate: ["category"] }, + }); + + expect(data[0].id).toBe(29); + expect(data[0].title).toBe("Hello"); + expect(data[0].content).toBe("Testtt "); + + expect(data[1].id).toBe(30); + expect(data[1].category.id).toBe(12); + }); + }); + + // getOne + describe("getOne", () => { + it("correct response", async () => { + const { data } = await dataProvider.getOne({ + resource: "posts", + id: "8", + }); + + expect(data.id).toBe(8); + expect(data.title).toBe("Hello"); + expect(data.content).toBe("New post content"); + }); + }); + + // updateOne + describe("updateOne", () => { + it("correct response", async () => { + const { data } = await dataProvider.update({ + resource: "posts", + id: "8", + variables: { + title: "Updated Title", + }, + }); + expect(data.data.id).toBe(8); + expect(data.data.attributes.title).toBe("Updated Title"); + }); + }); + + describe("custom", () => { + it("correct get response", async () => { + const { data } = await dataProvider.custom!({ + url: `${API_URL}/posts`, + method: "get", + }); + + expect(data.data[0].id).toBe(5); + expect(data.data[0].attributes.title).toBe( + "Lorem ipsum began as scrambled", + ); + }); + + it("correct filter response", async () => { + const { data } = await dataProvider.custom!({ + url: `${API_URL}/posts`, + method: "get", + filters: [ + { + field: "title", + operator: "eq", + value: "foo", + }, + ], + }); + + expect(data.data[0].id).toBe(19); + expect(data.data[0].attributes.title).toBe("foo"); + }); + + it("correct sort response", async () => { + const { data } = await dataProvider.custom!({ + url: `${API_URL}/posts`, + method: "get", + sorters: [ + { + field: "id", + order: "desc", + }, + ], + }); + + expect(data.data[0].id).toBe(37); + expect(data.data[0].attributes.title).toBe("New Post Two"); + }); + + it("correct post request", async () => { + const { data } = await dataProvider.custom!({ + url: `${API_URL}/posts`, + method: "post", + payload: { + data: { + title: "test", + content: "test", + }, + }, + }); + + expect(data.data).toEqual({ + id: 39, + attributes: { + title: "test", + content: "test", + createdAt: "2021-12-15T08:28:20.103Z", + updatedAt: "2021-12-15T08:28:20.103Z", + publishedAt: "2021-12-15T08:28:20.085Z", + locale: "en", + }, + }); + }); + }); +}); diff --git a/packages/rest/src/data-providers/strapi-v4/strapi-v4.options.ts b/packages/rest/src/data-providers/strapi-v4/strapi-v4.options.ts new file mode 100644 index 000000000000..e22d9b19ef36 --- /dev/null +++ b/packages/rest/src/data-providers/strapi-v4/strapi-v4.options.ts @@ -0,0 +1,292 @@ +import type { + CreateParams, + CustomParams, + DeleteOneParams, + GetListParams, + GetManyParams, + GetOneParams, + UpdateParams, +} from "@refinedev/core"; +import type { KyResponse } from "ky"; +import type { AnyObject } from "src/types"; +import { generateFilter } from "./utils/generateFilter"; +import { normalizeData } from "./utils/normalizeData"; +import { transformHttpError } from "./utils/transformHttpError"; + +export const strapiV4DataProviderOptions = { + getList: { + getEndpoint(params: GetListParams): string { + return `${params.resource}`; + }, + async buildFilters(params: GetListParams) { + const { filters = [] } = params; + + const queryFilters = generateFilter(filters); + + return queryFilters; + }, + async buildPagination(params: GetListParams) { + const { + currentPage = 1, + pageSize = 10, + mode = "server", + } = params.pagination ?? {}; + + if (mode === "server") { + return { + page: currentPage, + pageSize: pageSize, + }; + } + + return {}; + }, + async buildSorters(params: GetListParams) { + const { sorters = [] } = params; + + const _sorters: string[] = []; + + if (sorters) { + // strapi.com/categories?sort=id:asc + sorters.forEach((item) => { + if (item.order) { + _sorters.push(`${item.field}:${item.order}`); + } + }); + } + + const sort = _sorters.length ? _sorters.join(",") : undefined; + + return { sort }; + }, + async buildQueryParams(params: GetListParams) { + const filters = await this.buildFilters(params); + + const { sort } = await this.buildSorters(params); + + const pagination = await this.buildPagination(params); + + const { meta } = params; + + const locale = meta?.locale; + const fields = meta?.fields; + const populate = meta?.populate; + const publicationState = meta?.publicationState; + + const queryParams = { + pagination, + locale, + publicationState, + fields, + populate, + sort, + filters, + }; + + return queryParams; + }, + async mapResponse( + response: KyResponse, + params: GetListParams, + ): Promise { + const body = await response.json(); + + return normalizeData(body); + }, + async getTotalCount( + response: KyResponse, + params: GetListParams, + ): Promise { + const body = await response.json(); + + return body.meta?.pagination?.total || normalizeData(body)?.length; + }, + }, + getOne: { + getEndpoint(params: GetOneParams) { + return `${params.resource}/${params.id}`; + }, + async buildHeaders(params: GetOneParams) { + return params.meta?.headers ?? {}; + }, + async buildQueryParams(params: GetOneParams) { + const { meta } = params ?? {}; + + const locale = meta?.locale; + const fields = meta?.fields; + const populate = meta?.populate; + const publicationState = meta?.publicationState; + + const queryParams = { + locale, + fields, + populate, + publicationState, + }; + + return queryParams; + }, + async mapResponse( + response: KyResponse, + params: GetOneParams, + ): Promise> { + const body = await response.json(); + + return normalizeData(body); + }, + }, + getMany: { + getEndpoint(params: GetManyParams) { + return `${params.resource}`; + }, + async buildHeaders(params: GetManyParams) { + return params.meta?.headers ?? {}; + }, + async buildQueryParams(params: GetManyParams) { + const { ids, meta } = params ?? {}; + const locale = meta?.locale; + const fields = meta?.fields; + const populate = meta?.populate; + const publicationState = meta?.publicationState; + + const filters = generateFilter([ + { field: "id", operator: "in", value: ids }, + ]); + + const query = { + locale, + fields, + populate, + publicationState, + filters, + pagination: { pageSize: ids.length }, + }; + + return query; + }, + async mapResponse(response: KyResponse, params: GetManyParams) { + const body = await response.json(); + + return normalizeData(body); + }, + }, + create: { + getEndpoint(params: CreateParams): string { + return `${params.resource}`; + }, + + async buildBodyParams(params: CreateParams) { + const { resource, variables } = params; + let bodyParams = { data: variables }; + + if (resource === "users") { + bodyParams = variables; + } + + return bodyParams; + }, + async mapResponse( + response: KyResponse, + _params: CreateParams, + ): Promise> { + if (response.status >= 400) { + const httpError = transformHttpError(response); + + throw httpError; + } + + const body = await response.json(); + + return body; + }, + }, + update: { + getEndpoint(params: UpdateParams): string { + return `${params.resource}/${params.id}`; + }, + getRequestMethod(params: UpdateParams) { + return params.meta?.method ?? "put"; + }, + async buildHeaders(params: UpdateParams) { + return params.meta?.headers ?? {}; + }, + async buildQueryParams(params: UpdateParams) { + return params.meta?.query ?? {}; + }, + async buildBodyParams(params: UpdateParams) { + const { resource, variables } = params; + let bodyParams = { data: variables }; + + if (resource === "users") { + bodyParams = variables; + } + + return bodyParams; + }, + async mapResponse( + response: KyResponse, + params: UpdateParams, + ) { + return await response.json(); + }, + }, + deleteOne: { + getEndpoint(params: DeleteOneParams) { + return `${params.resource}/${params.id}`; + }, + async buildHeaders(params: DeleteOneParams) { + return params.meta?.headers ?? {}; + }, + async buildQueryParams(params: DeleteOneParams) { + return params.meta?.query ?? {}; + }, + async mapResponse( + _response: KyResponse, + _params: DeleteOneParams, + ) { + return undefined; + }, + }, + custom: { + async buildQueryParams(params: CustomParams) { + const queryParams = {}; + + if (params.sorters) { + const _sorters: string[] = []; + + if (params.sorters) { + // strapi.com/categories?sort=id:asc + params.sorters.forEach((item) => { + if (item.order) { + _sorters.push(`${item.field}:${item.order}`); + } + }); + } + + const sort = _sorters.length ? _sorters.join(",") : undefined; + + Object.assign(queryParams, { sort }); + } + + if (params.filters) { + const queryFilters = generateFilter(params.filters); + + Object.assign(queryParams, { filters: queryFilters }); + } + + return queryParams; + }, + async buildHeaders(params: CustomParams) { + return params.headers ?? {}; + }, + async buildBodyParams(params: CustomParams) { + return params.payload ?? {}; + }, + async mapResponse( + response: KyResponse, + params: CustomParams, + ) { + return await response.json(); + }, + }, +}; diff --git a/packages/rest/src/data-providers/strapi-v4/utils/generateFilter.ts b/packages/rest/src/data-providers/strapi-v4/utils/generateFilter.ts new file mode 100644 index 000000000000..8e411b7d59d1 --- /dev/null +++ b/packages/rest/src/data-providers/strapi-v4/utils/generateFilter.ts @@ -0,0 +1,89 @@ +import type { + CrudFilters, + LogicalFilter, + ConditionalFilter, +} from "@refinedev/core"; +import { mapOperator } from "./mapOperator"; + +export const generateNestedFilterField = (field: string): string[] => { + if (field.startsWith("[") && field.endsWith("]")) { + const matches = field.match(/\[([^\]]+)\]/g); + if (matches) { + return matches.map((match) => match.slice(1, -1)); + } + } + + return field.split("."); +}; + +const generateLogicalFilter = (filter: LogicalFilter): any => { + const { field, operator, value } = filter; + + const mappedOperator = mapOperator(operator); + const fieldPath = generateNestedFilterField(field); + + const filterObj: any = {}; + + let current = filterObj; + for (let i = 0; i < fieldPath.length - 1; i++) { + current[fieldPath[i]] = {}; + current = current[fieldPath[i]]; + } + current[fieldPath[fieldPath.length - 1]] = { [`$${mappedOperator}`]: value }; + + return filterObj; +}; + +const mergeFilters = (target: any, source: any): any => { + for (const key in source) { + if (key in target) { + if (typeof target[key] === "object" && typeof source[key] === "object") { + target[key] = mergeFilters(target[key], source[key]); + } + } else { + target[key] = source[key]; + } + } + return target; +}; + +const generateConditionalFilter = (filter: ConditionalFilter): any => { + const result: any = {}; + const operatorKey = `$${filter.operator}`; + + const subFilters = filter.value.map((item) => { + if (item.operator !== "or" && item.operator !== "and" && "field" in item) { + return generateLogicalFilter(item); + } + return generateConditionalFilter(item); + }); + + result[operatorKey] = subFilters; + return result; +}; + +export const generateFilter = (filters?: CrudFilters): any => { + if (!filters || filters.length === 0) { + return {}; + } + + let result: any = {}; + + filters.forEach((filter) => { + let filterObj: any; + + if ( + filter.operator !== "or" && + filter.operator !== "and" && + "field" in filter + ) { + filterObj = generateLogicalFilter(filter); + } else { + filterObj = generateConditionalFilter(filter); + } + + result = mergeFilters(result, filterObj); + }); + + return result; +}; diff --git a/packages/rest/src/data-providers/strapi-v4/utils/mapOperator.ts b/packages/rest/src/data-providers/strapi-v4/utils/mapOperator.ts new file mode 100644 index 000000000000..c8fa6ba0a260 --- /dev/null +++ b/packages/rest/src/data-providers/strapi-v4/utils/mapOperator.ts @@ -0,0 +1,24 @@ +import type { CrudOperators } from "@refinedev/core"; + +export const mapOperator = (operator: CrudOperators) => { + switch (operator) { + case "startswith": + return "startsWith"; + case "endswith": + return "endsWith"; + case "nin": + return "notIn"; + case "ncontains": + return "notContainsi"; + case "ncontainss": + return "notContains"; + case "containss": + return "contains"; + case "contains": + return "containsi"; + case "nnull": + return "notNull"; + } + + return operator; +}; diff --git a/packages/rest/src/data-providers/strapi-v4/utils/normalizeData.ts b/packages/rest/src/data-providers/strapi-v4/utils/normalizeData.ts new file mode 100644 index 000000000000..a3194d1301ce --- /dev/null +++ b/packages/rest/src/data-providers/strapi-v4/utils/normalizeData.ts @@ -0,0 +1,37 @@ +const flatten = (data: any) => { + if (!data.attributes) return data; + + return { + id: data.id, + ...data.attributes, + }; +}; + +const isObject = (data: any) => + Object.prototype.toString.call(data) === "[object Object]"; + +export const normalizeData = (data: any): any => { + if (Array.isArray(data)) { + return data.map((item) => normalizeData(item)); + } + + if (isObject(data)) { + if (Array.isArray(data.data)) { + data = [...data.data]; + } else if (isObject(data.data)) { + data = flatten({ ...data.data }); + } else if (data.data === null) { + data = null; + } else { + data = flatten(data); + } + + for (const key in data) { + data[key] = normalizeData(data[key]); + } + + return data; + } + + return data; +}; diff --git a/packages/rest/src/data-providers/strapi-v4/utils/transformErrorMessages.ts b/packages/rest/src/data-providers/strapi-v4/utils/transformErrorMessages.ts new file mode 100644 index 000000000000..c0db5d3c09d4 --- /dev/null +++ b/packages/rest/src/data-providers/strapi-v4/utils/transformErrorMessages.ts @@ -0,0 +1,27 @@ +type ErrorMessage = { + path: string[]; + message: string; + name: string; +}; + +type TransformedErrors = { + [key: string]: string[]; +}; + +export const transformErrorMessages = ( + errorMessages: ErrorMessage[], +): TransformedErrors => { + const transformedErrors: TransformedErrors = {}; + + for (const error of errorMessages) { + const key = error.path[0]; + + if (transformedErrors[key]) { + transformedErrors[key].push(error.message); + } else { + transformedErrors[key] = [error.message]; + } + } + + return transformedErrors; +}; diff --git a/packages/rest/src/data-providers/strapi-v4/utils/transformHttpError.ts b/packages/rest/src/data-providers/strapi-v4/utils/transformHttpError.ts new file mode 100644 index 000000000000..cc70bd8cba76 --- /dev/null +++ b/packages/rest/src/data-providers/strapi-v4/utils/transformHttpError.ts @@ -0,0 +1,18 @@ +import type { HttpError } from "@refinedev/core"; +import { transformErrorMessages } from "./transformErrorMessages"; + +export const transformHttpError = (err: any): HttpError => { + const error = err || {}; + + const message = error?.message; + const statusCode = error?.status; + const errorMessages = error?.details?.errors || []; + + const httpError: HttpError = { + statusCode, + message, + errors: transformErrorMessages(errorMessages), + }; + + return httpError; +}; diff --git a/packages/rest/src/default.options.ts b/packages/rest/src/default.options.ts new file mode 100644 index 000000000000..5e71171746cc --- /dev/null +++ b/packages/rest/src/default.options.ts @@ -0,0 +1,191 @@ +import type { + CreateParams, + CustomParams, + DeleteOneParams, + GetListParams, + GetOneParams, + UpdateParams, + HttpError, + GetManyParams, +} from "@refinedev/core"; +import type { KyResponse } from "ky"; + +import type { AnyObject } from "./types"; + +export const defaultCreateDataProviderOptions = { + getList: { + getEndpoint(params: GetListParams): string { + return `${params.resource}`; + }, + async buildHeaders(params: GetListParams) { + return params.meta?.headers ?? {}; + }, + async buildQueryParams(params: GetListParams) { + const { filters, sorters, pagination } = params; + + const queryParams = { + filters, + sorters, + pagination, + ...params.meta?.query, + }; + + // filters[0][field]=id&filters[0][operator]=eq&filters[0][value]=123 + + return queryParams; + }, + async mapResponse( + response: KyResponse, + _params: GetListParams, + ): Promise { + return await response.json(); + }, + async getTotalCount( + _response: KyResponse, + _params: GetListParams, + ): Promise { + return -1; + }, + }, + getOne: { + getEndpoint(params: GetOneParams) { + return `${params.resource}/${params.id}`; + }, + async buildHeaders(params: GetOneParams) { + return params.meta?.headers ?? {}; + }, + async buildQueryParams(params: GetOneParams) { + return params.meta?.query ?? {}; + }, + async mapResponse( + response: KyResponse, + _params: GetOneParams, + ): Promise> { + return await response.json(); + }, + }, + getMany: { + getEndpoint(params: GetManyParams) { + return `${params.resource}`; + }, + async buildHeaders(params: GetManyParams) { + return params.meta?.headers ?? {}; + }, + async buildQueryParams(params: GetManyParams) { + const queryParams = { + ids: params.ids.join(","), + }; + + return params.meta?.query ?? queryParams; + }, + async mapResponse(response: KyResponse, _params: GetManyParams) { + const body = await response.json(); + + return body.records; + }, + }, + create: { + getEndpoint(params: CreateParams): string { + return params.resource; + }, + async buildHeaders(params: CreateParams) { + return params.meta?.headers ?? {}; + }, + async buildQueryParams(params: CreateParams) { + return params.meta?.query ?? {}; + }, + async buildBodyParams(params: CreateParams) { + return params.variables; + }, + async mapResponse( + response: KyResponse, + _params: CreateParams, + ): Promise> { + return await response.json(); + }, + async transformError( + response: KyResponse, + params: CreateParams, + ): Promise { + const body = await response.json(); + + return { + message: JSON.stringify({ ...body, variables: params.variables }), + statusCode: response.status, + }; + }, + }, + update: { + getEndpoint(params: UpdateParams): string { + return `${params.resource}/${params.id}`; + }, + getRequestMethod(params: UpdateParams) { + return params.meta?.method ?? "patch"; + }, + async buildHeaders(params: UpdateParams) { + return params.meta?.headers ?? {}; + }, + async buildQueryParams(params: UpdateParams) { + return params.meta?.query ?? {}; + }, + async buildBodyParams(params: UpdateParams) { + return params.variables; + }, + async mapResponse( + response: KyResponse, + _params: UpdateParams, + ) { + return await response.json(); + }, + + async transformError( + response: KyResponse, + params: UpdateParams, + ): Promise { + const body = await response.json(); + + return { + message: JSON.stringify({ + ...body, + id: params.id, + variables: params.variables, + }), + statusCode: response.status, + }; + }, + }, + deleteOne: { + getEndpoint(params: DeleteOneParams) { + return `${params.resource}/${params.id}`; + }, + async buildHeaders(params: DeleteOneParams) { + return params.meta?.headers ?? {}; + }, + async buildQueryParams(params: DeleteOneParams) { + return params.meta?.query ?? {}; + }, + async mapResponse( + _response: KyResponse, + _params: DeleteOneParams, + ) { + return undefined; + }, + }, + custom: { + async buildQueryParams(params: CustomParams) { + return params.query ?? {}; + }, + async buildHeaders(params: CustomParams) { + return params.headers ?? {}; + }, + async buildBodyParams(params: CustomParams) { + return params.payload ?? {}; + }, + async mapResponse( + response: KyResponse, + _params: CustomParams, + ) { + return await response.json(); + }, + }, +}; diff --git a/packages/rest/src/hooks/auth-header.before-request.hook.spec.ts b/packages/rest/src/hooks/auth-header.before-request.hook.spec.ts new file mode 100644 index 000000000000..4e4081a3fc62 --- /dev/null +++ b/packages/rest/src/hooks/auth-header.before-request.hook.spec.ts @@ -0,0 +1,32 @@ +import nock from "nock"; +import { createDataProvider } from "../create-data-provider"; +import { authHeaderBeforeRequestHook } from "./auth-header.before-request.hook"; + +const API_URL = "https://example.com"; +const ACCESS_TOKEN_KEY = "accessToken"; + +const HEADER_NAME = "Authorization"; +const TOKEN = "my-test-token"; + +describe("auth", () => { + const { dataProvider } = createDataProvider( + API_URL, + {}, + { + hooks: { + beforeRequest: [authHeaderBeforeRequestHook({ ACCESS_TOKEN_KEY })], + }, + }, + ); + + it("should add Authorization header", async () => { + localStorage.setItem(ACCESS_TOKEN_KEY, TOKEN); + + nock(API_URL) + .matchHeader(HEADER_NAME, `Bearer ${TOKEN}`) + .get("/posts") + .reply(200, {}); + + await dataProvider.getList({ resource: "posts" }); + }); +}); diff --git a/packages/rest/src/hooks/auth-header.before-request.hook.ts b/packages/rest/src/hooks/auth-header.before-request.hook.ts new file mode 100644 index 000000000000..3ec0c472c951 --- /dev/null +++ b/packages/rest/src/hooks/auth-header.before-request.hook.ts @@ -0,0 +1,33 @@ +import type { Hooks } from "ky"; + +type AuthHeaderBeforeRequestHookOptions = { + ACCESS_TOKEN_KEY: string; +}; + +/** + * Gets the token from localStorage and adds `Bearer ` to the Authorization header. + * + * @param {string} options.ACCESS_TOKEN_KEY - The key used to retrieve the access token from localStorage. + * @returns Ky beforeRequest hook function. + * @example + * ```ts + * import { authHeaderBeforeRequestHook } from "@refinedev/rest"; + * + * const dataProvider = createDataProvider("https://api.example.com", {}, { + * hooks: { + * beforeRequest: [authHeaderBeforeRequestHook({ ACCESS_TOKEN_KEY: "accessToken" })], + * }, + * }); + * ``` + */ +export const authHeaderBeforeRequestHook = + ( + options: AuthHeaderBeforeRequestHookOptions, + ): NonNullable[number] => + async (req) => { + const token = localStorage.getItem(options.ACCESS_TOKEN_KEY); + + if (token) { + req.headers.set("Authorization", `Bearer ${token}`); + } + }; diff --git a/packages/rest/src/hooks/refresh-token.after-response.hook.spec.ts b/packages/rest/src/hooks/refresh-token.after-response.hook.spec.ts new file mode 100644 index 000000000000..1fc49a05afcd --- /dev/null +++ b/packages/rest/src/hooks/refresh-token.after-response.hook.spec.ts @@ -0,0 +1,63 @@ +import nock from "nock"; +import { createDataProvider } from "../create-data-provider"; +import { authHeaderBeforeRequestHook } from "./auth-header.before-request.hook"; +import { refreshTokenAfterResponseHook } from "./refresh-token.after-response.hook"; + +const API_URL = "https://example.com"; + +const ACCESS_TOKEN_KEY = "accessToken"; +const REFRESH_TOKEN_KEY = "refreshToken"; + +const CURRENT_TOKEN = "current-token"; +const CURRENT_REFRESH_TOKEN = "current-refresh-token"; + +const NEW_TOKEN = "new-token"; +const NEW_REFRESH_TOKEN = "new-refresh-token"; + +const REFRESH_TOKEN_URL = `${API_URL}/refresh-token`; + +describe("auth", () => { + const { dataProvider } = createDataProvider( + API_URL, + {}, + { + hooks: { + beforeRequest: [authHeaderBeforeRequestHook({ ACCESS_TOKEN_KEY })], + afterResponse: [ + refreshTokenAfterResponseHook({ + ACCESS_TOKEN_KEY, + REFRESH_TOKEN_KEY, + REFRESH_TOKEN_URL, + }), + ], + }, + }, + ); + + it("should add Authorization header", async () => { + localStorage.setItem(ACCESS_TOKEN_KEY, CURRENT_TOKEN); + localStorage.setItem(REFRESH_TOKEN_KEY, CURRENT_REFRESH_TOKEN); + + nock(API_URL) + .matchHeader("Authorization", `Bearer ${CURRENT_TOKEN}`) + .get("/posts") + .reply(401, {}); + + nock(API_URL) + .post("/refresh-token", { refreshToken: CURRENT_REFRESH_TOKEN }) + .reply(200, { token: NEW_TOKEN, refreshToken: NEW_REFRESH_TOKEN }); + + nock(API_URL) + .matchHeader("Authorization", `Bearer ${NEW_TOKEN}`) + .get("/posts") + .reply(200, [{ id: 1 }]); + + const result = await dataProvider.getList({ resource: "posts" }); + + expect(localStorage.getItem(ACCESS_TOKEN_KEY)).toEqual(NEW_TOKEN); + expect(localStorage.getItem(REFRESH_TOKEN_KEY)).toEqual(NEW_REFRESH_TOKEN); + + expect(result.data).toEqual([{ id: 1 }]); + expect(result.total).toEqual(-1); + }); +}); diff --git a/packages/rest/src/hooks/refresh-token.after-response.hook.ts b/packages/rest/src/hooks/refresh-token.after-response.hook.ts new file mode 100644 index 000000000000..47d6e2e1bc12 --- /dev/null +++ b/packages/rest/src/hooks/refresh-token.after-response.hook.ts @@ -0,0 +1,65 @@ +import ky, { type Hooks } from "ky"; + +type RefreshTokenAfterResponseHookOptions = { + ACCESS_TOKEN_KEY: string; + REFRESH_TOKEN_KEY: string; + REFRESH_TOKEN_URL: string; +}; + +/** + * Middleware to handle token refresh on 401 responses. + * + * @param {string} refineOptions.ACCESS_TOKEN_KEY - The key used to retrieve the access token from localStorage. + * @param {string} refineOptions.REFRESH_TOKEN_KEY - The key used to retrieve the refresh token from localStorage. + * @param {string} refineOptions.REFRESH_TOKEN_URL - The URL to send the refresh token request to. + * @returns Ky afterResponse hook function. + * @example + * ```ts + * import { refreshTokenAfterResponseHook } from "@refinedev/rest"; + * + * const dataProvider = createDataProvider("https://api.example.com", {}, { + * hooks: { + * afterResponse: [refreshTokenAfterResponseHook({ + * ACCESS_TOKEN_KEY: "accessToken", + * REFRESH_TOKEN_KEY: "refreshToken", + * REFRESH_TOKEN_URL: "https://api.example.com/refresh-token", + * })], + * }, + * }); + * ``` + */ +export const refreshTokenAfterResponseHook = + ( + refineOptions: RefreshTokenAfterResponseHookOptions, + ): NonNullable[number] => + async (request, _options, response) => { + if (response.status === 401) { + const currentRefreshToken = localStorage.getItem( + refineOptions.REFRESH_TOKEN_KEY, + ); + + try { + const data = await ky<{ token: string; refreshToken: string }>( + refineOptions.REFRESH_TOKEN_URL, + { + method: "post", + body: JSON.stringify({ refreshToken: currentRefreshToken }), + }, + ).json(); + + const accessToken = data.token; + const refreshToken = data.refreshToken; + + localStorage.setItem(refineOptions.ACCESS_TOKEN_KEY, accessToken); + localStorage.setItem(refineOptions.REFRESH_TOKEN_KEY, refreshToken); + + request.headers.set("Authorization", `token ${accessToken}`); + + return ky(request); + } catch (e) { + return response; + } + } + + return response; + }; diff --git a/packages/rest/src/index.ts b/packages/rest/src/index.ts new file mode 100644 index 000000000000..eac01f374a1e --- /dev/null +++ b/packages/rest/src/index.ts @@ -0,0 +1,6 @@ +export type { CreateDataProviderOptions } from "./types.js"; + +export { authHeaderBeforeRequestHook } from "./hooks/auth-header.before-request.hook.js"; +export { refreshTokenAfterResponseHook } from "./hooks/refresh-token.after-response.hook.js"; + +export { createDataProvider } from "./create-data-provider.js"; diff --git a/packages/rest/src/types.ts b/packages/rest/src/types.ts new file mode 100644 index 000000000000..011aefc5ff4c --- /dev/null +++ b/packages/rest/src/types.ts @@ -0,0 +1,131 @@ +import type { + CreateManyParams, + CreateParams, + CustomParams, + DeleteManyParams, + DeleteOneParams, + GetListParams, + GetManyParams, + GetOneParams, + HttpError, + UpdateManyParams, + UpdateParams, +} from "@refinedev/core"; +import type { KyResponse } from "ky"; + +export type AnyObject = Record; + +type GetEndpoint

= (params: P) => string; + +type BuildQueryParams

= (params: P) => Promise; +type BuildBodyParams

= (params: P) => Promise; +type BuildHeaders

= (params: P) => Promise; + +type MapResponse = ( + response: KyResponse, + params: P, +) => Promise; + +type TransformError

= ( + response: KyResponse, + params: P, +) => Promise; + +export type CreateDataProviderOptions = { + getList?: { + getEndpoint?: GetEndpoint; + buildHeaders?: BuildHeaders; + + buildQueryParams?: BuildQueryParams; + + mapResponse?: MapResponse; + getTotalCount?: MapResponse; + }; + create?: { + getEndpoint?: GetEndpoint>; + buildHeaders?: BuildHeaders>; + + buildQueryParams?: BuildQueryParams>; + + buildBodyParams?: BuildBodyParams>; + + mapResponse?: MapResponse, AnyObject>; + transformError?: TransformError>; + }; + createMany?: { + getEndpoint?: GetEndpoint>; + buildHeaders?: BuildHeaders>; + + buildQueryParams?: BuildQueryParams>; + + buildBodyParams: BuildBodyParams>; + + mapResponse: MapResponse, AnyObject[]>; + transformError?: TransformError>; + }; + getOne?: { + getEndpoint?: GetEndpoint; + buildHeaders?: BuildHeaders; + + buildQueryParams?: BuildQueryParams; + + mapResponse?: MapResponse; + }; + getMany?: { + getEndpoint?: GetEndpoint; + buildHeaders?: BuildHeaders; + + buildQueryParams?: BuildQueryParams; + + mapResponse?: MapResponse; + }; + update?: { + getEndpoint?: GetEndpoint>; + getRequestMethod?: (params: UpdateParams) => "put" | "patch"; + buildHeaders?: BuildHeaders; + + buildQueryParams?: BuildQueryParams>; + + buildBodyParams?: BuildBodyParams>; + + mapResponse?: MapResponse, AnyObject>; + transformError?: TransformError>; + }; + updateMany?: { + getEndpoint: GetEndpoint>; + getRequestMethod?: (params: UpdateManyParams) => "put" | "patch"; + buildHeaders?: BuildHeaders>; + + buildQueryParams?: BuildQueryParams>; + + buildBodyParams: BuildBodyParams>; + + mapResponse: MapResponse, AnyObject[]>; + transformError?: TransformError>; + }; + deleteOne?: { + getEndpoint?: GetEndpoint>; + buildHeaders?: BuildHeaders>; + + buildQueryParams?: BuildQueryParams>; + + mapResponse?: MapResponse, AnyObject | undefined>; + }; + deleteMany?: { + getEndpoint?: GetEndpoint>; + buildHeaders?: BuildHeaders>; + + buildQueryParams?: BuildQueryParams>; + + mapResponse?: MapResponse, AnyObject | undefined>; + }; + custom?: { + buildHeaders?: BuildHeaders>; + + buildQueryParams?: BuildQueryParams>; + + buildBodyParams?: BuildBodyParams>; + + mapResponse?: MapResponse, AnyObject>; + }; +}; diff --git a/packages/rest/test/index.ts b/packages/rest/test/index.ts new file mode 100644 index 000000000000..c053d3645b68 --- /dev/null +++ b/packages/rest/test/index.ts @@ -0,0 +1,3 @@ +export { createDataProvider } from "../src/create-data-provider"; + +export const API_URL = "https://example.com"; diff --git a/packages/rest/test/methods/create.spec.ts b/packages/rest/test/methods/create.spec.ts new file mode 100644 index 000000000000..8f9f949a3dbe --- /dev/null +++ b/packages/rest/test/methods/create.spec.ts @@ -0,0 +1,64 @@ +import nock from "nock"; +import { API_URL, createDataProvider } from ".."; + +const variables = { foo: "bar" }; + +describe("create", () => { + const { dataProvider } = createDataProvider( + API_URL, + {}, + { + headers: { "x-default-header": "create" }, + }, + ); + + describe("success", () => { + it("should return the data", async () => { + const response = { + createResult: { + id: 1, + ...variables, + }, + }; + + nock(API_URL) + .matchHeader("x-custom-header", "create") + .matchHeader("x-default-header", "create") + .post("/create", variables) + .query({ queryParams: variables }) + .reply(201, response); + + const result = await dataProvider.create({ + resource: "create", + variables, + meta: { + query: { queryParams: variables }, + headers: { "x-custom-header": "create" }, + }, + }); + + expect(result).toEqual({ data: response }); + }); + }); + + describe("failure", () => { + it("should throw HttpError with message and statusCode", async () => { + nock(API_URL).post("/create", variables).reply(400, { + message: "Bad Request", + }); + + await expect( + dataProvider.create({ + resource: "create", + variables, + }), + ).rejects.toMatchObject({ + message: JSON.stringify({ + message: "Bad Request", + variables, + }), + statusCode: 400, + }); + }); + }); +}); diff --git a/packages/rest/test/methods/custom.spec.ts b/packages/rest/test/methods/custom.spec.ts new file mode 100644 index 000000000000..a8cb56aa235d --- /dev/null +++ b/packages/rest/test/methods/custom.spec.ts @@ -0,0 +1,141 @@ +import nock from "nock"; +import qs from "qs"; +import { API_URL, createDataProvider } from ".."; + +const CUSTOM_API_URL = "https://custom.com"; + +describe("custom", () => { + const { dataProvider } = createDataProvider( + API_URL, + {}, + { + headers: { "x-custom-header-default": "default" }, + }, + ); + + describe("queryParams", () => { + const response = { customResult: { id: 1 } }; + + const queryParams = { foo: { bar: { baz: "test" } } }; + + nock(CUSTOM_API_URL) + .get("/custom-query-params") + .matchHeader("x-custom-header-default", "default") + .query(queryParams) + .reply(200, response); + + it("should return the data", async () => { + const result = await dataProvider.custom?.({ + method: "get", + url: `${CUSTOM_API_URL}/custom-query-params`, + query: queryParams, + }); + + expect(result).toEqual({ data: response }); + }); + }); + + describe("headers", () => { + const response = { customResult: { id: 1 } }; + + nock(CUSTOM_API_URL) + .get("/custom-headers") + .matchHeader("x-custom-header", "custom") + .matchHeader("x-custom-header-default", "default") + .reply(200, response); + + it("should return the data", async () => { + const result = await dataProvider.custom?.({ + method: "get", + url: `${CUSTOM_API_URL}/custom-headers`, + headers: { "x-custom-header": "custom" }, + }); + + expect(result).toEqual({ data: response }); + }); + }); + + describe("get", () => { + const response = { customResult: { id: 1 } }; + + nock(CUSTOM_API_URL).get("/custom-get").reply(200, response); + + it("should return the data", async () => { + const result = await dataProvider.custom?.({ + method: "get", + url: `${CUSTOM_API_URL}/custom-get`, + }); + + expect(result).toEqual({ data: response }); + }); + }); + + describe("post", () => { + const variables = { customPost: "customPost" }; + + const response = { customPostResult: { id: 1, ...variables } }; + + nock(CUSTOM_API_URL).post("/custom-post", variables).reply(201, response); + + it("should return the data", async () => { + const result = await dataProvider.custom?.({ + method: "post", + url: `${CUSTOM_API_URL}/custom-post`, + payload: variables, + }); + + expect(result).toEqual({ data: response }); + }); + }); + + describe("put", () => { + const variables = { customPut: "customPut" }; + + const response = { customPutResult: { id: 1, ...variables } }; + + nock(CUSTOM_API_URL).put("/custom-put", variables).reply(200, response); + + it("should return the data", async () => { + const result = await dataProvider.custom?.({ + method: "put", + url: `${CUSTOM_API_URL}/custom-put`, + payload: variables, + }); + + expect(result).toEqual({ data: response }); + }); + }); + + describe("patch", () => { + const variables = { customPatch: "customPatch" }; + + const response = { customPatchResult: { id: 1, ...variables } }; + + nock(CUSTOM_API_URL).patch("/custom-patch", variables).reply(200, response); + + it("should return the data", async () => { + const result = await dataProvider.custom?.({ + method: "patch", + url: `${CUSTOM_API_URL}/custom-patch`, + payload: variables, + }); + + expect(result).toEqual({ data: response }); + }); + }); + + describe("delete", () => { + const response = { customDeleteResult: { success: true } }; + + nock(CUSTOM_API_URL).delete("/custom-delete").reply(200, response); + + it("should return the data", async () => { + const result = await dataProvider.custom?.({ + method: "delete", + url: `${CUSTOM_API_URL}/custom-delete`, + }); + + expect(result).toEqual({ data: response }); + }); + }); +}); diff --git a/packages/rest/test/methods/deleteOne.spec.ts b/packages/rest/test/methods/deleteOne.spec.ts new file mode 100644 index 000000000000..a0a0e24b79dd --- /dev/null +++ b/packages/rest/test/methods/deleteOne.spec.ts @@ -0,0 +1,40 @@ +import nock from "nock"; +import { API_URL, createDataProvider } from ".."; + +const response = { + deleteOneResult: { + success: true, + }, +}; + +const queryParams = { queryParams: { id: 1 } }; + +nock(API_URL) + .matchHeader("x-default-header", "deleteOne") + .matchHeader("x-custom-header", "deleteOne") + .delete("/deleteOne/1") + .query(queryParams) + .reply(200, response); + +describe("deleteOne", () => { + const { dataProvider } = createDataProvider( + API_URL, + {}, + { + headers: { "x-default-header": "deleteOne" }, + }, + ); + + it("should return the data", async () => { + const result = await dataProvider.deleteOne({ + id: 1, + resource: "deleteOne", + meta: { + query: queryParams, + headers: { "x-custom-header": "deleteOne" }, + }, + }); + + expect(result).toEqual({ data: undefined }); + }); +}); diff --git a/packages/rest/test/methods/getList.spec.ts b/packages/rest/test/methods/getList.spec.ts new file mode 100644 index 000000000000..341e6a0f1de8 --- /dev/null +++ b/packages/rest/test/methods/getList.spec.ts @@ -0,0 +1,41 @@ +import nock from "nock"; +import { API_URL, createDataProvider } from ".."; + +const response = [{ id: 1 }]; + +nock(API_URL) + .matchHeader("x-default-header", "getList") + .matchHeader("x-custom-header", "getList") + .get("/getList") + .query({ + filters: [{ field: "name", operator: "eq", value: "john" }], + sorters: [{ field: "createdAt", order: "desc" }], + pagination: { currentPage: 1, pageSize: 10 }, + extra: { test: 1 }, + }) + .reply(200, response); + +describe("getList", () => { + const { dataProvider } = createDataProvider( + API_URL, + {}, + { + headers: { "x-default-header": "getList" }, + }, + ); + + it("should return the data", async () => { + const result = await dataProvider.getList({ + resource: "getList", + filters: [{ field: "name", operator: "eq", value: "john" }], + sorters: [{ field: "createdAt", order: "desc" }], + pagination: { currentPage: 1, pageSize: 10 }, + meta: { + query: { extra: { test: "1" } }, + headers: { "x-custom-header": "getList" }, + }, + }); + expect(result.data).toEqual(response); + expect(result.total).toEqual(-1); + }); +}); diff --git a/packages/rest/test/methods/getMany.spec.ts b/packages/rest/test/methods/getMany.spec.ts new file mode 100644 index 000000000000..c3a7fecf74d1 --- /dev/null +++ b/packages/rest/test/methods/getMany.spec.ts @@ -0,0 +1,38 @@ +import nock from "nock"; +import { API_URL, createDataProvider } from ".."; + +const response = { + records: [{ id: 1 }, { id: 2 }], + totalCount: 1, +}; + +const queryParams = { ids: "1,2" }; + +nock(API_URL) + .matchHeader("x-default-header", "getMany") + .matchHeader("x-custom-header", "getMany") + .get("/getMany") + .query(queryParams) + .reply(200, response); + +describe("getMany", () => { + const { dataProvider } = createDataProvider( + API_URL, + {}, + { + headers: { "x-default-header": "getMany" }, + }, + ); + + it("should return the data", async () => { + const result = await dataProvider.getMany!({ + ids: [1, 2], + resource: "getMany", + meta: { + headers: { "x-custom-header": "getMany" }, + }, + }); + + expect(result).toEqual({ data: response.records }); + }); +}); diff --git a/packages/rest/test/methods/getOne.spec.ts b/packages/rest/test/methods/getOne.spec.ts new file mode 100644 index 000000000000..7cb04de02f4c --- /dev/null +++ b/packages/rest/test/methods/getOne.spec.ts @@ -0,0 +1,40 @@ +import nock from "nock"; +import { API_URL, createDataProvider } from ".."; + +const response = { + getOneResult: { + id: 1, + }, +}; + +const queryParams = { queryParams: { id: 1 } }; + +nock(API_URL) + .matchHeader("x-default-header", "getOne") + .matchHeader("x-custom-header", "getOne") + .get("/getOne/1") + .query(queryParams) + .reply(200, response); + +describe("getOne", () => { + const { dataProvider } = createDataProvider( + API_URL, + {}, + { + headers: { "x-default-header": "getOne" }, + }, + ); + + it("should return the data", async () => { + const result = await dataProvider.getOne({ + id: 1, + resource: "getOne", + meta: { + query: queryParams, + headers: { "x-custom-header": "getOne" }, + }, + }); + + expect(result).toEqual({ data: response }); + }); +}); diff --git a/packages/rest/test/methods/update.spec.ts b/packages/rest/test/methods/update.spec.ts new file mode 100644 index 000000000000..e2ef1b9da52e --- /dev/null +++ b/packages/rest/test/methods/update.spec.ts @@ -0,0 +1,110 @@ +import nock from "nock"; +import { API_URL, createDataProvider } from ".."; + +const variables = { foo: "bar" }; + +const response = { + updateResult: { + id: 1, + ...variables, + }, +}; + +const queryParams = { queryParams: variables }; + +describe("update", () => { + const { dataProvider } = createDataProvider( + API_URL, + {}, + { headers: { "x-default-header": "update" } }, + ); + + describe("patch", () => { + describe("success", () => { + nock(API_URL) + .matchHeader("x-custom-header", "update") + .matchHeader("x-default-header", "update") + .patch("/update/1", variables) + .query(queryParams) + .reply(200, response); + + it("should return the data", async () => { + const result = await dataProvider.update({ + id: 1, + resource: "update", + variables, + meta: { + query: queryParams, + headers: { "x-custom-header": "update" }, + }, + }); + + expect(result).toEqual({ data: response }); + }); + }); + + describe("put", () => { + describe("from dataprovider config", () => { + nock(API_URL).put("/update/1", variables).reply(200, response); + + const { dataProvider } = createDataProvider(API_URL, { + update: { + getRequestMethod(params) { + return "put"; + }, + }, + }); + + it("should make request using PUT method", async () => { + const result = await dataProvider.update({ + id: 1, + resource: "update", + variables, + }); + + expect(result).toEqual({ data: response }); + }); + }); + + describe('from "method" parameter', () => { + nock(API_URL).put("/update/1", variables).reply(200, response); + + it("should make request using PUT method", async () => { + const result = await dataProvider.update({ + id: 1, + resource: "update", + variables, + meta: { + method: "put", + }, + }); + + expect(result).toEqual({ data: response }); + }); + }); + }); + }); + + describe("failure", () => { + it("should throw HttpError with message and statusCode", async () => { + nock(API_URL).patch("/update/1", variables).reply(400, { + message: "Bad Request", + }); + + await expect( + dataProvider.update({ + id: 1, + resource: "update", + variables, + }), + ).rejects.toMatchObject({ + message: JSON.stringify({ + message: "Bad Request", + id: 1, + variables, + }), + statusCode: 400, + }); + }); + }); +}); diff --git a/packages/rest/test/vitest.setup.ts b/packages/rest/test/vitest.setup.ts new file mode 100644 index 000000000000..81e3c018edb2 --- /dev/null +++ b/packages/rest/test/vitest.setup.ts @@ -0,0 +1,30 @@ +import nock from "nock"; +import { afterEach, afterAll, vi } from "vitest"; + +// nock.recorder.rec({ use_separator: false }); + +let storage: Record = {}; + +global.localStorage = { + getItem: vi.fn((key: string) => storage[key]), + setItem: vi.fn((key: string, value: string) => { + storage[key] = value; + }), + removeItem: vi.fn((key: string) => { + delete storage[key]; + }), + clear: vi.fn(() => { + storage = {}; + }), + key: vi.fn(), + length: Object.keys(storage).length, +}; + +afterEach(() => { + storage = {}; +}); + +afterAll(() => { + nock.cleanAll(); + nock.restore(); +}); diff --git a/packages/rest/tsconfig.declarations.json b/packages/rest/tsconfig.declarations.json new file mode 100644 index 000000000000..47c107f85158 --- /dev/null +++ b/packages/rest/tsconfig.declarations.json @@ -0,0 +1,23 @@ +{ + "extends": "./tsconfig.json", + "exclude": [ + "node_modules", + "dist", + "test", + "specs", + "**/test/**/*", + "**/specs/**/*", + "**/*.spec.ts", + "**/*.test.ts", + "**/*.spec.tsx", + "**/*.test.tsx" + ], + "compilerOptions": { + "outDir": "dist", + "declarationDir": "dist", + "declaration": true, + "emitDeclarationOnly": true, + "noEmit": false, + "declarationMap": true + } +} diff --git a/packages/rest/tsconfig.json b/packages/rest/tsconfig.json new file mode 100644 index 000000000000..9f939725aa29 --- /dev/null +++ b/packages/rest/tsconfig.json @@ -0,0 +1,8 @@ +{ + "include": ["src", "types"], + "extends": "../../tsconfig.build.json", + "compilerOptions": { + "rootDir": "./src", + "baseUrl": "." + } +} diff --git a/packages/rest/tsup.config.ts b/packages/rest/tsup.config.ts new file mode 100644 index 000000000000..03361f5864c7 --- /dev/null +++ b/packages/rest/tsup.config.ts @@ -0,0 +1,32 @@ +import { defineConfig } from "tsup"; +import { NodeResolvePlugin } from "@esbuild-plugins/node-resolve"; + +export default defineConfig((options) => ({ + entry: { + index: "src/index.ts", + "nestjsx-crud": "src/data-providers/nestjsx-crud/index.ts", + "simple-rest": "src/data-providers/simple-rest/index.ts", + "strapi-v4": "src/data-providers/strapi-v4/index.ts", + }, + splitting: false, + sourcemap: true, + clean: false, + minify: true, + format: ["cjs", "esm"], + outExtension: ({ format }) => ({ js: format === "cjs" ? ".cjs" : ".mjs" }), + platform: "browser", + esbuildPlugins: [ + NodeResolvePlugin({ + extensions: [".js", "ts", "tsx", "jsx"], + onResolved: (resolved) => { + if (resolved.includes("node_modules")) { + return { + external: true, + }; + } + return resolved; + }, + }), + ], + onSuccess: options.watch ? "pnpm types" : undefined, +})); diff --git a/packages/rest/vitest.config.mts b/packages/rest/vitest.config.mts new file mode 100644 index 000000000000..e0d1203de30f --- /dev/null +++ b/packages/rest/vitest.config.mts @@ -0,0 +1,23 @@ +import { defineConfig } from "vitest/config"; + +export default defineConfig({ + test: { + environment: "node", + setupFiles: ["./test/vitest.setup.ts"], + coverage: { + provider: "v8", + reporter: ["text", "json", "html"], + reportsDirectory: "./coverage", + }, + globals: true, + }, + resolve: { + alias: { + // Handle .js extension mapping from Jest config + "^(..?/.+)\\.js?$": "$1", + }, + }, + esbuild: { + target: "node22", + }, +}); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 97b01ad668ab..f7a6fbd2c958 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -2918,6 +2918,9 @@ importers: '@refinedev/react-router': specifier: ^2.0.1 version: link:../../packages/react-router + '@refinedev/rest': + specifier: ^2.0.0 + version: link:../../packages/rest '@refinedev/strapi-v4': specifier: ^7.0.0 version: link:../../packages/strapi-v4 @@ -2930,6 +2933,9 @@ importers: axios: specifier: ^1.11.0 version: 1.11.0 + ky: + specifier: ^1.10.0 + version: 1.10.0 react: specifier: ^19.1.0 version: 19.1.0 @@ -3465,7 +3471,7 @@ importers: version: 0.9.1 next: specifier: ^14.2.26 - version: 14.2.26(@babel/core@7.28.0)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(sass@1.75.0) + version: 14.2.26(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(sass@1.75.0) react: specifier: ^19.1.0 version: 19.1.0 @@ -5205,7 +5211,7 @@ importers: version: 3.0.5 next: specifier: ^14.2.26 - version: 14.2.26(@babel/core@7.28.0)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(sass@1.75.0) + version: 14.2.26(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(sass@1.75.0) next-compose-plugins: specifier: ^2.2.1 version: 2.2.1 @@ -6603,12 +6609,18 @@ importers: '@refinedev/react-router': specifier: ^2.0.1 version: link:../../packages/react-router + '@refinedev/rest': + specifier: ^2.0.0 + version: link:../../packages/rest '@tanstack/react-query': specifier: ^5.81.5 version: 5.81.5(react@19.1.0) dayjs: specifier: ^1.10.7 version: 1.11.10 + ky: + specifier: ^1.10.0 + version: 1.10.0 react: specifier: ^19.1.0 version: 19.1.0 @@ -10398,7 +10410,7 @@ importers: version: 3.0.5 next: specifier: ^14.2.26 - version: 14.2.26(@babel/core@7.28.0)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(sass@1.75.0) + version: 14.2.26(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(sass@1.75.0) nookies: specifier: ^2.5.2 version: 2.5.2 @@ -10477,7 +10489,7 @@ importers: version: 3.0.5 next: specifier: ^14.2.26 - version: 14.2.26(@babel/core@7.28.0)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(sass@1.75.0) + version: 14.2.26(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(sass@1.75.0) next-auth: specifier: ^4.24.5 version: 4.24.7(next@14.2.26(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(sass@1.75.0))(react-dom@19.1.0(react@19.1.0))(react@19.1.0) @@ -11087,7 +11099,7 @@ importers: version: 25.0.1(bufferutil@4.0.8)(utf-8-validate@5.0.10) nock: specifier: ^13.4.0 - version: 13.5.6 + version: 13.5.4 tslib: specifier: ^2.6.2 version: 2.6.2 @@ -11136,7 +11148,7 @@ importers: version: 25.0.1(bufferutil@4.0.8)(utf-8-validate@5.0.10) nock: specifier: ^13.4.0 - version: 13.5.6 + version: 13.5.4 node-fetch: specifier: ^2.6.7 version: 2.7.0(encoding@0.1.13) @@ -12359,7 +12371,7 @@ importers: version: 2.1.9(vitest@2.1.9) nock: specifier: ^13.4.0 - version: 13.5.6 + version: 13.5.4 tslib: specifier: ^2.6.2 version: 2.6.2 @@ -12751,7 +12763,7 @@ importers: version: 1.5.0 next: specifier: ^14.2.26 - version: 14.2.26(@babel/core@7.28.0)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(sass@1.75.0) + version: 14.2.26(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(sass@1.75.0) qs: specifier: ^6.10.1 version: 6.12.1 @@ -12958,7 +12970,7 @@ importers: version: 25.0.1(bufferutil@4.0.8)(utf-8-validate@5.0.10) nock: specifier: ^13.4.0 - version: 13.5.6 + version: 13.5.4 tslib: specifier: ^2.6.2 version: 2.6.2 @@ -13155,7 +13167,7 @@ importers: version: 25.0.1(bufferutil@4.0.8)(utf-8-validate@5.0.10) nock: specifier: ^13.4.0 - version: 13.5.6 + version: 13.5.4 tslib: specifier: ^2.6.2 version: 2.6.2 @@ -13201,7 +13213,7 @@ importers: version: 25.0.1(bufferutil@4.0.8)(utf-8-validate@5.0.10) nock: specifier: ^13.4.0 - version: 13.5.6 + version: 13.5.4 tslib: specifier: ^2.6.2 version: 2.6.2 @@ -13253,7 +13265,7 @@ importers: version: 6.9.15 '@vitejs/plugin-react': specifier: ^4.2.1 - version: 4.7.0(vite@5.4.15(@types/node@20.5.1)(lightningcss@1.30.1)(sass@1.75.0)(terser@5.30.4)) + version: 4.2.1(vite@5.4.15(@types/node@20.5.1)(lightningcss@1.30.1)(sass@1.75.0)(terser@5.30.4)) '@vitest/ui': specifier: ^2.1.8 version: 2.1.9(vitest@2.1.9) @@ -13262,7 +13274,7 @@ importers: version: 15.11.7 next: specifier: ^14.2.26 - version: 14.2.26(@babel/core@7.28.0)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(sass@1.75.0) + version: 14.2.26(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(sass@1.75.0) tslib: specifier: ^2.6.2 version: 2.6.2 @@ -13719,6 +13731,46 @@ importers: specifier: ^2.1.8 version: 2.1.9(@types/node@20.5.1)(@vitest/ui@2.1.9)(happy-dom@15.11.7)(jsdom@26.1.0(bufferutil@4.0.8)(utf-8-validate@5.0.10))(lightningcss@1.30.1)(msw@2.11.2(@types/node@20.5.1)(typescript@5.8.3))(sass@1.75.0)(terser@5.30.4) + packages/rest: + dependencies: + '@nestjsx/crud-request': + specifier: ^5.0.0-alpha.3 + version: 5.0.0-alpha.3 + '@refinedev/core': + specifier: ^5.0.0 + version: link:../core + deepmerge: + specifier: ^4.3.1 + version: 4.3.1 + ky: + specifier: ^1.10.0 + version: 1.10.0 + qs: + specifier: ^6.10.1 + version: 6.13.0 + devDependencies: + '@esbuild-plugins/node-resolve': + specifier: ^0.1.4 + version: 0.1.4(esbuild@0.21.5) + '@types/node': + specifier: ^20 + version: 20.5.1 + '@vitest/ui': + specifier: ^2.1.8 + version: 2.1.9(vitest@2.1.9) + nock: + specifier: ^14.0.5 + version: 14.0.10 + tsup: + specifier: ^6.7.0 + version: 6.7.0(postcss@8.5.3)(ts-node@10.9.2(@types/node@20.5.1)(typescript@5.8.3))(typescript@5.8.3) + typescript: + specifier: ^5.8.3 + version: 5.8.3 + vitest: + specifier: ^2.1.8 + version: 2.1.9(@types/node@20.5.1)(@vitest/ui@2.1.9)(happy-dom@15.11.7)(jsdom@26.1.0(bufferutil@4.0.8)(utf-8-validate@5.0.10))(lightningcss@1.30.1)(msw@2.11.2(@types/node@20.5.1)(typescript@5.8.3))(sass@1.75.0)(terser@5.30.4) + packages/simple-rest: dependencies: axios: @@ -13745,7 +13797,7 @@ importers: version: 2.1.9(vitest@2.1.9) nock: specifier: ^13.4.0 - version: 13.5.6 + version: 13.5.4 tslib: specifier: ^2.6.2 version: 2.6.2 @@ -13797,7 +13849,7 @@ importers: version: 25.0.1(bufferutil@4.0.8)(utf-8-validate@5.0.10) nock: specifier: ^13.4.0 - version: 13.5.6 + version: 13.5.4 tslib: specifier: ^2.6.2 version: 2.6.2 @@ -13852,7 +13904,7 @@ importers: version: 2.1.9(vitest@2.1.9) nock: specifier: ^13.4.0 - version: 13.5.6 + version: 13.5.4 tslib: specifier: ^2.6.2 version: 2.6.2 @@ -15181,24 +15233,12 @@ packages: peerDependencies: '@babel/core': ^7.0.0-0 - '@babel/plugin-transform-react-jsx-self@7.27.1': - resolution: {integrity: sha512-6UzkCs+ejGdZ5mFFC/OCUrv028ab2fp1znZmCZjAOBKiBK2jXD1O+BPSfX8X2qjJ75fZBMSnQn3Rq2mrBJK2mw==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - '@babel/plugin-transform-react-jsx-source@7.24.1': resolution: {integrity: sha512-1v202n7aUq4uXAieRTKcwPzNyphlCuqHHDcdSNc+vdhoTEZcFMh+L5yZuCmGaIO7bs1nJUNfHB89TZyoL48xNA==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 - '@babel/plugin-transform-react-jsx-source@7.27.1': - resolution: {integrity: sha512-zbwoTsBruTeKB9hSq73ha66iFeJHuaFkUbwvqElnygoNbj/jHRsSeokowZFN3CZ64IvEqcmmkVe89OPXc7ldAw==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - '@babel/plugin-transform-react-jsx@7.23.4': resolution: {integrity: sha512-5xOpoPguCZCRbo/JeHlloSkTA8Bld1J/E1/kLfD1nsuiW1m8tduTA1ERCgIZokDflX/IBzKcqR3l7VlRgiIfHA==} engines: {node: '>=6.9.0'} @@ -19486,9 +19526,6 @@ packages: '@repeaterjs/repeater@3.0.5': resolution: {integrity: sha512-l3YHBLAol6d/IKnB9LhpD0cEZWAoe3eFKUyTYWmFmCO2Q/WOckxLQAUyMZWwZV2M/m3+4vgRoaolFqaII82/TA==} - '@rolldown/pluginutils@1.0.0-beta.27': - resolution: {integrity: sha512-+d0F4MKMCbeVUJwG96uQ4SgAznZNSq93I3V+9NHA4OpvqG8mRCpGdKmK8l/dl02h2CCDHwW2FqilnTyDcAnqjA==} - '@rollup/plugin-babel@5.3.1': resolution: {integrity: sha512-WFfdLWU/xVWKeRQnKmIAQULUI7Il0gZnBIH/ZFO069wYIfPu+8zrfp/KMW0atmELoRDq8FbiP3VCss9MhCut7Q==} engines: {node: '>= 10.0.0'} @@ -20915,12 +20952,6 @@ packages: peerDependencies: vite: ^4.2.0 || ^5.0.0 - '@vitejs/plugin-react@4.7.0': - resolution: {integrity: sha512-gUu9hwfWvvEDBBmgtAowQCojwZmJ5mcLn3aufeCsitijs3+f2NsrPtlAWIR6OPiqljl96GVCUbLe0HyqIpVaoA==} - engines: {node: ^14.18.0 || >=16.0.0} - peerDependencies: - vite: ^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 - '@vitest/expect@2.1.9': resolution: {integrity: sha512-UJCIkTBenHeKT1TTlKMJWy1laZewsRIzYighyYiJKZreqtdxSos/S1t+ktRMQWu2CKqaarrkeszJx1cgC5tGZw==} @@ -26253,6 +26284,10 @@ packages: resolution: {integrity: sha512-dhG34DXATL5hSxJbIexCft8FChFXtmskoZYnoPWjXQuebWYCNkVeV3KkGegCK9CP1oswI/vQibS2GY7Em/sJJA==} engines: {node: '>= 8'} + ky@1.10.0: + resolution: {integrity: sha512-YRPCzHEWZffbfvmRrfwa+5nwBHwZuYiTrfDX0wuhGBPV0pA/zCqcOq93MDssON/baIkpYbvehIX5aLpMxrRhaA==} + engines: {node: '>=18'} + language-subtag-registry@0.3.22: resolution: {integrity: sha512-tN0MCzyWnoz/4nHS6uxdlFWoUZT7ABptwKPQ52Ea7URk6vll88bWBVhodtnlfEuCcKWNGoc+uGbw1cwa9IKh/w==} @@ -27517,10 +27552,14 @@ packages: no-case@3.0.4: resolution: {integrity: sha512-fgAN3jGAh+RoxUGZHTSOLJIqUc2wmoBwGR4tbpNAKmmovFoWq0OdRkb0VkldReO2a2iBT/OEulG9XSUc10r3zg==} - nock@13.5.6: - resolution: {integrity: sha512-o2zOYiCpzRqSzPj0Zt/dQ/DqZeYoaQ7TUonc/xUPjCGl9WeHpNbxgVvOquXYAaJzI0M9BXV3HTzG0p8IUAbBTQ==} + nock@13.5.4: + resolution: {integrity: sha512-yAyTfdeNJGGBFxWdzSKCBYxs5FxLbCg5X5Q4ets974hcQzG1+qCxvIyOo4j2Ry6MUlhWVMX4OoYDefAIIwupjw==} engines: {node: '>= 10.13'} + nock@14.0.10: + resolution: {integrity: sha512-Q7HjkpyPeLa0ZVZC5qpxBt5EyLczFJ91MEewQiIi9taWuA0KB/MDJlUWtON+7dGouVdADTQsf9RA7TZk6D8VMw==} + engines: {node: '>=18.20.0 <20 || >=20.12.1'} + nock@14.0.5: resolution: {integrity: sha512-R49fALR9caB6vxuSWUIaK2eBYeTloZQUFBZ4rHO+TbhMGQHtwnhdqKLYki+o+8qMgLvoBYWrp/2KzGPhxL4S6w==} engines: {node: '>=18.20.0 <20 || >=20.12.1'} @@ -29733,10 +29772,6 @@ packages: resolution: {integrity: sha512-iZiRCtNGY3QYP3pYOSSBOvQmBpQTcJccr/VcK2blpJrpPTUDjeN51mxm5nsrkCzBwsbGUj+TN9q2oPz5E13FLg==} engines: {node: '>=0.10.0'} - react-refresh@0.17.0: - resolution: {integrity: sha512-z6F7K9bV85EfseRCp2bzrpyQ0Gkw1uLoCel9XBVWPg/TjRj94SkJzUTGfOa4bs7iJvBWtQG0Wq7wnI0syw3EBQ==} - engines: {node: '>=0.10.0'} - react-remove-scroll-bar@2.3.6: resolution: {integrity: sha512-DtSYaao4mBmX+HDo5YWYdBWQwYIQQshUV/dVxFxK+KM26Wjwp1gZ6rv6OC3oujI6Bfu6Xyg3TwK533AQutsn/g==} engines: {node: '>=10'} @@ -33171,7 +33206,7 @@ snapshots: dependencies: '@ant-design/cssinjs': 1.24.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0) antd: 5.26.7(date-fns@4.1.0)(luxon@3.4.4)(moment@2.30.1)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) - next: 14.2.26(@babel/core@7.28.0)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(sass@1.75.0) + next: 14.2.26(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(sass@1.75.0) react: 19.1.0 react-dom: 19.1.0(react@19.1.0) @@ -35478,21 +35513,11 @@ snapshots: '@babel/core': 7.24.4 '@babel/helper-plugin-utils': 7.24.0 - '@babel/plugin-transform-react-jsx-self@7.27.1(@babel/core@7.28.0)': - dependencies: - '@babel/core': 7.28.0 - '@babel/helper-plugin-utils': 7.27.1 - '@babel/plugin-transform-react-jsx-source@7.24.1(@babel/core@7.24.4)': dependencies: '@babel/core': 7.24.4 '@babel/helper-plugin-utils': 7.24.0 - '@babel/plugin-transform-react-jsx-source@7.27.1(@babel/core@7.28.0)': - dependencies: - '@babel/core': 7.28.0 - '@babel/helper-plugin-utils': 7.27.1 - '@babel/plugin-transform-react-jsx@7.23.4(@babel/core@7.24.4)': dependencies: '@babel/core': 7.24.4 @@ -37749,7 +37774,7 @@ snapshots: '@eslint/config-array@0.21.0': dependencies: '@eslint/object-schema': 2.1.6 - debug: 4.4.3(supports-color@8.1.1) + debug: 4.3.4(supports-color@8.1.1) minimatch: 3.1.2 transitivePeerDependencies: - supports-color @@ -41721,8 +41746,6 @@ snapshots: '@repeaterjs/repeater@3.0.5': {} - '@rolldown/pluginutils@1.0.0-beta.27': {} - '@rollup/plugin-babel@5.3.1(@babel/core@7.28.0)(@types/babel__core@7.20.5)(rollup@2.79.1)': dependencies: '@babel/core': 7.28.0 @@ -42261,7 +42284,7 @@ snapshots: '@supabase/node-fetch': 2.6.15 '@types/phoenix': 1.6.4 '@types/ws': 8.5.10 - ws: 8.18.3(bufferutil@4.0.8)(utf-8-validate@5.0.10) + ws: 8.16.0(bufferutil@4.0.8)(utf-8-validate@5.0.10) transitivePeerDependencies: - bufferutil - utf-8-validate @@ -43451,18 +43474,6 @@ snapshots: transitivePeerDependencies: - supports-color - '@vitejs/plugin-react@4.7.0(vite@5.4.15(@types/node@20.5.1)(lightningcss@1.30.1)(sass@1.75.0)(terser@5.30.4))': - dependencies: - '@babel/core': 7.28.0 - '@babel/plugin-transform-react-jsx-self': 7.27.1(@babel/core@7.28.0) - '@babel/plugin-transform-react-jsx-source': 7.27.1(@babel/core@7.28.0) - '@rolldown/pluginutils': 1.0.0-beta.27 - '@types/babel__core': 7.20.5 - react-refresh: 0.17.0 - vite: 5.4.15(@types/node@20.5.1)(lightningcss@1.30.1)(sass@1.75.0)(terser@5.30.4) - transitivePeerDependencies: - - supports-color - '@vitest/expect@2.1.9': dependencies: '@vitest/spy': 2.1.9 @@ -50473,6 +50484,8 @@ snapshots: klona@2.0.6: {} + ky@1.10.0: {} + language-subtag-registry@0.3.22: {} language-tags@1.0.9: @@ -52264,7 +52277,7 @@ snapshots: '@panva/hkdf': 1.1.1 cookie: 0.5.0 jose: 4.15.5 - next: 14.2.26(@babel/core@7.28.0)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(sass@1.75.0) + next: 14.2.26(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(sass@1.75.0) oauth: 0.9.15 openid-client: 5.6.5 preact: 10.20.2 @@ -52279,7 +52292,7 @@ snapshots: dependencies: '@formatjs/intl-localematcher': 0.5.4 negotiator: 1.0.0 - next: 14.2.26(@babel/core@7.28.0)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(sass@1.75.0) + next: 14.2.26(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(sass@1.75.0) react: 19.1.0 use-intl: 3.25.3(react@19.1.0) @@ -52290,7 +52303,7 @@ snapshots: next-tick@1.1.0: {} - next@14.2.26(@babel/core@7.28.0)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(sass@1.75.0): + next@14.2.26(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(sass@1.75.0): dependencies: '@next/env': 14.2.26 '@swc/helpers': 0.5.5 @@ -52300,7 +52313,7 @@ snapshots: postcss: 8.4.31 react: 19.1.0 react-dom: 19.1.0(react@19.1.0) - styled-jsx: 5.1.1(@babel/core@7.28.0)(react@19.1.0) + styled-jsx: 5.1.1(react@19.1.0) optionalDependencies: '@next/swc-darwin-arm64': 14.2.26 '@next/swc-darwin-x64': 14.2.26 @@ -52347,14 +52360,20 @@ snapshots: lower-case: 2.0.2 tslib: 2.6.2 - nock@13.5.6: + nock@13.5.4: dependencies: - debug: 4.4.3(supports-color@8.1.1) + debug: 4.3.4(supports-color@8.1.1) json-stringify-safe: 5.0.1 propagate: 2.0.1 transitivePeerDependencies: - supports-color + nock@14.0.10: + dependencies: + '@mswjs/interceptors': 0.39.6 + json-stringify-safe: 5.0.1 + propagate: 2.0.1 + nock@14.0.5: dependencies: '@mswjs/interceptors': 0.38.7 @@ -54951,8 +54970,6 @@ snapshots: react-refresh@0.14.1: {} - react-refresh@0.17.0: {} - react-remove-scroll-bar@2.3.6(@types/react@19.1.8)(react@19.1.0): dependencies: react: 19.1.0 @@ -56947,12 +56964,10 @@ snapshots: stylis: 4.3.1 tslib: 2.5.0 - styled-jsx@5.1.1(@babel/core@7.28.0)(react@19.1.0): + styled-jsx@5.1.1(react@19.1.0): dependencies: client-only: 0.0.1 react: 19.1.0 - optionalDependencies: - '@babel/core': 7.28.0 styled-jsx@5.1.6(@babel/core@7.28.0)(react@19.1.0): dependencies: