From d1bdbdf6b9e9745e08636c76e3d1331e0d3dba76 Mon Sep 17 00:00:00 2001 From: Juan M Date: Tue, 2 Apr 2024 00:51:40 -0300 Subject: [PATCH 1/2] Search drops --- packages/drops/src/DropsClient.ts | 131 ++++++++++++------ packages/drops/src/queries/PaginatedDrop.ts | 45 +----- packages/drops/src/queries/SearchDrops.ts | 60 ++++++++ packages/drops/src/types/DropImageResponse.ts | 10 ++ packages/drops/src/types/DropResponse.ts | 35 +++++ packages/drops/src/types/SearchDropsInput.ts | 5 + packages/drops/src/types/index.ts | 3 + packages/utils/src/format/index.ts | 1 + .../src/format/removeSpecialCharacters.ts | 10 ++ packages/utils/src/index.ts | 2 + packages/utils/src/validation/index.ts | 1 + packages/utils/src/validation/isNumeric.ts | 18 +++ 12 files changed, 236 insertions(+), 85 deletions(-) create mode 100644 packages/drops/src/queries/SearchDrops.ts create mode 100644 packages/drops/src/types/DropImageResponse.ts create mode 100644 packages/drops/src/types/DropResponse.ts create mode 100644 packages/drops/src/types/SearchDropsInput.ts create mode 100644 packages/utils/src/format/index.ts create mode 100644 packages/utils/src/format/removeSpecialCharacters.ts create mode 100644 packages/utils/src/validation/index.ts create mode 100644 packages/utils/src/validation/isNumeric.ts diff --git a/packages/drops/src/DropsClient.ts b/packages/drops/src/DropsClient.ts index 865a070b..b9dc76ee 100644 --- a/packages/drops/src/DropsClient.ts +++ b/packages/drops/src/DropsClient.ts @@ -4,13 +4,15 @@ import { DropResponse as ProviderDropResponse, } from '@poap-xyz/providers'; import { Drop } from './domain/Drop'; +import { PaginatedDropsResponse, PAGINATED_DROPS_QUERY } from './queries'; import { - PaginatedDropsResponse, - PAGINATED_DROPS_QUERY, + CreateDropsInput, DropImageResponse, DropResponse, -} from './queries'; -import { CreateDropsInput, FetchDropsInput, UpdateDropsInput } from './types'; + FetchDropsInput, + SearchDropsInput, + UpdateDropsInput, +} from './types'; import { PaginatedResult, nextCursor, @@ -19,8 +21,12 @@ import { createBetweenFilter, createFilter, createInFilter, + Order, + isNumeric, + removeSpecialCharacters, } from '@poap-xyz/utils'; import { DropImage } from './types/dropImage'; +import { SEARCH_DROPS_QUERY, SearchDropsResponse } from './queries/SearchDrops'; /** * Represents a client for working with POAP drops. @@ -32,8 +38,8 @@ export class DropsClient { * Creates a new DropsClient object. * * @constructor - * @param {CompassProvider} CompassProvider - The provider for the POAP compass API. - * @param {DropApiProvider} DropApiProvider - The provider for the POAP drop API. + * @param {CompassProvider} compassProvider - The provider for the POAP compass API. + * @param {DropApiProvider} dropApiProvider - The provider for the POAP drop API. */ constructor( private compassProvider: CompassProvider, @@ -78,41 +84,48 @@ export class DropsClient { variables, ); - const drops = data.drops.map((drop) => { - const { imageUrl, originalImageUrl } = this.computeDropImages(drop); - - return new Drop({ - id: Number(drop.id), - fancyId: drop.fancy_id, - name: drop.name, - description: drop.description, - city: drop.city, - country: drop.country, - channel: drop.channel, - platform: drop.platform, - locationType: drop.location_type, - dropUrl: drop.drop_url, - imageUrl, - originalImageUrl, - animationUrl: drop.animation_url, - year: Number(drop.year), - startDate: new Date(drop.start_date), - timezone: drop.timezone, - private: drop.private, - createdDate: new Date(drop.created_date), - poapCount: drop.stats_by_chain_aggregate.aggregate.sum - ? Number(drop.stats_by_chain_aggregate.aggregate.sum.poap_count) - : 0, - transferCount: drop.stats_by_chain_aggregate.aggregate.sum - ? Number(drop.stats_by_chain_aggregate.aggregate.sum.transfer_count) - : 0, - emailReservationCount: drop.email_claims_stats - ? Number(drop.email_claims_stats.total) - : 0, - expiryDate: new Date(drop.expiry_date), - endDate: new Date(drop.end_date), - }); - }); + const drops = data.drops.map( + (drop: DropResponse): Drop => this.mapDrop(drop), + ); + + return new PaginatedResult( + drops, + nextCursor(drops.length, limit, offset), + ); + } + + /** + * Searches drops based on the specified input. + * + * @async + * @method + * @param {SearchDropsInput} input - The input for searching drops. + * @returns {Promise>} A paginated result of drops. + */ + async search(input: SearchDropsInput): Promise> { + const { search, offset, limit } = input; + + if (!search) { + return new PaginatedResult([], null); + } + + const variables = { + limit, + offset, + ...(isNumeric(search) && { orderBy: { id: Order.ASC } }), + args: { + search: removeSpecialCharacters(search), + }, + }; + + const { data } = await this.compassProvider.request( + SEARCH_DROPS_QUERY, + variables, + ); + + const drops = data.search_drops.map( + (drop: DropResponse): Drop => this.mapDrop(drop), + ); return new PaginatedResult( drops, @@ -226,4 +239,40 @@ export class DropsClient { return { ...images }; } + + private mapDrop(drop: DropResponse): Drop { + const { imageUrl, originalImageUrl } = this.computeDropImages(drop); + + return new Drop({ + id: Number(drop.id), + fancyId: drop.fancy_id, + name: drop.name, + description: drop.description, + city: drop.city, + country: drop.country, + channel: drop.channel, + platform: drop.platform, + locationType: drop.location_type, + dropUrl: drop.drop_url, + imageUrl, + originalImageUrl, + animationUrl: drop.animation_url, + year: Number(drop.year), + startDate: new Date(drop.start_date), + timezone: drop.timezone, + private: drop.private, + createdDate: new Date(drop.created_date), + poapCount: drop.stats_by_chain_aggregate.aggregate.sum + ? Number(drop.stats_by_chain_aggregate.aggregate.sum.poap_count) + : 0, + transferCount: drop.stats_by_chain_aggregate.aggregate.sum + ? Number(drop.stats_by_chain_aggregate.aggregate.sum.transfer_count) + : 0, + emailReservationCount: drop.email_claims_stats + ? Number(drop.email_claims_stats.total) + : 0, + expiryDate: new Date(drop.expiry_date), + endDate: new Date(drop.end_date), + }); + } } diff --git a/packages/drops/src/queries/PaginatedDrop.ts b/packages/drops/src/queries/PaginatedDrop.ts index 78accd75..04d34525 100644 --- a/packages/drops/src/queries/PaginatedDrop.ts +++ b/packages/drops/src/queries/PaginatedDrop.ts @@ -1,4 +1,4 @@ -import { DropImageGatewayType } from '../types/dropImage'; +import { DropResponse } from '../types/DropResponse'; export const PAGINATED_DROPS_QUERY = ` query PaginatedDrops( @@ -48,49 +48,6 @@ export const PAGINATED_DROPS_QUERY = ` } `; -export interface DropImageGatewayResponse { - type: DropImageGatewayType; - url: string; -} - -export interface DropImageResponse { - gateways: Array; -} - -export interface DropResponse { - id: number; - fancy_id: string; - name: string; - description: string; - city: string; - country: string; - channel: string; - platform: string; - location_type: string; - drop_url: string; - image_url: string; - animation_url: string; - year: number; - start_date: string; - timezone: string; - private: boolean; - created_date: string; - expiry_date: string; - end_date: string; - stats_by_chain_aggregate: { - aggregate: { - sum: { - transfer_count: number; - poap_count: number; - }; - }; - }; - email_claims_stats: { - total: number; - }; - drop_image?: DropImageResponse; -} - export interface PaginatedDropsResponse { data: { drops: DropResponse[]; diff --git a/packages/drops/src/queries/SearchDrops.ts b/packages/drops/src/queries/SearchDrops.ts new file mode 100644 index 00000000..4cee8066 --- /dev/null +++ b/packages/drops/src/queries/SearchDrops.ts @@ -0,0 +1,60 @@ +import { DropResponse } from '../types/DropResponse'; + +export const SEARCH_DROPS_QUERY = ` + query SearchDrops( + $limit: Int! + $offset: Int! + $args: search_drops_args! + $orderBy: [drops_order_by!] + ) { + search_drops( + limit: $limit + offset: $offset + args: $args + order_by: $orderBy + ) { + id + fancy_id + name + description + city + country + channel + platform + location_type + drop_url + image_url + animation_url + year + start_date + timezone + private + created_date + expiry_date + end_date + stats_by_chain_aggregate { + aggregate { + sum { + transfer_count + poap_count + } + } + } + email_claims_stats { + total + } + drop_image { + gateways { + type + url + } + } + } + } +`; + +export interface SearchDropsResponse { + data: { + search_drops: DropResponse[]; + }; +} diff --git a/packages/drops/src/types/DropImageResponse.ts b/packages/drops/src/types/DropImageResponse.ts new file mode 100644 index 00000000..5de23371 --- /dev/null +++ b/packages/drops/src/types/DropImageResponse.ts @@ -0,0 +1,10 @@ +import { DropImageGatewayType } from './dropImage'; + +interface DropImageGatewayResponse { + type: DropImageGatewayType; + url: string; +} + +export interface DropImageResponse { + gateways: Array; +} diff --git a/packages/drops/src/types/DropResponse.ts b/packages/drops/src/types/DropResponse.ts new file mode 100644 index 00000000..0284b1e2 --- /dev/null +++ b/packages/drops/src/types/DropResponse.ts @@ -0,0 +1,35 @@ +import { DropImageResponse } from './DropImageResponse'; + +export interface DropResponse { + id: number; + fancy_id: string; + name: string; + description: string; + city: string; + country: string; + channel: string; + platform: string; + location_type: string; + drop_url: string; + image_url: string; + animation_url: string; + year: number; + start_date: string; + timezone: string; + private: boolean; + created_date: string; + expiry_date: string; + end_date: string; + stats_by_chain_aggregate: { + aggregate: { + sum: { + transfer_count: number; + poap_count: number; + }; + }; + }; + email_claims_stats: { + total: number; + }; + drop_image?: DropImageResponse; +} diff --git a/packages/drops/src/types/SearchDropsInput.ts b/packages/drops/src/types/SearchDropsInput.ts new file mode 100644 index 00000000..682de91a --- /dev/null +++ b/packages/drops/src/types/SearchDropsInput.ts @@ -0,0 +1,5 @@ +import { PaginationInput } from '@poap-xyz/utils'; + +export interface SearchDropsInput extends PaginationInput { + search: string; +} diff --git a/packages/drops/src/types/index.ts b/packages/drops/src/types/index.ts index e3365cb9..c2f59ef7 100644 --- a/packages/drops/src/types/index.ts +++ b/packages/drops/src/types/index.ts @@ -1 +1,4 @@ export * from './input'; +export * from './DropImageResponse'; +export * from './DropResponse'; +export * from './SearchDropsInput'; diff --git a/packages/utils/src/format/index.ts b/packages/utils/src/format/index.ts new file mode 100644 index 00000000..9aa215bd --- /dev/null +++ b/packages/utils/src/format/index.ts @@ -0,0 +1 @@ +export * from './removeSpecialCharacters'; diff --git a/packages/utils/src/format/removeSpecialCharacters.ts b/packages/utils/src/format/removeSpecialCharacters.ts new file mode 100644 index 00000000..74d07a0e --- /dev/null +++ b/packages/utils/src/format/removeSpecialCharacters.ts @@ -0,0 +1,10 @@ +/** + * Removes all special characters from a given string, leaving only alphanumeric + * characters and spaces. + * + * @param {string} str - The string from which to remove special characters. + * @returns {string} The cleaned string with only alphanumeric characters and spaces. + */ +export function removeSpecialCharacters(str: string): string { + return str.replace(/[^a-zA-Z0-9 ]/g, ''); +} diff --git a/packages/utils/src/index.ts b/packages/utils/src/index.ts index ee3b5313..8a2e9f4f 100644 --- a/packages/utils/src/index.ts +++ b/packages/utils/src/index.ts @@ -17,3 +17,5 @@ export { createBetweenFilter, creatPrivateFilter, } from './queries/utils'; +export * from './format'; +export * from './validation'; diff --git a/packages/utils/src/validation/index.ts b/packages/utils/src/validation/index.ts new file mode 100644 index 00000000..9c53f7b9 --- /dev/null +++ b/packages/utils/src/validation/index.ts @@ -0,0 +1 @@ +export * from './isNumeric'; diff --git a/packages/utils/src/validation/isNumeric.ts b/packages/utils/src/validation/isNumeric.ts new file mode 100644 index 00000000..cd0409d4 --- /dev/null +++ b/packages/utils/src/validation/isNumeric.ts @@ -0,0 +1,18 @@ +/** + * Checks if a value is numeric. + * + * @param {unknown} value The value to check. + * @param {boolean} allowNegative Whether to allow negative numbers. + * @returns {boolean} True if the value is numeric, false otherwise. + */ +export function isNumeric(value: unknown, allowNegative = false): boolean { + if (typeof value === 'string') { + return (allowNegative ? /^-?\d+$/ : /^\d+$/).test(value); + } + + if (typeof value === 'number') { + return true; + } + + return false; +} From 0a6084b970a22f7ac423a9c8396465023b511e77 Mon Sep 17 00:00:00 2001 From: Juan M Date: Tue, 2 Apr 2024 01:05:19 -0300 Subject: [PATCH 2/2] Documentation on search drops --- docs/pages/packages/drops.mdx | 1 + docs/pages/packages/drops/SearchDrops.mdx | 31 +++++++++++++++++++++++ docs/pages/packages/drops/_meta.json | 3 +++ packages/drops/README.md | 1 + 4 files changed, 36 insertions(+) create mode 100644 docs/pages/packages/drops/SearchDrops.mdx create mode 100644 docs/pages/packages/drops/_meta.json diff --git a/docs/pages/packages/drops.mdx b/docs/pages/packages/drops.mdx index 62dd820c..d58a9c6c 100644 --- a/docs/pages/packages/drops.mdx +++ b/docs/pages/packages/drops.mdx @@ -10,6 +10,7 @@ - Update a Drop attributes - Fetch a single Drop - Fetch multiple Drops +- Search Drops ## Installation diff --git a/docs/pages/packages/drops/SearchDrops.mdx b/docs/pages/packages/drops/SearchDrops.mdx new file mode 100644 index 00000000..03d6512b --- /dev/null +++ b/docs/pages/packages/drops/SearchDrops.mdx @@ -0,0 +1,31 @@ +# Search Drops + +There are two ways that drops can be searched, by exact word in name or fuzzy +search in name and description. + +## Exact word search + +When searching for a whole word, use `fetch` method: + +```typescript + const data: PaginatedResult = await dropsClient.fetch({ + sortField: DropsSortFields.Name, + sortDir: Order.ASC, + limit: 3, + offset: 0, + name: 'POAP', + }); +``` + +## Fuzzy search + +When given a generic search input, and to match any part of the words used in +the name of the description, use `search` method: + +```typescript + const data: PaginatedResult = await dropsClient.search({ + limit: 3, + offset: 0, + search: 'POAP', + }); +``` diff --git a/docs/pages/packages/drops/_meta.json b/docs/pages/packages/drops/_meta.json new file mode 100644 index 00000000..23cd2cff --- /dev/null +++ b/docs/pages/packages/drops/_meta.json @@ -0,0 +1,3 @@ +{ + "SearchDrops": "Search Drops" +} diff --git a/packages/drops/README.md b/packages/drops/README.md index 62dd820c..d58a9c6c 100644 --- a/packages/drops/README.md +++ b/packages/drops/README.md @@ -10,6 +10,7 @@ - Update a Drop attributes - Fetch a single Drop - Fetch multiple Drops +- Search Drops ## Installation