From 0defebc23f7b0fb58056496ddb21a0998b75d25e Mon Sep 17 00:00:00 2001 From: daiagi Date: Wed, 4 Oct 2023 19:54:37 +0700 Subject: [PATCH 01/30] proper count on token entity --- src/mappings/nfts/burn.ts | 5 +- src/mappings/nfts/mint.ts | 3 + src/mappings/nfts/setMetadata.ts | 18 ++- src/mappings/shared/handleTokenEntity.ts | 153 +++++++++++++++++++---- src/mappings/uniques/burn.ts | 3 + src/mappings/uniques/mint.ts | 8 +- src/mappings/uniques/setMetadata.ts | 11 +- 7 files changed, 152 insertions(+), 49 deletions(-) diff --git a/src/mappings/nfts/burn.ts b/src/mappings/nfts/burn.ts index 5fb2387..e11c842 100644 --- a/src/mappings/nfts/burn.ts +++ b/src/mappings/nfts/burn.ts @@ -1,10 +1,11 @@ -import { getWith } from '@kodadot1/metasquid/entity' +import { getWith } from '@kodadot1/metasquid/entity' import { NFTEntity as NE } from '../../model' import { unwrap } from '../utils/extract' import { debug, pending, success } from '../utils/logger' import { Action, Context, createTokenId } from '../utils/types' import { createEvent } from '../shared/event' import { calculateCollectionOwnerCountAndDistribution } from '../utils/helper' +import { eventHandlers } from '../shared/handleTokenEntity' import { getBurnTokenEvent } from './getters' const OPERATION = Action.BURN @@ -31,6 +32,8 @@ export async function handleTokenBurn(context: Context): Promise { entity.collection.ownerCount = ownerCount entity.collection.distribution = distribution + await eventHandlers.burnHandler(context, entity) + success(OPERATION, `${id} by ${event.caller}}`) await context.store.save(entity) const meta = entity.metadata ?? '' diff --git a/src/mappings/nfts/mint.ts b/src/mappings/nfts/mint.ts index 7c63eea..07d239c 100644 --- a/src/mappings/nfts/mint.ts +++ b/src/mappings/nfts/mint.ts @@ -8,6 +8,7 @@ import { unwrap } from '../utils/extract' import { debug, pending, success } from '../utils/logger' import { Action, Context, createTokenId } from '../utils/types' import { calculateCollectionOwnerCountAndDistribution, versionOf } from '../utils/helper' +import { eventHandlers } from '../shared/handleTokenEntity' import { getCreateTokenEvent } from './getters' const OPERATION = Action.MINT @@ -60,6 +61,8 @@ export async function handleTokenCreate(context: Context): Promise { final.name = metadata?.name final.image = metadata?.image final.media = metadata?.animationUrl + + await eventHandlers.mintHandler(context, collection, final) } success(OPERATION, `${final.id}`) diff --git a/src/mappings/nfts/setMetadata.ts b/src/mappings/nfts/setMetadata.ts index f914625..9ea3cf7 100644 --- a/src/mappings/nfts/setMetadata.ts +++ b/src/mappings/nfts/setMetadata.ts @@ -1,4 +1,4 @@ -import { get, getOptional } from '@kodadot1/metasquid/entity' +import { get, getOptional, getWith } from '@kodadot1/metasquid/entity' import { isFetchable } from '@kodadot1/minipfs' import { unwrap } from '../utils/extract' import { Context, isNFT } from '../utils/types' @@ -6,7 +6,7 @@ import { CollectionEntity, NFTEntity } from '../../model' import { handleMetadata } from '../shared/metadata' import { debug, warn } from '../utils/logger' import { updateItemMetadataByCollection } from '../utils/cache' -import { handleTokenEntity } from '../shared/handleTokenEntity' +import { eventHandlers } from '../shared/handleTokenEntity' import { tokenIdOf } from './types' import { getMetadataEvent } from './getters' @@ -51,17 +51,15 @@ export async function handleMetadataSet(context: Context): Promise { warn(OPERATION, `collection ${event.collectionId} not found`) return } - const nft = final as NFTEntity - const token = await handleTokenEntity(context, collection, nft) - if (token) { - nft.token = token + if (final instanceof NFTEntity) { + await eventHandlers.setMetadataHandler(context, collection, final) } } - } - await context.store.save(final) + await context.store.save(final) - if (!event.sn && final.metadata) { - await updateItemMetadataByCollection(context.store, event.collectionId) + if (!event.sn && final.metadata) { + await updateItemMetadataByCollection(context.store, event.collectionId) + } } } diff --git a/src/mappings/shared/handleTokenEntity.ts b/src/mappings/shared/handleTokenEntity.ts index c911b71..f87076a 100644 --- a/src/mappings/shared/handleTokenEntity.ts +++ b/src/mappings/shared/handleTokenEntity.ts @@ -1,44 +1,145 @@ -import { create, getOptional } from '@kodadot1/metasquid/entity' +import { create, getOptional, getWith } from '@kodadot1/metasquid/entity' import md5 from 'md5' import { CollectionEntity as CE, NFTEntity as NE, TokenEntity as TE } from '../../model' -import { warn } from '../utils/logger' +import { debug, warn } from '../utils/logger' import { Context } from '../utils/types' const OPERATION = 'TokenEntity' as any -export async function handleTokenEntity(context: Context, collection: CE, nft: NE): Promise { - const nftMedia = nft.image || nft.media +function wait(seconds = 60): Promise { + return new Promise((resolve) => setTimeout(resolve, seconds * 1000)) +} + +function generateTokenId(collection: CE, nftMedia: string): string { + return `${collection.id}-${md5(nftMedia)}` +} + +async function createToken(context: Context, collection: CE, nft: NE): Promise { + const nftMedia = nft.image ?? nft.media if (!nftMedia || nftMedia === '') { warn(OPERATION, `MISSING NFT MEDIA ${nft.id}`) return } + const tokenId = generateTokenId(collection, nftMedia) + debug(OPERATION, { createToken: `Create TOKEN ${tokenId} for NFT ${nft.id}` }) + const tokenName = typeof nft.name === 'string' ? nft.name?.replace(/([#_]\d+$)/g, '').trim() : '' - const tokenId = `${collection.id}-${md5(nftMedia)}` - let token = await getOptional(context.store, TE, tokenId) + const token = create(TE, tokenId, { + createdAt: nft.createdAt, + collection, + name: tokenName, + count: 1, + hash: md5(tokenId), + image: nft.image, + media: nft.media, + blockNumber: nft.blockNumber, + updatedAt: nft.updatedAt, + id: tokenId, + }) - if (!token) { - const tokenName = (typeof nft.name === 'string' ? nft.name?.replace(/([#_]\d+$)/g, '').trim() : '') - - token = create(TE, tokenId, { - createdAt: nft.createdAt, - collection, - name: tokenName, - count: 1, - hash: md5(tokenId), - image: nft.image, - media: nft.media, - blockNumber: nft.blockNumber, - updatedAt: nft.updatedAt, - id: tokenId, - }) - } else { - token.count += 1 - } + nft.token = token + await context.store.save(token) + await context.store.save(nft) - token.updatedAt = nft.updatedAt - token.blockNumber = nft.blockNumber + debug(OPERATION, { createToken: `Token After Create:` }) + const newToken = await getOptional(context.store, TE, token.id) + const nftIds = (await getWith(context.store, TE, token.id, { nfts: true })).nfts.map((nft) => nft.id) + debug(OPERATION, { createToken: `id: ${newToken?.id}, count: ${newToken?.count}, last_nft: ${nftIds.slice(-1)} ` }) + debug(OPERATION, { nftTokenRef: `nft.token.id: ${nft.token?.id}` }) + // await wait(5) + + return token +} +async function addNftToToken(context: Context, nft: NE, token: TE): Promise { + debug(OPERATION, { updateToken: `Update TOKEN ${token.id} for NFT ${nft.id}` }) + token.count += 1 + token.updatedAt = nft.updatedAt + nft.token = token await context.store.save(token) + await context.store.save(nft) + + debug(OPERATION, { updateToken: `Token After Update:` }) + const newToken = await getOptional(context.store, TE, token.id) + const nftIds = (await getWith(context.store, TE, token.id, { nfts: true })).nfts.map((nft) => nft.id) + debug(OPERATION, { updateToken: `id: ${newToken?.id}, count: ${newToken?.count}, last_nft: ${nftIds.slice(-1)} ` }) + // await wait(5) return token } + +async function removeNftFromToken(context: Context, nft: NE, token: TE): Promise { + if (!token) { + return + } + debug(OPERATION, { handleExistingToken: `Unlink TOKEN ${token.id} from NFT ${nft.id}` }) + + await context.store.update(NE, nft.id, { token: null }) + await context.store.update(TE, token.id, { count: token.count - 1, updatedAt: nft.updatedAt }) + + const tokenNfts = (await getWith(context.store, TE, token.id, { nfts: true })).nfts + if (tokenNfts.length === 0) { + debug(OPERATION, { deleteEmptyToken: `delete empty token ${token.id}` }) + await context.store.delete(TE, token.id) + } +} + +async function mintHandler(context: Context, collection: CE, nft: NE): Promise { + const nftMedia = nft.image ?? nft.media + debug(OPERATION, { mintHandler: `Handle mint for NFT ${nft.id}` }) + + if (!nftMedia || nftMedia === '') { + warn(OPERATION, `MISSING NFT MEDIA ${nft.id}`) + return + } + + const existingToken = await getOptional(context.store, TE, generateTokenId(collection, nftMedia)) + return await (existingToken ? addNftToToken(context, nft, existingToken) : createToken(context, collection, nft)) +} + +async function handleMetadataSet(context: Context, collection: CE, nft: NE): Promise { + const nftMedia = nft.image ?? nft.media + + if (!nftMedia || nftMedia === '') { + warn(OPERATION, `MISSING NFT MEDIA ${nft.id}`) + return + } + try { + const nftWithToken = await getWith(context.store, NE, nft.id, { token: true }) + const token = nftWithToken.token + if (token) { + debug(OPERATION, { removeNftFromToken: `removeNftFromToken TOKEN ${token.id} for NFT ${nft.id}` }) + await removeNftFromToken(context, nft, token) + } + } catch (error) { + warn(OPERATION, `ERROR ${error}`) + } + const existingToken = await getOptional(context.store, TE, generateTokenId(collection, nftMedia)) + debug(OPERATION, { existingToken: `existingToken ${Boolean(existingToken)}` }) + return await (existingToken ? addNftToToken(context, nft, existingToken) : createToken(context, collection, nft)) +} + +async function handleBurn(context: Context, nft: NE): Promise { + const nftMedia = nft.image ?? nft.media + + if (!nftMedia || nftMedia === '') { + warn(OPERATION, `MISSING NFT MEDIA ${nft.id}`) + return + } + + const token = await getOptional(context.store, TE, generateTokenId(nft.collection, nftMedia)) + + if (!token) { + return + } + + debug(OPERATION, { handleBurn: `Handle burn for NFT ${nft.id}` }) + + await context.store.update(TE, token.id, { count: token.count - 1, updatedAt: nft.updatedAt }) +} + +export const eventHandlers = { + setMetadataHandler: handleMetadataSet, + mintHandler, + burnHandler: handleBurn, +} diff --git a/src/mappings/uniques/burn.ts b/src/mappings/uniques/burn.ts index 5fb2387..00fa4f3 100644 --- a/src/mappings/uniques/burn.ts +++ b/src/mappings/uniques/burn.ts @@ -5,6 +5,7 @@ import { debug, pending, success } from '../utils/logger' import { Action, Context, createTokenId } from '../utils/types' import { createEvent } from '../shared/event' import { calculateCollectionOwnerCountAndDistribution } from '../utils/helper' +import { eventHandlers } from '../shared/handleTokenEntity' import { getBurnTokenEvent } from './getters' const OPERATION = Action.BURN @@ -31,6 +32,8 @@ export async function handleTokenBurn(context: Context): Promise { entity.collection.ownerCount = ownerCount entity.collection.distribution = distribution + await eventHandlers.burnHandler(context, entity) + success(OPERATION, `${id} by ${event.caller}}`) await context.store.save(entity) const meta = entity.metadata ?? '' diff --git a/src/mappings/uniques/mint.ts b/src/mappings/uniques/mint.ts index ee3d7a6..a704e8d 100644 --- a/src/mappings/uniques/mint.ts +++ b/src/mappings/uniques/mint.ts @@ -8,7 +8,7 @@ import { unwrap } from '../utils/extract' import { debug, pending, success } from '../utils/logger' import { Action, Context, createTokenId } from '../utils/types' import { versionOf , calculateCollectionOwnerCountAndDistribution } from '../utils/helper' -import { handleTokenEntity } from '../shared/handleTokenEntity' +import { eventHandlers } from '../shared/handleTokenEntity' import { getCreateTokenEvent } from './getters' const OPERATION = Action.MINT @@ -63,10 +63,8 @@ export async function handleTokenCreate(context: Context): Promise { final.media = metadata?.animationUrl } - const token = await handleTokenEntity(context, collection, final) - if (token) { - final.token = token - } + await eventHandlers.mintHandler(context, collection, final) + success(OPERATION, `${final.id}`) await context.store.save(final) diff --git a/src/mappings/uniques/setMetadata.ts b/src/mappings/uniques/setMetadata.ts index a140faa..26edcaf 100644 --- a/src/mappings/uniques/setMetadata.ts +++ b/src/mappings/uniques/setMetadata.ts @@ -1,4 +1,4 @@ -import { get, getOptional } from '@kodadot1/metasquid/entity' +import { get, getOptional, getWith } from '@kodadot1/metasquid/entity' import { isFetchable } from '@kodadot1/minipfs' import { unwrap } from '../utils/extract' import { Context, isNFT } from '../utils/types' @@ -6,7 +6,7 @@ import { CollectionEntity, NFTEntity } from '../../model' import { handleMetadata } from '../shared/metadata' import { debug, warn } from '../utils/logger' import { updateItemMetadataByCollection } from '../utils/cache' -import { handleTokenEntity } from '../shared/handleTokenEntity' +import { eventHandlers } from '../shared/handleTokenEntity' import { tokenIdOf } from './types' import { getMetadataEvent } from './getters' @@ -52,11 +52,8 @@ export async function handleMetadataSet(context: Context): Promise { warn(OPERATION, `collection ${event.collectionId} not found`) return } - const nft = final as NFTEntity - const token = await handleTokenEntity(context, collection, nft) - if (token) { - nft.token = token - } + + await eventHandlers.setMetadataHandler(context, collection, final as NFTEntity) } } From 2456fc3c998551333709afd6f881abec9fd20a01 Mon Sep 17 00:00:00 2001 From: daiagi Date: Wed, 4 Oct 2023 20:11:33 +0700 Subject: [PATCH 02/30] remove exapnsive debug logs --- src/mappings/shared/handleTokenEntity.ts | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/src/mappings/shared/handleTokenEntity.ts b/src/mappings/shared/handleTokenEntity.ts index f87076a..8c97539 100644 --- a/src/mappings/shared/handleTokenEntity.ts +++ b/src/mappings/shared/handleTokenEntity.ts @@ -41,12 +41,6 @@ async function createToken(context: Context, collection: CE, nft: NE): Promise(context.store, TE, token.id) - const nftIds = (await getWith(context.store, TE, token.id, { nfts: true })).nfts.map((nft) => nft.id) - debug(OPERATION, { createToken: `id: ${newToken?.id}, count: ${newToken?.count}, last_nft: ${nftIds.slice(-1)} ` }) - debug(OPERATION, { nftTokenRef: `nft.token.id: ${nft.token?.id}` }) - // await wait(5) return token } @@ -59,11 +53,6 @@ async function addNftToToken(context: Context, nft: NE, token: TE): Promise await context.store.save(token) await context.store.save(nft) - debug(OPERATION, { updateToken: `Token After Update:` }) - const newToken = await getOptional(context.store, TE, token.id) - const nftIds = (await getWith(context.store, TE, token.id, { nfts: true })).nfts.map((nft) => nft.id) - debug(OPERATION, { updateToken: `id: ${newToken?.id}, count: ${newToken?.count}, last_nft: ${nftIds.slice(-1)} ` }) - // await wait(5) return token } From 3ec22b8eaacb388e0f24812abc4ec3a753603ec6 Mon Sep 17 00:00:00 2001 From: daiagi Date: Wed, 4 Oct 2023 20:21:00 +0700 Subject: [PATCH 03/30] slim down - pass only collection id --- src/mappings/shared/handleTokenEntity.ts | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/src/mappings/shared/handleTokenEntity.ts b/src/mappings/shared/handleTokenEntity.ts index 8c97539..73728c7 100644 --- a/src/mappings/shared/handleTokenEntity.ts +++ b/src/mappings/shared/handleTokenEntity.ts @@ -6,12 +6,9 @@ import { Context } from '../utils/types' const OPERATION = 'TokenEntity' as any -function wait(seconds = 60): Promise { - return new Promise((resolve) => setTimeout(resolve, seconds * 1000)) -} -function generateTokenId(collection: CE, nftMedia: string): string { - return `${collection.id}-${md5(nftMedia)}` +function generateTokenId(collectionId: string, nftMedia: string): string { + return `${collectionId}-${md5(nftMedia)}` } async function createToken(context: Context, collection: CE, nft: NE): Promise { @@ -20,7 +17,7 @@ async function createToken(context: Context, collection: CE, nft: NE): Promise(context.store, TE, generateTokenId(collection, nftMedia)) + const existingToken = await getOptional(context.store, TE, generateTokenId(collection.id, nftMedia)) return await (existingToken ? addNftToToken(context, nft, existingToken) : createToken(context, collection, nft)) } @@ -103,7 +100,7 @@ async function handleMetadataSet(context: Context, collection: CE, nft: NE): Pro } catch (error) { warn(OPERATION, `ERROR ${error}`) } - const existingToken = await getOptional(context.store, TE, generateTokenId(collection, nftMedia)) + const existingToken = await getOptional(context.store, TE, generateTokenId(collection.id, nftMedia)) debug(OPERATION, { existingToken: `existingToken ${Boolean(existingToken)}` }) return await (existingToken ? addNftToToken(context, nft, existingToken) : createToken(context, collection, nft)) } @@ -116,7 +113,7 @@ async function handleBurn(context: Context, nft: NE): Promise { return } - const token = await getOptional(context.store, TE, generateTokenId(nft.collection, nftMedia)) + const token = await getOptional(context.store, TE, generateTokenId(nft.collection.id, nftMedia)) if (!token) { return From 0bcaafccb89f55565bb25621384da4ab9c734a28 Mon Sep 17 00:00:00 2001 From: daiagi Date: Wed, 4 Oct 2023 21:34:54 +0700 Subject: [PATCH 04/30] better debug logging --- src/mappings/shared/handleTokenEntity.ts | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/src/mappings/shared/handleTokenEntity.ts b/src/mappings/shared/handleTokenEntity.ts index 73728c7..eca76c4 100644 --- a/src/mappings/shared/handleTokenEntity.ts +++ b/src/mappings/shared/handleTokenEntity.ts @@ -6,7 +6,6 @@ import { Context } from '../utils/types' const OPERATION = 'TokenEntity' as any - function generateTokenId(collectionId: string, nftMedia: string): string { return `${collectionId}-${md5(nftMedia)}` } @@ -38,19 +37,17 @@ async function createToken(context: Context, collection: CE, nft: NE): Promise { - debug(OPERATION, { updateToken: `Update TOKEN ${token.id} for NFT ${nft.id}` }) + debug(OPERATION, { updateToken: `Add NFT ${nft.id} to TOKEN ${token.id} for ` }) token.count += 1 token.updatedAt = nft.updatedAt nft.token = token await context.store.save(token) await context.store.save(nft) - return token } @@ -58,7 +55,7 @@ async function removeNftFromToken(context: Context, nft: NE, token: TE): Promise if (!token) { return } - debug(OPERATION, { handleExistingToken: `Unlink TOKEN ${token.id} from NFT ${nft.id}` }) + debug(OPERATION, { removeNftFromToken: `Unlink NFT ${nft.id} from TOKEN ${token.id} for ` }) await context.store.update(NE, nft.id, { token: null }) await context.store.update(TE, token.id, { count: token.count - 1, updatedAt: nft.updatedAt }) @@ -84,6 +81,7 @@ async function mintHandler(context: Context, collection: CE, nft: NE): Promise { + debug(OPERATION, { handleMetadataSet: `Handle set metadata for NFT ${nft.id}` }) const nftMedia = nft.image ?? nft.media if (!nftMedia || nftMedia === '') { @@ -94,18 +92,17 @@ async function handleMetadataSet(context: Context, collection: CE, nft: NE): Pro const nftWithToken = await getWith(context.store, NE, nft.id, { token: true }) const token = nftWithToken.token if (token) { - debug(OPERATION, { removeNftFromToken: `removeNftFromToken TOKEN ${token.id} for NFT ${nft.id}` }) await removeNftFromToken(context, nft, token) } } catch (error) { warn(OPERATION, `ERROR ${error}`) } const existingToken = await getOptional(context.store, TE, generateTokenId(collection.id, nftMedia)) - debug(OPERATION, { existingToken: `existingToken ${Boolean(existingToken)}` }) return await (existingToken ? addNftToToken(context, nft, existingToken) : createToken(context, collection, nft)) } async function handleBurn(context: Context, nft: NE): Promise { + debug(OPERATION, { handleBurn: `Handle Burn for NFT ${nft.id}` }) const nftMedia = nft.image ?? nft.media if (!nftMedia || nftMedia === '') { @@ -119,7 +116,7 @@ async function handleBurn(context: Context, nft: NE): Promise { return } - debug(OPERATION, { handleBurn: `Handle burn for NFT ${nft.id}` }) + debug(OPERATION, { BURN: `decrement Token's ${token.id} count` }) await context.store.update(TE, token.id, { count: token.count - 1, updatedAt: nft.updatedAt }) } From e470451f2a14c5dc52005f01363a1c01a34f78a9 Mon Sep 17 00:00:00 2001 From: daiagi Date: Wed, 4 Oct 2023 22:14:26 +0700 Subject: [PATCH 05/30] cheapest nft --- db/migrations/1696428349370-Data.js | 15 ++++++++++++ schema.graphql | 1 + src/mappings/nfts/list.ts | 2 ++ src/mappings/shared/handleTokenEntity.ts | 29 ++++++++++++++++++++++++ src/mappings/uniques/list.ts | 2 ++ src/model/generated/tokenEntity.model.ts | 4 ++++ 6 files changed, 53 insertions(+) create mode 100644 db/migrations/1696428349370-Data.js diff --git a/db/migrations/1696428349370-Data.js b/db/migrations/1696428349370-Data.js new file mode 100644 index 0000000..1a504c2 --- /dev/null +++ b/db/migrations/1696428349370-Data.js @@ -0,0 +1,15 @@ +module.exports = class Data1696428349370 { + name = 'Data1696428349370' + + async up(db) { + await db.query(`ALTER TABLE "token_entity" ADD "cheapest_nft_id" character varying`) + await db.query(`CREATE INDEX "IDX_6d56119ac99ab53644b1b21a6e" ON "token_entity" ("cheapest_nft_id") `) + await db.query(`ALTER TABLE "token_entity" ADD CONSTRAINT "FK_6d56119ac99ab53644b1b21a6ef" FOREIGN KEY ("cheapest_nft_id") REFERENCES "nft_entity"("id") ON DELETE NO ACTION ON UPDATE NO ACTION`) + } + + async down(db) { + await db.query(`ALTER TABLE "token_entity" DROP COLUMN "cheapest_nft_id"`) + await db.query(`DROP INDEX "public"."IDX_6d56119ac99ab53644b1b21a6e"`) + await db.query(`ALTER TABLE "token_entity" DROP CONSTRAINT "FK_6d56119ac99ab53644b1b21a6ef"`) + } +} diff --git a/schema.graphql b/schema.graphql index ae2b15a..ebc61e4 100644 --- a/schema.graphql +++ b/schema.graphql @@ -30,6 +30,7 @@ type TokenEntity @entity { id: ID! blockNumber: BigInt collection: CollectionEntity + cheapestNFT: NFTEntity nfts: [NFTEntity!] @derivedFrom(field: "token") hash: String! @index image: String diff --git a/src/mappings/nfts/list.ts b/src/mappings/nfts/list.ts index d722e5a..6455d86 100644 --- a/src/mappings/nfts/list.ts +++ b/src/mappings/nfts/list.ts @@ -4,6 +4,7 @@ import { unwrap } from '../utils/extract' import { debug, pending, success } from '../utils/logger' import { Action, Context, createTokenId } from '../utils/types' import { createEvent } from '../shared/event' +import { eventHandlers } from '../shared/handleTokenEntity' import { getPriceTokenEvent } from './getters' const OPERATION = Action.LIST @@ -22,6 +23,7 @@ export async function handleTokenList(context: Context): Promise { if (entity.price && (entity.collection.floor === 0n || entity.price < entity.collection.floor)) { entity.collection.floor = entity.price } + eventHandlers.listHandler(context, entity) success(OPERATION, `${id} by ${event.caller}} for ${String(event.price)}`) await context.store.save(entity) diff --git a/src/mappings/shared/handleTokenEntity.ts b/src/mappings/shared/handleTokenEntity.ts index eca76c4..51dd419 100644 --- a/src/mappings/shared/handleTokenEntity.ts +++ b/src/mappings/shared/handleTokenEntity.ts @@ -121,8 +121,37 @@ async function handleBurn(context: Context, nft: NE): Promise { await context.store.update(TE, token.id, { count: token.count - 1, updatedAt: nft.updatedAt }) } +async function listHandler(context: Context, nft: NE): Promise { + const nftMedia = nft.image ?? nft.media + + if (!nftMedia || nftMedia === '') { + warn(OPERATION, `MISSING NFT MEDIA ${nft.id}`) + return + } + + try { + const nftWithToken = await getWith(context.store, NE, nft.id, { token: true }) + if (!nftWithToken.token) { + warn(OPERATION, `nft ${nft.id} is not linked to a token`) + return + } + const cheapestNFT = nftWithToken.token.cheapestNFT + if (!nft.price && !cheapestNFT?.price) { + return; + } + + if (!cheapestNFT?.price || (nft.price && nft.price < cheapestNFT.price)) { + context.store.update(TE, nftWithToken.token.id, { cheapestNFT: nft, updatedAt: nft.updatedAt }) + } + } catch (error) { + warn(OPERATION, `ERROR ${error}`) + } +} + + export const eventHandlers = { setMetadataHandler: handleMetadataSet, mintHandler, burnHandler: handleBurn, + listHandler } diff --git a/src/mappings/uniques/list.ts b/src/mappings/uniques/list.ts index d722e5a..6455d86 100644 --- a/src/mappings/uniques/list.ts +++ b/src/mappings/uniques/list.ts @@ -4,6 +4,7 @@ import { unwrap } from '../utils/extract' import { debug, pending, success } from '../utils/logger' import { Action, Context, createTokenId } from '../utils/types' import { createEvent } from '../shared/event' +import { eventHandlers } from '../shared/handleTokenEntity' import { getPriceTokenEvent } from './getters' const OPERATION = Action.LIST @@ -22,6 +23,7 @@ export async function handleTokenList(context: Context): Promise { if (entity.price && (entity.collection.floor === 0n || entity.price < entity.collection.floor)) { entity.collection.floor = entity.price } + eventHandlers.listHandler(context, entity) success(OPERATION, `${id} by ${event.caller}} for ${String(event.price)}`) await context.store.save(entity) diff --git a/src/model/generated/tokenEntity.model.ts b/src/model/generated/tokenEntity.model.ts index 3c46348..6d6da06 100644 --- a/src/model/generated/tokenEntity.model.ts +++ b/src/model/generated/tokenEntity.model.ts @@ -19,6 +19,10 @@ export class TokenEntity { @ManyToOne_(() => CollectionEntity, {nullable: true}) collection!: CollectionEntity | undefined | null + @Index_() + @ManyToOne_(() => NFTEntity, {nullable: true}) + cheapestNFT!: NFTEntity | undefined | null + @OneToMany_(() => NFTEntity, e => e.token) nfts!: NFTEntity[] From 3ae7031bce2b03083dbd05f20eeae89413292674 Mon Sep 17 00:00:00 2001 From: daiagi Date: Fri, 6 Oct 2023 10:54:24 +0700 Subject: [PATCH 06/30] improve performance --- src/mappings/shared/handleTokenEntity.ts | 26 ++++++++++++++---------- 1 file changed, 15 insertions(+), 11 deletions(-) diff --git a/src/mappings/shared/handleTokenEntity.ts b/src/mappings/shared/handleTokenEntity.ts index eca76c4..88dc456 100644 --- a/src/mappings/shared/handleTokenEntity.ts +++ b/src/mappings/shared/handleTokenEntity.ts @@ -55,13 +55,13 @@ async function removeNftFromToken(context: Context, nft: NE, token: TE): Promise if (!token) { return } - debug(OPERATION, { removeNftFromToken: `Unlink NFT ${nft.id} from TOKEN ${token.id} for ` }) + debug(OPERATION, { removeNftFromToken: `Unlink NFT ${nft.id} from TOKEN ${token.id}` }) await context.store.update(NE, nft.id, { token: null }) - await context.store.update(TE, token.id, { count: token.count - 1, updatedAt: nft.updatedAt }) + const updatedCount = token.count - 1 + await context.store.update(TE, token.id, { count: updatedCount, updatedAt: nft.updatedAt }) - const tokenNfts = (await getWith(context.store, TE, token.id, { nfts: true })).nfts - if (tokenNfts.length === 0) { + if (updatedCount === 0) { debug(OPERATION, { deleteEmptyToken: `delete empty token ${token.id}` }) await context.store.delete(TE, token.id) } @@ -88,16 +88,20 @@ async function handleMetadataSet(context: Context, collection: CE, nft: NE): Pro warn(OPERATION, `MISSING NFT MEDIA ${nft.id}`) return } + + let nftWithToken, existingToken try { - const nftWithToken = await getWith(context.store, NE, nft.id, { token: true }) - const token = nftWithToken.token - if (token) { - await removeNftFromToken(context, nft, token) - } + [nftWithToken, existingToken] = await Promise.all([ + getWith(context.store, NE, nft.id, { token: true }), + getOptional(context.store, TE, generateTokenId(collection.id, nftMedia)), + ]) } catch (error) { warn(OPERATION, `ERROR ${error}`) + return + } + if (nftWithToken.token) { + await removeNftFromToken(context, nft, nftWithToken.token) } - const existingToken = await getOptional(context.store, TE, generateTokenId(collection.id, nftMedia)) return await (existingToken ? addNftToToken(context, nft, existingToken) : createToken(context, collection, nft)) } @@ -116,7 +120,7 @@ async function handleBurn(context: Context, nft: NE): Promise { return } - debug(OPERATION, { BURN: `decrement Token's ${token.id} count` }) + debug(OPERATION, { BURN: `decrement Token's ${token.id} count` }) await context.store.update(TE, token.id, { count: token.count - 1, updatedAt: nft.updatedAt }) } From 0a93e904ba0592cc09650e99f113d2e8b379d39f Mon Sep 17 00:00:00 2001 From: daiagi Date: Fri, 6 Oct 2023 11:31:54 +0700 Subject: [PATCH 07/30] resolver for tokenEntities of specific owner --- .../model/tokenEntity.model.ts | 123 ++++++++++++++++++ .../query/tokenEntityByOwner.ts | 42 ++++++ src/server-extension/resolvers/index.ts | 11 +- .../resolvers/tokenEntityByOwner.ts | 69 ++++++++++ 4 files changed, 236 insertions(+), 9 deletions(-) create mode 100644 src/server-extension/model/tokenEntity.model.ts create mode 100644 src/server-extension/query/tokenEntityByOwner.ts create mode 100644 src/server-extension/resolvers/tokenEntityByOwner.ts diff --git a/src/server-extension/model/tokenEntity.model.ts b/src/server-extension/model/tokenEntity.model.ts new file mode 100644 index 0000000..68f4014 --- /dev/null +++ b/src/server-extension/model/tokenEntity.model.ts @@ -0,0 +1,123 @@ +import { Field, ObjectType, registerEnumType} from 'type-graphql'; + + +export enum OrderBy { + blockNumber_ASC = "blockNumber_ASC", + blockNumber_DESC = "blockNumber_DESC", + updatedAt_ASC = "updatedAt_ASC", + updatedAt_DESC = "updatedAt_DESC", + createdAt_ASC = "createdAt_ASC", + createdAt_DESC = "createdAt_DESC", + price_ASC = "price_ASC", + price_DESC = "price_DESC", + +} + +registerEnumType(OrderBy, { + name: "OrderBy", + description: "Order by options for sorting results", +}); + + +@ObjectType() +class CheapestNFT { + @Field(() => String, { nullable: true }) + id!: string; + + @Field(() => BigInt, { nullable: true }) + price!: bigint; +} + +@ObjectType() +class Collection { + @Field(() => String, { nullable: true }) + id!: string; + + @Field(() => String, { nullable: true }) + name!: string; +} + +@ObjectType() +export class TokenEntityByOwner { + @Field(() => String, { nullable: false }) + id!: string; + + @Field(() => String, { nullable: false }) + name!: string; + + @Field(() => String, { nullable: true }) + image!: string; + + @Field(() => String, { nullable: true }) + media!: string; + + @Field(() => Date, { nullable: false }) + createdAt!: Date; + + @Field(() => Date, { nullable: false }) + updatedAt!: Date; + + @Field(() => BigInt, { nullable: false }) + blockNumber!: bigint; + + @Field(() => Number, { nullable: false }) + count!: number; + + @Field(() => CheapestNFT) + cheapestNFT!: CheapestNFT; + + @Field(() => Collection) + collection!: Collection; + + + constructor(props: Partial) { + Object.assign(this, props); + } +} + +@ObjectType() +export class TokenEntityByOwnerQueryResult { + @Field(() => String, { nullable: false }) + id!: string; + + @Field(() => String, { nullable: false }) + name!: string; + + @Field(() => String, { nullable: true }) + image!: string; + + @Field(() => String, { nullable: true }) + media!: string; + + @Field(() => Date, { nullable: false }) + createdAt!: Date; + + @Field(() => Date, { nullable: false }) + updatedAt!: Date; + + @Field(() => BigInt, { nullable: false }) + blockNumber!: bigint; + + @Field(() => Number, { nullable: false }) + count!: number; + + @Field(() => String, { nullable: true }) + cheapestNFTId!: string; + + @Field(() => BigInt, { nullable: true }) + cheapestNFTPrice!: bigint; + + @Field(() => String, { nullable: false }) + collectionId!: string; + + @Field(() => String, { nullable: false }) + collectionName!: string; + + + + + constructor(props: Partial) { + Object.assign(this, props); + } +} + diff --git a/src/server-extension/query/tokenEntityByOwner.ts b/src/server-extension/query/tokenEntityByOwner.ts new file mode 100644 index 0000000..cb609e4 --- /dev/null +++ b/src/server-extension/query/tokenEntityByOwner.ts @@ -0,0 +1,42 @@ +export const tokenEntityByOwner = `WITH CheapestNFT AS ( + SELECT + ne.token_id, + MIN(ne.price) AS "cheapestNFTPrice" + FROM + nft_entity ne + WHERE + ne.current_owner = $1 + GROUP BY + ne.token_id +) + +SELECT + t.id as id, + t.name as name, + t.image as image, + t.media as media, + t.block_number as "blockNumber", + t.created_at AS "createdAt", + t.updated_at AS "updatedAt", + COUNT(ne.id) as count, + + c."cheapestNFTPrice", + + col.id AS "collectionId", + col.name AS "collectionName" +FROM + token_entity as t + LEFT JOIN nft_entity as ne ON t.id = ne.token_id AND ne.current_owner = $1 + LEFT JOIN CheapestNFT as c ON t.id = c.token_id + LEFT JOIN collection_entity as col ON t.collection_id = col.id +GROUP BY + t.id, c."cheapestNFTPrice", col.id +HAVING + COUNT(ne.id) > 0 AND + ($5::bigint IS NULL OR c."cheapestNFTPrice" >= $5::bigint) AND + ($6::bigint IS NULL OR c."cheapestNFTPrice" > $6::bigint) AND + ($7::bigint IS NULL OR c."cheapestNFTPrice" <= $7::bigint) + +ORDER BY $4 LIMIT $2 OFFSET $3; + +` \ No newline at end of file diff --git a/src/server-extension/resolvers/index.ts b/src/server-extension/resolvers/index.ts index 2126ed9..d290e97 100644 --- a/src/server-extension/resolvers/index.ts +++ b/src/server-extension/resolvers/index.ts @@ -1,13 +1,6 @@ - - - - - - - - export { CollectionEventResolver } from './collectionEvent' export { WalletResolver } from './walletResolver' export { EventResolver } from './event' export { SeriesResolver } from './series' -export { SpotlightResolver } from './spotlight' \ No newline at end of file +export { SpotlightResolver } from './spotlight' +export { TokenResolver } from './tokenEntityByOwner' diff --git a/src/server-extension/resolvers/tokenEntityByOwner.ts b/src/server-extension/resolvers/tokenEntityByOwner.ts new file mode 100644 index 0000000..fa9d460 --- /dev/null +++ b/src/server-extension/resolvers/tokenEntityByOwner.ts @@ -0,0 +1,69 @@ +/* eslint-disable camelcase */ +import { Arg, Query, Resolver } from 'type-graphql' +import { EntityManager } from 'typeorm' +import { OrderBy, TokenEntityByOwner, TokenEntityByOwnerQueryResult } from '../model/tokenEntity.model' +import { makeQuery } from '../utils' +import { tokenEntityByOwner } from '../query/tokenEntityByOwner' +import { TokenEntity } from '../../model/generated' + +@Resolver() +export class TokenResolver { + constructor(private tx: () => Promise) {} + + @Query(() => [TokenEntityByOwner]) + async tokenEntitiesByOwner( + @Arg('owner', { nullable: false }) owner: string, + @Arg('limit', { nullable: true }) limit?: number, + @Arg('offset', { nullable: true, defaultValue: 0 }) offset?: number, + @Arg('orderBy', { nullable: true, defaultValue: OrderBy.blockNumber_DESC }) orderBy?: OrderBy, + @Arg('price_gte', { nullable: true }) price_gte?: number, + @Arg('price_gt', { nullable: true }) price_gt?: number, + @Arg('price_lte', { nullable: true }) price_lte?: number + ): Promise { + const orderQuery = this.getOrderByQuery(orderBy) + + const result: TokenEntityByOwnerQueryResult[] = await makeQuery(this.tx, TokenEntity, tokenEntityByOwner, [ + owner, + limit, + offset, + orderQuery, + price_gte, + price_gt, + price_lte, + ]) + return result.map((row) => ({ + ...row, + cheapestNFT: { + id: row.cheapestNFTId, + price: row.cheapestNFTPrice, + }, + collection: { + id: row.collectionId, + name: row.collectionName, + }, + })) + } + + private getOrderByQuery(orderBy?: OrderBy): string { + switch (orderBy) { + case OrderBy.blockNumber_ASC: + return 't.block_number ASC' + case OrderBy.blockNumber_DESC: + return 't.block_number DESC' + case OrderBy.createdAt_ASC: + return 't.created_at ASC' + case OrderBy.createdAt_DESC: + return 't.created_at DESC' + case OrderBy.updatedAt_ASC: + return 't.updated_at ASC' + case OrderBy.updatedAt_DESC: + return 't.updated_at DESC' + case OrderBy.price_ASC: + return 'c."cheapestNFTPrice" ASC' + case OrderBy.price_DESC: + return 'c."cheapestNFTPrice" DESC' + default: + return 't.block_number DESC' + } + } +} From 0db457c8d976dc8ea7fe87aac9a5b74e73affa22 Mon Sep 17 00:00:00 2001 From: daiagi Date: Fri, 6 Oct 2023 13:40:16 +0700 Subject: [PATCH 08/30] add metadata and meta to TokenEntity --- schema.graphql | 2 ++ src/mappings/shared/handleTokenEntity.ts | 3 +++ src/model/generated/tokenEntity.model.ts | 8 ++++++++ 3 files changed, 13 insertions(+) diff --git a/schema.graphql b/schema.graphql index ebc61e4..a989100 100644 --- a/schema.graphql +++ b/schema.graphql @@ -35,6 +35,8 @@ type TokenEntity @entity { hash: String! @index image: String media: String + meta: MetadataEntity + metadata: String name: String @index updatedAt: DateTime! createdAt: DateTime! diff --git a/src/mappings/shared/handleTokenEntity.ts b/src/mappings/shared/handleTokenEntity.ts index 90cd6e6..b23be3f 100644 --- a/src/mappings/shared/handleTokenEntity.ts +++ b/src/mappings/shared/handleTokenEntity.ts @@ -28,6 +28,8 @@ async function createToken(context: Context, collection: CE, nft: NE): Promise MetadataEntity, {nullable: true}) + meta!: MetadataEntity | undefined | null + + @Column_("text", {nullable: true}) + metadata!: string | undefined | null + @Index_() @Column_("text", {nullable: true}) name!: string | undefined | null From 2917612039ed4ca4b37515ee1bc0af3bb9737d9e Mon Sep 17 00:00:00 2001 From: daiagi Date: Fri, 6 Oct 2023 14:07:32 +0700 Subject: [PATCH 09/30] add totalCount field to avoid bug when delting token --- db/migrations/1689623692588-Data.js | 1 + schema.graphql | 1 + src/mappings/shared/handleTokenEntity.ts | 10 ++++++---- src/model/generated/tokenEntity.model.ts | 3 +++ 4 files changed, 11 insertions(+), 4 deletions(-) diff --git a/db/migrations/1689623692588-Data.js b/db/migrations/1689623692588-Data.js index 6f8d7e4..efe94d3 100644 --- a/db/migrations/1689623692588-Data.js +++ b/db/migrations/1689623692588-Data.js @@ -3,6 +3,7 @@ module.exports = class Data1689623692588 { async up(db) { await db.query(`CREATE TABLE "token_entity" ("id" character varying NOT NULL, "block_number" numeric, "hash" text NOT NULL, "image" text, "media" text, "name" text, "updated_at" TIMESTAMP WITH TIME ZONE NOT NULL, "created_at" TIMESTAMP WITH TIME ZONE NOT NULL, "count" integer NOT NULL, "collection_id" character varying, CONSTRAINT "PK_687443f2a51af49b5472e2c5ddc" PRIMARY KEY ("id"))`) + await db.query(`ALTER TABLE "token_entity" ADD "total_count" integer NOT NULL`) await db.query(`CREATE INDEX "IDX_0eb2ed7929c3e81941fa1b51b3" ON "token_entity" ("collection_id") `) await db.query(`CREATE INDEX "IDX_40d6049fd30532dada71922792" ON "token_entity" ("hash") `) await db.query(`CREATE INDEX "IDX_47b385945a425667b9e690bc02" ON "token_entity" ("name") `) diff --git a/schema.graphql b/schema.graphql index ae2b15a..54b4f7e 100644 --- a/schema.graphql +++ b/schema.graphql @@ -38,6 +38,7 @@ type TokenEntity @entity { updatedAt: DateTime! createdAt: DateTime! count: Int! + totalCount: Int! } type NFTEntity @entity { diff --git a/src/mappings/shared/handleTokenEntity.ts b/src/mappings/shared/handleTokenEntity.ts index 88dc456..4eeb293 100644 --- a/src/mappings/shared/handleTokenEntity.ts +++ b/src/mappings/shared/handleTokenEntity.ts @@ -25,6 +25,7 @@ async function createToken(context: Context, collection: CE, nft: NE): Promise { debug(OPERATION, { updateToken: `Add NFT ${nft.id} to TOKEN ${token.id} for ` }) token.count += 1 + token.totalCount += 1 token.updatedAt = nft.updatedAt nft.token = token await context.store.save(token) @@ -58,10 +60,10 @@ async function removeNftFromToken(context: Context, nft: NE, token: TE): Promise debug(OPERATION, { removeNftFromToken: `Unlink NFT ${nft.id} from TOKEN ${token.id}` }) await context.store.update(NE, nft.id, { token: null }) - const updatedCount = token.count - 1 - await context.store.update(TE, token.id, { count: updatedCount, updatedAt: nft.updatedAt }) + const updatedTotalCount = token.totalCount - 1 + await context.store.update(TE, token.id, { count: token.count - 1, totalCount: updatedTotalCount , updatedAt: nft.updatedAt }) - if (updatedCount === 0) { + if (updatedTotalCount === 0) { debug(OPERATION, { deleteEmptyToken: `delete empty token ${token.id}` }) await context.store.delete(TE, token.id) } @@ -91,7 +93,7 @@ async function handleMetadataSet(context: Context, collection: CE, nft: NE): Pro let nftWithToken, existingToken try { - [nftWithToken, existingToken] = await Promise.all([ + ;[nftWithToken, existingToken] = await Promise.all([ getWith(context.store, NE, nft.id, { token: true }), getOptional(context.store, TE, generateTokenId(collection.id, nftMedia)), ]) diff --git a/src/model/generated/tokenEntity.model.ts b/src/model/generated/tokenEntity.model.ts index 3c46348..62c51a7 100644 --- a/src/model/generated/tokenEntity.model.ts +++ b/src/model/generated/tokenEntity.model.ts @@ -44,4 +44,7 @@ export class TokenEntity { @Column_("int4", {nullable: false}) count!: number + + @Column_("int4", {nullable: false}) + totalCount!: number } From 7e6e5dc0c315af4a10d2e25f7536672f86e3aa81 Mon Sep 17 00:00:00 2001 From: daiagi Date: Fri, 6 Oct 2023 14:16:46 +0700 Subject: [PATCH 10/30] merge migration files --- db/migrations/1689623692588-Data.js | 76 ++++++++++++++++++++--------- db/migrations/1696428349370-Data.js | 15 ------ 2 files changed, 54 insertions(+), 37 deletions(-) delete mode 100644 db/migrations/1696428349370-Data.js diff --git a/db/migrations/1689623692588-Data.js b/db/migrations/1689623692588-Data.js index efe94d3..fa2f99d 100644 --- a/db/migrations/1689623692588-Data.js +++ b/db/migrations/1689623692588-Data.js @@ -1,26 +1,58 @@ module.exports = class Data1689623692588 { - name = 'Data1689623692588' + name = 'Data1689623692588' - async up(db) { - await db.query(`CREATE TABLE "token_entity" ("id" character varying NOT NULL, "block_number" numeric, "hash" text NOT NULL, "image" text, "media" text, "name" text, "updated_at" TIMESTAMP WITH TIME ZONE NOT NULL, "created_at" TIMESTAMP WITH TIME ZONE NOT NULL, "count" integer NOT NULL, "collection_id" character varying, CONSTRAINT "PK_687443f2a51af49b5472e2c5ddc" PRIMARY KEY ("id"))`) - await db.query(`ALTER TABLE "token_entity" ADD "total_count" integer NOT NULL`) - await db.query(`CREATE INDEX "IDX_0eb2ed7929c3e81941fa1b51b3" ON "token_entity" ("collection_id") `) - await db.query(`CREATE INDEX "IDX_40d6049fd30532dada71922792" ON "token_entity" ("hash") `) - await db.query(`CREATE INDEX "IDX_47b385945a425667b9e690bc02" ON "token_entity" ("name") `) - await db.query(`ALTER TABLE "nft_entity" ADD "token_id" character varying`) - await db.query(`CREATE INDEX "IDX_060d0f515d293fac1d81ee61a7" ON "nft_entity" ("token_id") `) - await db.query(`ALTER TABLE "token_entity" ADD CONSTRAINT "FK_0eb2ed7929c3e81941fa1b51b35" FOREIGN KEY ("collection_id") REFERENCES "collection_entity"("id") ON DELETE NO ACTION ON UPDATE NO ACTION`) - await db.query(`ALTER TABLE "nft_entity" ADD CONSTRAINT "FK_060d0f515d293fac1d81ee61a79" FOREIGN KEY ("token_id") REFERENCES "token_entity"("id") ON DELETE NO ACTION ON UPDATE NO ACTION`) - } + async up(db) { + await db.query( + `CREATE TABLE "token_entity" ("id" character varying NOT NULL, "block_number" numeric, "hash" text NOT NULL, "image" text, "media" text, "name" text, "updated_at" TIMESTAMP WITH TIME ZONE NOT NULL, "created_at" TIMESTAMP WITH TIME ZONE NOT NULL, "count" integer NOT NULL, "collection_id" character varying, CONSTRAINT "PK_687443f2a51af49b5472e2c5ddc" PRIMARY KEY ("id"))` + ) + await db.query(`ALTER TABLE "token_entity" ADD "total_count" integer NOT NULL`) + await db.query(`CREATE INDEX "IDX_0eb2ed7929c3e81941fa1b51b3" ON "token_entity" ("collection_id") `) + await db.query(`CREATE INDEX "IDX_40d6049fd30532dada71922792" ON "token_entity" ("hash") `) + await db.query(`CREATE INDEX "IDX_47b385945a425667b9e690bc02" ON "token_entity" ("name") `) + await db.query(`ALTER TABLE "nft_entity" ADD "token_id" character varying`) + await db.query(`CREATE INDEX "IDX_060d0f515d293fac1d81ee61a7" ON "nft_entity" ("token_id") `) + await db.query( + `ALTER TABLE "token_entity" ADD CONSTRAINT "FK_0eb2ed7929c3e81941fa1b51b35" FOREIGN KEY ("collection_id") REFERENCES "collection_entity"("id") ON DELETE NO ACTION ON UPDATE NO ACTION` + ) + await db.query( + `ALTER TABLE "nft_entity" ADD CONSTRAINT "FK_060d0f515d293fac1d81ee61a79" FOREIGN KEY ("token_id") REFERENCES "token_entity"("id") ON DELETE NO ACTION ON UPDATE NO ACTION` + ) - async down(db) { - await db.query(`DROP TABLE "token_entity"`) - await db.query(`DROP INDEX "public"."IDX_0eb2ed7929c3e81941fa1b51b3"`) - await db.query(`DROP INDEX "public"."IDX_40d6049fd30532dada71922792"`) - await db.query(`DROP INDEX "public"."IDX_47b385945a425667b9e690bc02"`) - await db.query(`ALTER TABLE "nft_entity" DROP COLUMN "token_id"`) - await db.query(`DROP INDEX "public"."IDX_060d0f515d293fac1d81ee61a7"`) - await db.query(`ALTER TABLE "token_entity" DROP CONSTRAINT "FK_0eb2ed7929c3e81941fa1b51b35"`) - await db.query(`ALTER TABLE "nft_entity" DROP CONSTRAINT "FK_060d0f515d293fac1d81ee61a79"`) - } + // Commands from Data1696428349370 + await db.query(`ALTER TABLE "token_entity" ADD "cheapest_nft_id" character varying`) + await db.query(`CREATE INDEX "IDX_6d56119ac99ab53644b1b21a6e" ON "token_entity" ("cheapest_nft_id") `) + await db.query( + `ALTER TABLE "token_entity" ADD CONSTRAINT "FK_6d56119ac99ab53644b1b21a6ef" FOREIGN KEY ("cheapest_nft_id") REFERENCES "nft_entity"("id") ON DELETE NO ACTION ON UPDATE NO ACTION` + ) + + // Commands from Data1696576165016 + await db.query(`ALTER TABLE "token_entity" ADD "metadata" text`) + await db.query(`ALTER TABLE "token_entity" ADD "meta_id" character varying`) + await db.query(`CREATE INDEX "IDX_ae4ff3b28e3fec72aa14124d1e" ON "token_entity" ("meta_id") `) + await db.query( + `ALTER TABLE "token_entity" ADD CONSTRAINT "FK_ae4ff3b28e3fec72aa14124d1e1" FOREIGN KEY ("meta_id") REFERENCES "metadata_entity"("id") ON DELETE NO ACTION ON UPDATE NO ACTION` + ) + } + + async down(db) { + // Rollback commands from Data1696576165016 + await db.query(`ALTER TABLE "token_entity" DROP COLUMN "metadata"`) + await db.query(`ALTER TABLE "token_entity" DROP COLUMN "meta_id"`) + await db.query(`DROP INDEX "public"."IDX_ae4ff3b28e3fec72aa14124d1e"`) + await db.query(`ALTER TABLE "token_entity" DROP CONSTRAINT "FK_ae4ff3b28e3fec72aa14124d1e1"`) + + // Rollback commands from Data1696428349370 + await db.query(`ALTER TABLE "token_entity" DROP COLUMN "cheapest_nft_id"`) + await db.query(`DROP INDEX "public"."IDX_6d56119ac99ab53644b1b21a6e"`) + await db.query(`ALTER TABLE "token_entity" DROP CONSTRAINT "FK_6d56119ac99ab53644b1b21a6ef"`) + + await db.query(`DROP TABLE "token_entity"`) + await db.query(`DROP INDEX "public"."IDX_0eb2ed7929c3e81941fa1b51b3"`) + await db.query(`DROP INDEX "public"."IDX_40d6049fd30532dada71922792"`) + await db.query(`DROP INDEX "public"."IDX_47b385945a425667b9e690bc02"`) + await db.query(`ALTER TABLE "nft_entity" DROP COLUMN "token_id"`) + await db.query(`DROP INDEX "public"."IDX_060d0f515d293fac1d81ee61a7"`) + await db.query(`ALTER TABLE "token_entity" DROP CONSTRAINT "FK_0eb2ed7929c3e81941fa1b51b35"`) + await db.query(`ALTER TABLE "nft_entity" DROP CONSTRAINT "FK_060d0f515d293fac1d81ee61a79"`) + } } diff --git a/db/migrations/1696428349370-Data.js b/db/migrations/1696428349370-Data.js deleted file mode 100644 index 1a504c2..0000000 --- a/db/migrations/1696428349370-Data.js +++ /dev/null @@ -1,15 +0,0 @@ -module.exports = class Data1696428349370 { - name = 'Data1696428349370' - - async up(db) { - await db.query(`ALTER TABLE "token_entity" ADD "cheapest_nft_id" character varying`) - await db.query(`CREATE INDEX "IDX_6d56119ac99ab53644b1b21a6e" ON "token_entity" ("cheapest_nft_id") `) - await db.query(`ALTER TABLE "token_entity" ADD CONSTRAINT "FK_6d56119ac99ab53644b1b21a6ef" FOREIGN KEY ("cheapest_nft_id") REFERENCES "nft_entity"("id") ON DELETE NO ACTION ON UPDATE NO ACTION`) - } - - async down(db) { - await db.query(`ALTER TABLE "token_entity" DROP COLUMN "cheapest_nft_id"`) - await db.query(`DROP INDEX "public"."IDX_6d56119ac99ab53644b1b21a6e"`) - await db.query(`ALTER TABLE "token_entity" DROP CONSTRAINT "FK_6d56119ac99ab53644b1b21a6ef"`) - } -} From cd0de707cfb5b186b30607a4a76af6e0e1d7e50d Mon Sep 17 00:00:00 2001 From: daiagi Date: Fri, 6 Oct 2023 15:12:23 +0700 Subject: [PATCH 11/30] add meta and metadata to resolver --- src/mappings/shared/handleTokenEntity.ts | 2 +- .../model/tokenEntity.model.ts | 36 ++++++++++++++++++- .../query/tokenEntityByOwner.ts | 8 ++++- .../resolvers/tokenEntityByOwner.ts | 6 ++++ 4 files changed, 49 insertions(+), 3 deletions(-) diff --git a/src/mappings/shared/handleTokenEntity.ts b/src/mappings/shared/handleTokenEntity.ts index 0a699c4..b5d0fb4 100644 --- a/src/mappings/shared/handleTokenEntity.ts +++ b/src/mappings/shared/handleTokenEntity.ts @@ -96,7 +96,7 @@ async function handleMetadataSet(context: Context, collection: CE, nft: NE): Pro let nftWithToken, existingToken try { - ;[nftWithToken, existingToken] = await Promise.all([ + [nftWithToken, existingToken] = await Promise.all([ getWith(context.store, NE, nft.id, { token: true }), getOptional(context.store, TE, generateTokenId(collection.id, nftMedia)), ]) diff --git a/src/server-extension/model/tokenEntity.model.ts b/src/server-extension/model/tokenEntity.model.ts index 68f4014..8247936 100644 --- a/src/server-extension/model/tokenEntity.model.ts +++ b/src/server-extension/model/tokenEntity.model.ts @@ -1,4 +1,4 @@ -import { Field, ObjectType, registerEnumType} from 'type-graphql'; +import { Field, ObjectType, registerEnumType } from 'type-graphql'; export enum OrderBy { @@ -37,6 +37,22 @@ class Collection { name!: string; } + +@ObjectType() + class PartialMetadataEntity { + @Field(() => String, { nullable: false }) + id!: string; + + @Field(() => String, { nullable: true }) + description?: string; + + @Field(() => String, { nullable: true }) + image?: string; + + @Field(() => String, { nullable: true }) + animationUrl?: string; +} + @ObjectType() export class TokenEntityByOwner { @Field(() => String, { nullable: false }) @@ -51,6 +67,12 @@ export class TokenEntityByOwner { @Field(() => String, { nullable: true }) media!: string; + @Field(() => String, { nullable: true }) + metadata?: string; + + @Field(() => PartialMetadataEntity, { nullable: true }) + meta?: PartialMetadataEntity; + @Field(() => Date, { nullable: false }) createdAt!: Date; @@ -113,6 +135,18 @@ export class TokenEntityByOwnerQueryResult { @Field(() => String, { nullable: false }) collectionName!: string; + @Field(() => String, { nullable: false }) + metaId!: string; + + @Field(() => String, { nullable: true }) + metaDescription?: string; + + @Field(() => String, { nullable: true }) + metaImage?: string; + + @Field(() => String, { nullable: true }) + metaAnimationUrl?: string; + diff --git a/src/server-extension/query/tokenEntityByOwner.ts b/src/server-extension/query/tokenEntityByOwner.ts index cb609e4..062268d 100644 --- a/src/server-extension/query/tokenEntityByOwner.ts +++ b/src/server-extension/query/tokenEntityByOwner.ts @@ -15,6 +15,11 @@ SELECT t.name as name, t.image as image, t.media as media, + t.metadata as metadata, + me.id as "metaId", + me.description as "metaDescription", + me.image as "metaImage", + me.animation_url as "metaAnimationUrl", t.block_number as "blockNumber", t.created_at AS "createdAt", t.updated_at AS "updatedAt", @@ -29,8 +34,9 @@ FROM LEFT JOIN nft_entity as ne ON t.id = ne.token_id AND ne.current_owner = $1 LEFT JOIN CheapestNFT as c ON t.id = c.token_id LEFT JOIN collection_entity as col ON t.collection_id = col.id + LEFT JOIN metadata_entity as me ON t.meta_id = me.id GROUP BY - t.id, c."cheapestNFTPrice", col.id + t.id, c."cheapestNFTPrice", col.id, me.id HAVING COUNT(ne.id) > 0 AND ($5::bigint IS NULL OR c."cheapestNFTPrice" >= $5::bigint) AND diff --git a/src/server-extension/resolvers/tokenEntityByOwner.ts b/src/server-extension/resolvers/tokenEntityByOwner.ts index fa9d460..616890a 100644 --- a/src/server-extension/resolvers/tokenEntityByOwner.ts +++ b/src/server-extension/resolvers/tokenEntityByOwner.ts @@ -41,6 +41,12 @@ export class TokenResolver { id: row.collectionId, name: row.collectionName, }, + meta: { + id: row.metaId, + description: row.metaDescription, + animationUrl: row.metaAnimationUrl, + image: row.metaImage, + } })) } From 810718eeddaf79852dcb9d82d8d106401ccfca22 Mon Sep 17 00:00:00 2001 From: daiagi Date: Sun, 8 Oct 2023 14:58:03 +0700 Subject: [PATCH 12/30] schema and migration --- db/migrations/1689623692588-Data.js | 67 ++++++++--------------------- db/migrations/1696751796584-Data.js | 25 +++++++++++ schema.graphql | 4 +- 3 files changed, 44 insertions(+), 52 deletions(-) create mode 100644 db/migrations/1696751796584-Data.js diff --git a/db/migrations/1689623692588-Data.js b/db/migrations/1689623692588-Data.js index fa2f99d..94becdb 100644 --- a/db/migrations/1689623692588-Data.js +++ b/db/migrations/1689623692588-Data.js @@ -2,57 +2,24 @@ module.exports = class Data1689623692588 { name = 'Data1689623692588' async up(db) { - await db.query( - `CREATE TABLE "token_entity" ("id" character varying NOT NULL, "block_number" numeric, "hash" text NOT NULL, "image" text, "media" text, "name" text, "updated_at" TIMESTAMP WITH TIME ZONE NOT NULL, "created_at" TIMESTAMP WITH TIME ZONE NOT NULL, "count" integer NOT NULL, "collection_id" character varying, CONSTRAINT "PK_687443f2a51af49b5472e2c5ddc" PRIMARY KEY ("id"))` - ) - await db.query(`ALTER TABLE "token_entity" ADD "total_count" integer NOT NULL`) - await db.query(`CREATE INDEX "IDX_0eb2ed7929c3e81941fa1b51b3" ON "token_entity" ("collection_id") `) - await db.query(`CREATE INDEX "IDX_40d6049fd30532dada71922792" ON "token_entity" ("hash") `) - await db.query(`CREATE INDEX "IDX_47b385945a425667b9e690bc02" ON "token_entity" ("name") `) - await db.query(`ALTER TABLE "nft_entity" ADD "token_id" character varying`) - await db.query(`CREATE INDEX "IDX_060d0f515d293fac1d81ee61a7" ON "nft_entity" ("token_id") `) - await db.query( - `ALTER TABLE "token_entity" ADD CONSTRAINT "FK_0eb2ed7929c3e81941fa1b51b35" FOREIGN KEY ("collection_id") REFERENCES "collection_entity"("id") ON DELETE NO ACTION ON UPDATE NO ACTION` - ) - await db.query( - `ALTER TABLE "nft_entity" ADD CONSTRAINT "FK_060d0f515d293fac1d81ee61a79" FOREIGN KEY ("token_id") REFERENCES "token_entity"("id") ON DELETE NO ACTION ON UPDATE NO ACTION` - ) - - // Commands from Data1696428349370 - await db.query(`ALTER TABLE "token_entity" ADD "cheapest_nft_id" character varying`) - await db.query(`CREATE INDEX "IDX_6d56119ac99ab53644b1b21a6e" ON "token_entity" ("cheapest_nft_id") `) - await db.query( - `ALTER TABLE "token_entity" ADD CONSTRAINT "FK_6d56119ac99ab53644b1b21a6ef" FOREIGN KEY ("cheapest_nft_id") REFERENCES "nft_entity"("id") ON DELETE NO ACTION ON UPDATE NO ACTION` - ) - - // Commands from Data1696576165016 - await db.query(`ALTER TABLE "token_entity" ADD "metadata" text`) - await db.query(`ALTER TABLE "token_entity" ADD "meta_id" character varying`) - await db.query(`CREATE INDEX "IDX_ae4ff3b28e3fec72aa14124d1e" ON "token_entity" ("meta_id") `) - await db.query( - `ALTER TABLE "token_entity" ADD CONSTRAINT "FK_ae4ff3b28e3fec72aa14124d1e1" FOREIGN KEY ("meta_id") REFERENCES "metadata_entity"("id") ON DELETE NO ACTION ON UPDATE NO ACTION` - ) + await db.query(`CREATE TABLE "token_entity" ("id" character varying NOT NULL, "block_number" numeric, "hash" text NOT NULL, "image" text, "media" text, "name" text, "updated_at" TIMESTAMP WITH TIME ZONE NOT NULL, "created_at" TIMESTAMP WITH TIME ZONE NOT NULL, "count" integer NOT NULL, "collection_id" character varying, CONSTRAINT "PK_687443f2a51af49b5472e2c5ddc" PRIMARY KEY ("id"))`) + await db.query(`CREATE INDEX "IDX_0eb2ed7929c3e81941fa1b51b3" ON "token_entity" ("collection_id") `) + await db.query(`CREATE INDEX "IDX_40d6049fd30532dada71922792" ON "token_entity" ("hash") `) + await db.query(`CREATE INDEX "IDX_47b385945a425667b9e690bc02" ON "token_entity" ("name") `) + await db.query(`ALTER TABLE "nft_entity" ADD "token_id" character varying`) + await db.query(`CREATE INDEX "IDX_060d0f515d293fac1d81ee61a7" ON "nft_entity" ("token_id") `) + await db.query(`ALTER TABLE "token_entity" ADD CONSTRAINT "FK_0eb2ed7929c3e81941fa1b51b35" FOREIGN KEY ("collection_id") REFERENCES "collection_entity"("id") ON DELETE NO ACTION ON UPDATE NO ACTION`) + await db.query(`ALTER TABLE "nft_entity" ADD CONSTRAINT "FK_060d0f515d293fac1d81ee61a79" FOREIGN KEY ("token_id") REFERENCES "token_entity"("id") ON DELETE NO ACTION ON UPDATE NO ACTION`) } async down(db) { - // Rollback commands from Data1696576165016 - await db.query(`ALTER TABLE "token_entity" DROP COLUMN "metadata"`) - await db.query(`ALTER TABLE "token_entity" DROP COLUMN "meta_id"`) - await db.query(`DROP INDEX "public"."IDX_ae4ff3b28e3fec72aa14124d1e"`) - await db.query(`ALTER TABLE "token_entity" DROP CONSTRAINT "FK_ae4ff3b28e3fec72aa14124d1e1"`) - - // Rollback commands from Data1696428349370 - await db.query(`ALTER TABLE "token_entity" DROP COLUMN "cheapest_nft_id"`) - await db.query(`DROP INDEX "public"."IDX_6d56119ac99ab53644b1b21a6e"`) - await db.query(`ALTER TABLE "token_entity" DROP CONSTRAINT "FK_6d56119ac99ab53644b1b21a6ef"`) - - await db.query(`DROP TABLE "token_entity"`) - await db.query(`DROP INDEX "public"."IDX_0eb2ed7929c3e81941fa1b51b3"`) - await db.query(`DROP INDEX "public"."IDX_40d6049fd30532dada71922792"`) - await db.query(`DROP INDEX "public"."IDX_47b385945a425667b9e690bc02"`) - await db.query(`ALTER TABLE "nft_entity" DROP COLUMN "token_id"`) - await db.query(`DROP INDEX "public"."IDX_060d0f515d293fac1d81ee61a7"`) - await db.query(`ALTER TABLE "token_entity" DROP CONSTRAINT "FK_0eb2ed7929c3e81941fa1b51b35"`) - await db.query(`ALTER TABLE "nft_entity" DROP CONSTRAINT "FK_060d0f515d293fac1d81ee61a79"`) + await db.query(`DROP TABLE "token_entity"`) + await db.query(`DROP INDEX "public"."IDX_0eb2ed7929c3e81941fa1b51b3"`) + await db.query(`DROP INDEX "public"."IDX_40d6049fd30532dada71922792"`) + await db.query(`DROP INDEX "public"."IDX_47b385945a425667b9e690bc02"`) + await db.query(`ALTER TABLE "nft_entity" DROP COLUMN "token_id"`) + await db.query(`DROP INDEX "public"."IDX_060d0f515d293fac1d81ee61a7"`) + await db.query(`ALTER TABLE "token_entity" DROP CONSTRAINT "FK_0eb2ed7929c3e81941fa1b51b35"`) + await db.query(`ALTER TABLE "nft_entity" DROP CONSTRAINT "FK_060d0f515d293fac1d81ee61a79"`) } -} +} \ No newline at end of file diff --git a/db/migrations/1696751796584-Data.js b/db/migrations/1696751796584-Data.js new file mode 100644 index 0000000..2e2d03b --- /dev/null +++ b/db/migrations/1696751796584-Data.js @@ -0,0 +1,25 @@ +module.exports = class Data1696751796584 { + name = 'Data1696751796584' + + async up(db) { + await db.query(`ALTER TABLE "token_entity" ADD "metadata" text`) + await db.query(`ALTER TABLE "token_entity" ADD "supply" integer NOT NULL`) + await db.query(`ALTER TABLE "token_entity" ADD "cheapest_id" character varying`) + await db.query(`ALTER TABLE "token_entity" ADD "meta_id" character varying`) + await db.query(`CREATE INDEX "IDX_637db5c040f1d9f935817ae1e8" ON "token_entity" ("cheapest_id") `) + await db.query(`CREATE INDEX "IDX_ae4ff3b28e3fec72aa14124d1e" ON "token_entity" ("meta_id") `) + await db.query(`ALTER TABLE "token_entity" ADD CONSTRAINT "FK_637db5c040f1d9f935817ae1e8a" FOREIGN KEY ("cheapest_id") REFERENCES "nft_entity"("id") ON DELETE NO ACTION ON UPDATE NO ACTION`) + await db.query(`ALTER TABLE "token_entity" ADD CONSTRAINT "FK_ae4ff3b28e3fec72aa14124d1e1" FOREIGN KEY ("meta_id") REFERENCES "metadata_entity"("id") ON DELETE NO ACTION ON UPDATE NO ACTION`) + } + + async down(db) { + await db.query(`ALTER TABLE "token_entity" DROP COLUMN "metadata"`) + await db.query(`ALTER TABLE "token_entity" DROP COLUMN "supply"`) + await db.query(`ALTER TABLE "token_entity" DROP COLUMN "cheapest_id"`) + await db.query(`ALTER TABLE "token_entity" DROP COLUMN "meta_id"`) + await db.query(`DROP INDEX "public"."IDX_637db5c040f1d9f935817ae1e8"`) + await db.query(`DROP INDEX "public"."IDX_ae4ff3b28e3fec72aa14124d1e"`) + await db.query(`ALTER TABLE "token_entity" DROP CONSTRAINT "FK_637db5c040f1d9f935817ae1e8a"`) + await db.query(`ALTER TABLE "token_entity" DROP CONSTRAINT "FK_ae4ff3b28e3fec72aa14124d1e1"`) + } +} diff --git a/schema.graphql b/schema.graphql index 03ba82c..39d8af4 100644 --- a/schema.graphql +++ b/schema.graphql @@ -30,7 +30,7 @@ type TokenEntity @entity { id: ID! blockNumber: BigInt collection: CollectionEntity - cheapestNFT: NFTEntity + cheapest: NFTEntity nfts: [NFTEntity!] @derivedFrom(field: "token") hash: String! @index image: String @@ -40,8 +40,8 @@ type TokenEntity @entity { name: String @index updatedAt: DateTime! createdAt: DateTime! + supply: Int! count: Int! - totalCount: Int! } type NFTEntity @entity { From 6559ba30af57245fda4bb8a49b18cd6063753e23 Mon Sep 17 00:00:00 2001 From: daiagi Date: Sun, 8 Oct 2023 16:53:52 +0700 Subject: [PATCH 13/30] re orgenize --- src/mappings/nfts/burn.ts | 4 +- src/mappings/nfts/buy.ts | 3 + src/mappings/nfts/list.ts | 4 +- src/mappings/nfts/mint.ts | 4 +- src/mappings/nfts/setMetadata.ts | 6 +- src/mappings/shared/handleTokenEntity.ts | 166 ----------------------- src/mappings/shared/token/burn.ts | 24 ++++ src/mappings/shared/token/buy.ts | 1 + src/mappings/shared/token/index.ts | 5 + src/mappings/shared/token/list.ts | 30 ++++ src/mappings/shared/token/mint.ts | 20 +++ src/mappings/shared/token/setMetadata.ts | 32 +++++ src/mappings/shared/token/tokenAPI.ts | 75 ++++++++++ src/mappings/shared/token/utils.ts | 20 +++ src/mappings/uniques/burn.ts | 44 +++--- src/mappings/uniques/buy.ts | 59 ++++---- src/mappings/uniques/destroy.ts | 14 +- src/mappings/uniques/list.ts | 32 ++--- src/mappings/uniques/mint.ts | 7 +- src/mappings/uniques/setMetadata.ts | 6 +- src/mappings/uniques/transfer.ts | 40 +++--- 21 files changed, 321 insertions(+), 275 deletions(-) delete mode 100644 src/mappings/shared/handleTokenEntity.ts create mode 100644 src/mappings/shared/token/burn.ts create mode 100644 src/mappings/shared/token/buy.ts create mode 100644 src/mappings/shared/token/index.ts create mode 100644 src/mappings/shared/token/list.ts create mode 100644 src/mappings/shared/token/mint.ts create mode 100644 src/mappings/shared/token/setMetadata.ts create mode 100644 src/mappings/shared/token/tokenAPI.ts create mode 100644 src/mappings/shared/token/utils.ts diff --git a/src/mappings/nfts/burn.ts b/src/mappings/nfts/burn.ts index e11c842..977abfe 100644 --- a/src/mappings/nfts/burn.ts +++ b/src/mappings/nfts/burn.ts @@ -5,7 +5,7 @@ import { debug, pending, success } from '../utils/logger' import { Action, Context, createTokenId } from '../utils/types' import { createEvent } from '../shared/event' import { calculateCollectionOwnerCountAndDistribution } from '../utils/helper' -import { eventHandlers } from '../shared/handleTokenEntity' +import { burnHandler } from '../shared/token' import { getBurnTokenEvent } from './getters' const OPERATION = Action.BURN @@ -32,7 +32,7 @@ export async function handleTokenBurn(context: Context): Promise { entity.collection.ownerCount = ownerCount entity.collection.distribution = distribution - await eventHandlers.burnHandler(context, entity) + await burnHandler(context, entity) success(OPERATION, `${id} by ${event.caller}}`) await context.store.save(entity) diff --git a/src/mappings/nfts/buy.ts b/src/mappings/nfts/buy.ts index 396cf8b..4521d39 100644 --- a/src/mappings/nfts/buy.ts +++ b/src/mappings/nfts/buy.ts @@ -4,6 +4,7 @@ import { createEvent } from '../shared/event' import { unwrap } from '../utils/extract' import { debug, pending, success } from '../utils/logger' import { Action, Context, createTokenId } from '../utils/types' +import { buyHandler } from '../shared/token' import { calculateCollectionOwnerCountAndDistribution } from '../utils/helper' import { getBuyTokenEvent } from './getters' @@ -38,6 +39,8 @@ export async function handleTokenBuy(context: Context): Promise { entity.collection.ownerCount = ownerCount entity.collection.distribution = distribution + await buyHandler(context, entity) + success(OPERATION, `${id} by ${event.caller} for ${String(event.price)}`) await context.store.save(entity) const meta = String(event.price || '') diff --git a/src/mappings/nfts/list.ts b/src/mappings/nfts/list.ts index 6455d86..8992079 100644 --- a/src/mappings/nfts/list.ts +++ b/src/mappings/nfts/list.ts @@ -4,7 +4,7 @@ import { unwrap } from '../utils/extract' import { debug, pending, success } from '../utils/logger' import { Action, Context, createTokenId } from '../utils/types' import { createEvent } from '../shared/event' -import { eventHandlers } from '../shared/handleTokenEntity' +import { listHandler } from '../shared/token' import { getPriceTokenEvent } from './getters' const OPERATION = Action.LIST @@ -23,7 +23,7 @@ export async function handleTokenList(context: Context): Promise { if (entity.price && (entity.collection.floor === 0n || entity.price < entity.collection.floor)) { entity.collection.floor = entity.price } - eventHandlers.listHandler(context, entity) + await listHandler(context, entity) success(OPERATION, `${id} by ${event.caller}} for ${String(event.price)}`) await context.store.save(entity) diff --git a/src/mappings/nfts/mint.ts b/src/mappings/nfts/mint.ts index 07d239c..0bec168 100644 --- a/src/mappings/nfts/mint.ts +++ b/src/mappings/nfts/mint.ts @@ -8,7 +8,7 @@ import { unwrap } from '../utils/extract' import { debug, pending, success } from '../utils/logger' import { Action, Context, createTokenId } from '../utils/types' import { calculateCollectionOwnerCountAndDistribution, versionOf } from '../utils/helper' -import { eventHandlers } from '../shared/handleTokenEntity' +import { mintHandler } from '../shared/token' import { getCreateTokenEvent } from './getters' const OPERATION = Action.MINT @@ -62,7 +62,7 @@ export async function handleTokenCreate(context: Context): Promise { final.image = metadata?.image final.media = metadata?.animationUrl - await eventHandlers.mintHandler(context, collection, final) + await mintHandler(context, collection, final) } success(OPERATION, `${final.id}`) diff --git a/src/mappings/nfts/setMetadata.ts b/src/mappings/nfts/setMetadata.ts index 9ea3cf7..552c615 100644 --- a/src/mappings/nfts/setMetadata.ts +++ b/src/mappings/nfts/setMetadata.ts @@ -1,4 +1,4 @@ -import { get, getOptional, getWith } from '@kodadot1/metasquid/entity' +import { get, getOptional } from '@kodadot1/metasquid/entity' import { isFetchable } from '@kodadot1/minipfs' import { unwrap } from '../utils/extract' import { Context, isNFT } from '../utils/types' @@ -6,7 +6,7 @@ import { CollectionEntity, NFTEntity } from '../../model' import { handleMetadata } from '../shared/metadata' import { debug, warn } from '../utils/logger' import { updateItemMetadataByCollection } from '../utils/cache' -import { eventHandlers } from '../shared/handleTokenEntity' +import { setMetadataHandler } from '../shared/token' import { tokenIdOf } from './types' import { getMetadataEvent } from './getters' @@ -52,7 +52,7 @@ export async function handleMetadataSet(context: Context): Promise { return } if (final instanceof NFTEntity) { - await eventHandlers.setMetadataHandler(context, collection, final) + await setMetadataHandler(context, collection, final) } } diff --git a/src/mappings/shared/handleTokenEntity.ts b/src/mappings/shared/handleTokenEntity.ts deleted file mode 100644 index b5d0fb4..0000000 --- a/src/mappings/shared/handleTokenEntity.ts +++ /dev/null @@ -1,166 +0,0 @@ -import { create, getOptional, getWith } from '@kodadot1/metasquid/entity' -import md5 from 'md5' -import { CollectionEntity as CE, NFTEntity as NE, TokenEntity as TE } from '../../model' -import { debug, warn } from '../utils/logger' -import { Context } from '../utils/types' - -const OPERATION = 'TokenEntity' as any - -function generateTokenId(collectionId: string, nftMedia: string): string { - return `${collectionId}-${md5(nftMedia)}` -} - -async function createToken(context: Context, collection: CE, nft: NE): Promise { - const nftMedia = nft.image ?? nft.media - if (!nftMedia || nftMedia === '') { - warn(OPERATION, `MISSING NFT MEDIA ${nft.id}`) - return - } - const tokenId = generateTokenId(collection.id, nftMedia) - debug(OPERATION, { createToken: `Create TOKEN ${tokenId} for NFT ${nft.id}` }) - const tokenName = typeof nft.name === 'string' ? nft.name?.replace(/([#_]\d+$)/g, '').trim() : '' - - const token = create(TE, tokenId, { - createdAt: nft.createdAt, - collection, - name: tokenName, - count: 1, - totalCount: 1, - hash: md5(tokenId), - image: nft.image, - media: nft.media, - metadata: nft.metadata, - meta: nft.meta, - blockNumber: nft.blockNumber, - updatedAt: nft.updatedAt, - id: tokenId, - }) - - nft.token = token - await context.store.save(token) - await context.store.save(nft) - - return token -} - -async function addNftToToken(context: Context, nft: NE, token: TE): Promise { - debug(OPERATION, { updateToken: `Add NFT ${nft.id} to TOKEN ${token.id} for ` }) - token.count += 1 - token.totalCount += 1 - token.updatedAt = nft.updatedAt - nft.token = token - await context.store.save(token) - await context.store.save(nft) - - return token -} - -async function removeNftFromToken(context: Context, nft: NE, token: TE): Promise { - if (!token) { - return - } - debug(OPERATION, { removeNftFromToken: `Unlink NFT ${nft.id} from TOKEN ${token.id}` }) - - await context.store.update(NE, nft.id, { token: null }) - const updatedTotalCount = token.totalCount - 1 - await context.store.update(TE, token.id, { count: token.count - 1, totalCount: updatedTotalCount , updatedAt: nft.updatedAt }) - - if (updatedTotalCount === 0) { - debug(OPERATION, { deleteEmptyToken: `delete empty token ${token.id}` }) - - await context.store.delete(TE, token.id) - } -} - -async function mintHandler(context: Context, collection: CE, nft: NE): Promise { - const nftMedia = nft.image ?? nft.media - debug(OPERATION, { mintHandler: `Handle mint for NFT ${nft.id}` }) - - if (!nftMedia || nftMedia === '') { - warn(OPERATION, `MISSING NFT MEDIA ${nft.id}`) - return - } - - const existingToken = await getOptional(context.store, TE, generateTokenId(collection.id, nftMedia)) - return await (existingToken ? addNftToToken(context, nft, existingToken) : createToken(context, collection, nft)) -} - -async function handleMetadataSet(context: Context, collection: CE, nft: NE): Promise { - debug(OPERATION, { handleMetadataSet: `Handle set metadata for NFT ${nft.id}` }) - const nftMedia = nft.image ?? nft.media - - if (!nftMedia || nftMedia === '') { - warn(OPERATION, `MISSING NFT MEDIA ${nft.id}`) - return - } - - let nftWithToken, existingToken - try { - [nftWithToken, existingToken] = await Promise.all([ - getWith(context.store, NE, nft.id, { token: true }), - getOptional(context.store, TE, generateTokenId(collection.id, nftMedia)), - ]) - } catch (error) { - warn(OPERATION, `ERROR ${error}`) - return - } - if (nftWithToken.token) { - await removeNftFromToken(context, nft, nftWithToken.token) - } - return await (existingToken ? addNftToToken(context, nft, existingToken) : createToken(context, collection, nft)) -} - -async function handleBurn(context: Context, nft: NE): Promise { - debug(OPERATION, { handleBurn: `Handle Burn for NFT ${nft.id}` }) - const nftMedia = nft.image ?? nft.media - - if (!nftMedia || nftMedia === '') { - warn(OPERATION, `MISSING NFT MEDIA ${nft.id}`) - return - } - - const token = await getOptional(context.store, TE, generateTokenId(nft.collection.id, nftMedia)) - - if (!token) { - return - } - - debug(OPERATION, { BURN: `decrement Token's ${token.id} count` }) - - await context.store.update(TE, token.id, { count: token.count - 1, updatedAt: nft.updatedAt }) -} - -async function listHandler(context: Context, nft: NE): Promise { - const nftMedia = nft.image ?? nft.media - - if (!nftMedia || nftMedia === '') { - warn(OPERATION, `MISSING NFT MEDIA ${nft.id}`) - return - } - - try { - const nftWithToken = await getWith(context.store, NE, nft.id, { token: true }) - if (!nftWithToken.token) { - warn(OPERATION, `nft ${nft.id} is not linked to a token`) - return - } - const cheapestNFT = nftWithToken.token.cheapestNFT - if (!nft.price && !cheapestNFT?.price) { - return; - } - - if (!cheapestNFT?.price || (nft.price && nft.price < cheapestNFT.price)) { - context.store.update(TE, nftWithToken.token.id, { cheapestNFT: nft, updatedAt: nft.updatedAt }) - } - } catch (error) { - warn(OPERATION, `ERROR ${error}`) - } -} - - -export const eventHandlers = { - setMetadataHandler: handleMetadataSet, - mintHandler, - burnHandler: handleBurn, - listHandler -} diff --git a/src/mappings/shared/token/burn.ts b/src/mappings/shared/token/burn.ts new file mode 100644 index 0000000..820d32d --- /dev/null +++ b/src/mappings/shared/token/burn.ts @@ -0,0 +1,24 @@ +import { getOptional } from '@kodadot1/metasquid/entity' +import { Context } from '../../utils/types' +import { NFTEntity as NE, TokenEntity as TE } from '../../../model' +import { debug } from '../../utils/logger' +import { OPERATION, generateTokenId, mediaOf } from './utils' + +export async function burnHandler(context: Context, nft: NE): Promise { + debug(OPERATION, { handleBurn: `Handle Burn for NFT ${nft.id}` }) + + const nftMedia = mediaOf(nft) + if (!nftMedia) { + return + } + + const token = await getOptional(context.store, TE, generateTokenId(nft.collection.id, nftMedia)) + + if (!token) { + return + } + + debug(OPERATION, { BURN: `decrement Token's ${token.id} supply` }) + + await context.store.update(TE, token.id, { supply: token.supply - 1, updatedAt: nft.updatedAt }) +} diff --git a/src/mappings/shared/token/buy.ts b/src/mappings/shared/token/buy.ts new file mode 100644 index 0000000..7f24955 --- /dev/null +++ b/src/mappings/shared/token/buy.ts @@ -0,0 +1 @@ +export { listHandler as buyHandler } from './list' diff --git a/src/mappings/shared/token/index.ts b/src/mappings/shared/token/index.ts new file mode 100644 index 0000000..b8feba8 --- /dev/null +++ b/src/mappings/shared/token/index.ts @@ -0,0 +1,5 @@ +export * from './mint' +export * from './list' +export * from './setMetadata' +export * from './burn' +export * from './buy' diff --git a/src/mappings/shared/token/list.ts b/src/mappings/shared/token/list.ts new file mode 100644 index 0000000..963dab4 --- /dev/null +++ b/src/mappings/shared/token/list.ts @@ -0,0 +1,30 @@ +import { getWith } from '@kodadot1/metasquid/entity' +import { Context } from '../../utils/types' +import { NFTEntity as NE, TokenEntity as TE } from '../../../model' +import { warn } from '../../utils/logger' +import { OPERATION, mediaOf } from './utils' + +export async function listHandler(context: Context, nft: NE): Promise { + const nftMedia = mediaOf(nft) + if (!nftMedia) { + return + } + + try { + const nftWithToken = await getWith(context.store, NE, nft.id, { token: true }) + if (!nftWithToken.token) { + warn(OPERATION, `nft ${nft.id} is not linked to a token`) + return + } + const cheapest = nftWithToken.token.cheapest + if (!nft.price && !cheapest?.price) { + return + } + + if (!cheapest?.price || (nft.price && nft.price < cheapest.price)) { + context.store.update(TE, nftWithToken.token.id, { cheapest: nft, updatedAt: nft.updatedAt }) + } + } catch (error) { + warn(OPERATION, `ERROR ${error}`) + } +} diff --git a/src/mappings/shared/token/mint.ts b/src/mappings/shared/token/mint.ts new file mode 100644 index 0000000..32b7ad2 --- /dev/null +++ b/src/mappings/shared/token/mint.ts @@ -0,0 +1,20 @@ +import { getOptional } from '@kodadot1/metasquid/entity' +import { Context } from '../../utils/types' +import { CollectionEntity as CE, NFTEntity as NE, TokenEntity as TE } from '../../../model' +import { debug } from '../../utils/logger' +import { OPERATION, generateTokenId, mediaOf } from './utils' +import { TokenAPI } from './tokenAPI' + +export async function mintHandler(context: Context, collection: CE, nft: NE): Promise { + debug(OPERATION, { mintHandler: `Handle mint for NFT ${nft.id}` }) + + const nftMedia = mediaOf(nft) + if (!nftMedia) { + return + } + + const tokenApi = new TokenAPI(context.store) + + const existingToken = await getOptional(context.store, TE, generateTokenId(collection.id, nftMedia)) + return await (existingToken ? tokenApi.addNftToToken(nft, existingToken) : tokenApi.create(collection, nft)) +} diff --git a/src/mappings/shared/token/setMetadata.ts b/src/mappings/shared/token/setMetadata.ts new file mode 100644 index 0000000..db6fdbd --- /dev/null +++ b/src/mappings/shared/token/setMetadata.ts @@ -0,0 +1,32 @@ +import { getOptional, getWith } from '@kodadot1/metasquid/entity' +import { Context } from '../../utils/types' +import { CollectionEntity as CE, NFTEntity as NE, TokenEntity as TE } from '../../../model' +import { debug, warn } from '../../utils/logger' +import { OPERATION, generateTokenId, mediaOf } from './utils' +import { TokenAPI } from './tokenAPI' + +export async function setMetadataHandler(context: Context, collection: CE, nft: NE): Promise { + debug(OPERATION, { handleMetadataSet: `Handle set metadata for NFT ${nft.id}` }) + const nftMedia = mediaOf(nft) + if (!nftMedia) { + return + } + + let nftWithToken, existingToken + try { + [nftWithToken, existingToken] = await Promise.all([ + getWith(context.store, NE, nft.id, { token: true }), + getOptional(context.store, TE, generateTokenId(collection.id, nftMedia)), + ]) + } catch (error) { + warn(OPERATION, `ERROR ${error}`) + return + } + + const tokenAPI = new TokenAPI(context.store) + + if (nftWithToken.token) { + await tokenAPI.removeNftFromToken(nft, nftWithToken.token) + } + return await (existingToken ? tokenAPI.addNftToToken(nft, existingToken) : tokenAPI.create(collection, nft)) +} diff --git a/src/mappings/shared/token/tokenAPI.ts b/src/mappings/shared/token/tokenAPI.ts new file mode 100644 index 0000000..c7073b4 --- /dev/null +++ b/src/mappings/shared/token/tokenAPI.ts @@ -0,0 +1,75 @@ +import { create as createEntity } from '@kodadot1/metasquid/entity' +import md5 from 'md5' +import { Store } from '../../utils/types' +import { CollectionEntity as CE, NFTEntity as NE, TokenEntity as TE } from '../../../model' +import { debug, warn } from '../../utils/logger' +import { OPERATION, generateTokenId } from './utils' + +export class TokenAPI { + constructor(private store: Store) {} + + async create(collection: CE, nft: NE): Promise { + const nftMedia = nft.image ?? nft.media + if (!nftMedia || nftMedia === '') { + warn(OPERATION, `MISSING NFT MEDIA ${nft.id}`) + return + } + const tokenId = generateTokenId(collection.id, nftMedia) + debug(OPERATION, { createToken: `Create TOKEN ${tokenId} for NFT ${nft.id}` }) + const tokenName = typeof nft.name === 'string' ? nft.name?.replace(/([#_]\d+$)/g, '').trim() : '' + + const token = createEntity(TE, tokenId, { + createdAt: nft.createdAt, + collection, + name: tokenName, + count: 1, + supply: 1, + hash: md5(tokenId), + image: nft.image, + media: nft.media, + metadata: nft.metadata, + meta: nft.meta, + blockNumber: nft.blockNumber, + updatedAt: nft.updatedAt, + id: tokenId, + }) + + await this.store.save(token) + await this.store.update(NE, nft.id, { token }) + + return token + } + + async removeNftFromToken(nft: NE, token: TE): Promise { + if (!token) { + return + } + debug(OPERATION, { removeNftFromToken: `Unlink NFT ${nft.id} from TOKEN ${token.id}` }) + + await this.store.update(NE, nft.id, { token: null }) + const updatedCount = token.count - 1 + await this.store.update(TE, token.id, { + supply: token.supply - 1, + count: updatedCount, + updatedAt: nft.updatedAt, + }) + + if (updatedCount === 0) { + debug(OPERATION, { deleteEmptyToken: `delete empty token ${token.id}` }) + + await this.store.delete(TE, token.id) + } + } + + async addNftToToken(nft: NE, token: TE): Promise { + debug(OPERATION, { updateToken: `Add NFT ${nft.id} to TOKEN ${token.id} for ` }) + token.count += 1 + token.supply += 1 + token.updatedAt = nft.updatedAt + nft.token = token + await this.store.save(token) + await this.store.save(nft) + + return token + } +} diff --git a/src/mappings/shared/token/utils.ts b/src/mappings/shared/token/utils.ts new file mode 100644 index 0000000..e300042 --- /dev/null +++ b/src/mappings/shared/token/utils.ts @@ -0,0 +1,20 @@ +import md5 from 'md5' +import { NFTEntity as NE } from '../../../model' +import { warn } from '../../utils/logger' + +export const OPERATION = 'TokenEntity' as any + +export function generateTokenId(collectionId: string, nftMedia: string): string { + return `${collectionId}-${md5(nftMedia)}` +} + +export const mediaOf = (nft: NE): string | undefined => { + const nftMedia = nft.image ?? nft.media + + if (!nftMedia || nftMedia === '') { + warn(OPERATION, `MISSING NFT MEDIA ${nft.id}`) + return undefined + } + + return nftMedia +} diff --git a/src/mappings/uniques/burn.ts b/src/mappings/uniques/burn.ts index 00fa4f3..2815866 100644 --- a/src/mappings/uniques/burn.ts +++ b/src/mappings/uniques/burn.ts @@ -5,37 +5,37 @@ import { debug, pending, success } from '../utils/logger' import { Action, Context, createTokenId } from '../utils/types' import { createEvent } from '../shared/event' import { calculateCollectionOwnerCountAndDistribution } from '../utils/helper' -import { eventHandlers } from '../shared/handleTokenEntity' +import { burnHandler } from '../shared/token' import { getBurnTokenEvent } from './getters' const OPERATION = Action.BURN export async function handleTokenBurn(context: Context): Promise { - pending(OPERATION, `${context.block.height}`) - const event = unwrap(context, getBurnTokenEvent) - debug(OPERATION, event) + // pending(OPERATION, `${context.block.height}`) + // const event = unwrap(context, getBurnTokenEvent) + // debug(OPERATION, event) - const id = createTokenId(event.collectionId, event.sn) - const entity = await getWith(context.store, NE, id, { collection: true }) + // const id = createTokenId(event.collectionId, event.sn) + // const entity = await getWith(context.store, NE, id, { collection: true }) - const { ownerCount, distribution } = await calculateCollectionOwnerCountAndDistribution( - context.store, - entity.collection.id, - entity.currentOwner - ) + // const { ownerCount, distribution } = await calculateCollectionOwnerCountAndDistribution( + // context.store, + // entity.collection.id, + // entity.currentOwner + // ) - entity.burned = true - entity.updatedAt = event.timestamp + // entity.burned = true + // entity.updatedAt = event.timestamp - entity.collection.updatedAt = event.timestamp - entity.collection.supply -= 1 - entity.collection.ownerCount = ownerCount - entity.collection.distribution = distribution + // entity.collection.updatedAt = event.timestamp + // entity.collection.supply -= 1 + // entity.collection.ownerCount = ownerCount + // entity.collection.distribution = distribution - await eventHandlers.burnHandler(context, entity) + // await burnHandler(context, entity) - success(OPERATION, `${id} by ${event.caller}}`) - await context.store.save(entity) - const meta = entity.metadata ?? '' - await createEvent(entity, OPERATION, event, meta, context.store) + // success(OPERATION, `${id} by ${event.caller}}`) + // await context.store.save(entity) + // const meta = entity.metadata ?? '' + // await createEvent(entity, OPERATION, event, meta, context.store) } diff --git a/src/mappings/uniques/buy.ts b/src/mappings/uniques/buy.ts index 396cf8b..3d22853 100644 --- a/src/mappings/uniques/buy.ts +++ b/src/mappings/uniques/buy.ts @@ -5,41 +5,44 @@ import { unwrap } from '../utils/extract' import { debug, pending, success } from '../utils/logger' import { Action, Context, createTokenId } from '../utils/types' import { calculateCollectionOwnerCountAndDistribution } from '../utils/helper' +import { buyHandler } from '../shared/token' import { getBuyTokenEvent } from './getters' const OPERATION = Action.BUY export async function handleTokenBuy(context: Context): Promise { - pending(OPERATION, `${context.block.height}`) - const event = unwrap(context, getBuyTokenEvent) - debug(OPERATION, event, true) + // pending(OPERATION, `${context.block.height}`) + // const event = unwrap(context, getBuyTokenEvent) + // debug(OPERATION, event, true) - const id = createTokenId(event.collectionId, event.sn) - const entity = await getWith(context.store, NE, id, { collection: true }) + // const id = createTokenId(event.collectionId, event.sn) + // const entity = await getWith(context.store, NE, id, { collection: true }) - const originalPrice = entity.price - const originalOwner = entity.currentOwner ?? undefined + // const originalPrice = entity.price + // const originalOwner = entity.currentOwner ?? undefined - entity.price = BigInt(0) - entity.currentOwner = event.caller - entity.updatedAt = event.timestamp - if (originalPrice) { - entity.collection.volume += originalPrice - if (originalPrice > entity.collection.highestSale) { - entity.collection.highestSale = originalPrice - } - } - const { ownerCount, distribution } = await calculateCollectionOwnerCountAndDistribution( - context.store, - entity.collection.id, - entity.currentOwner, - originalOwner - ) - entity.collection.ownerCount = ownerCount - entity.collection.distribution = distribution + // entity.price = BigInt(0) + // entity.currentOwner = event.caller + // entity.updatedAt = event.timestamp + // if (originalPrice) { + // entity.collection.volume += originalPrice + // if (originalPrice > entity.collection.highestSale) { + // entity.collection.highestSale = originalPrice + // } + // } + // const { ownerCount, distribution } = await calculateCollectionOwnerCountAndDistribution( + // context.store, + // entity.collection.id, + // entity.currentOwner, + // originalOwner + // ) + // entity.collection.ownerCount = ownerCount + // entity.collection.distribution = distribution - success(OPERATION, `${id} by ${event.caller} for ${String(event.price)}`) - await context.store.save(entity) - const meta = String(event.price || '') - await createEvent(entity, OPERATION, event, meta, context.store, event.currentOwner) + // await buyHandler(context, entity) + + // success(OPERATION, `${id} by ${event.caller} for ${String(event.price)}`) + // await context.store.save(entity) + // const meta = String(event.price || '') + // await createEvent(entity, OPERATION, event, meta, context.store, event.currentOwner) } diff --git a/src/mappings/uniques/destroy.ts b/src/mappings/uniques/destroy.ts index a69833f..4642612 100644 --- a/src/mappings/uniques/destroy.ts +++ b/src/mappings/uniques/destroy.ts @@ -8,13 +8,13 @@ import { getDestroyCollectionEvent } from './getters' const OPERATION = Action.DESTROY export async function handleCollectionDestroy(context: Context): Promise { - pending(OPERATION, `${context.block.height}`) - const event = unwrap(context, getDestroyCollectionEvent) - debug(OPERATION, event) + // pending(OPERATION, `${context.block.height}`) + // const event = unwrap(context, getDestroyCollectionEvent) + // debug(OPERATION, event) - const entity = await get(context.store, CE, event.id) - entity.burned = true + // const entity = await get(context.store, CE, event.id) + // entity.burned = true - success(OPERATION, `${event.id} by ${event.caller}}`) - await context.store.save(entity) + // success(OPERATION, `${event.id} by ${event.caller}}`) + // await context.store.save(entity) } diff --git a/src/mappings/uniques/list.ts b/src/mappings/uniques/list.ts index 6455d86..7488181 100644 --- a/src/mappings/uniques/list.ts +++ b/src/mappings/uniques/list.ts @@ -4,30 +4,30 @@ import { unwrap } from '../utils/extract' import { debug, pending, success } from '../utils/logger' import { Action, Context, createTokenId } from '../utils/types' import { createEvent } from '../shared/event' -import { eventHandlers } from '../shared/handleTokenEntity' +import { listHandler } from '../shared/token' import { getPriceTokenEvent } from './getters' const OPERATION = Action.LIST const UNLIST = Action.UNLIST export async function handleTokenList(context: Context): Promise { - pending(OPERATION, `${context.block.height}`) - const event = unwrap(context, getPriceTokenEvent) - debug(OPERATION, event, true) + // pending(OPERATION, `${context.block.height}`) + // const event = unwrap(context, getPriceTokenEvent) + // debug(OPERATION, event, true) - const id = createTokenId(event.collectionId, event.sn) - const entity = await getWith(context.store, NE, id, { collection: true }) + // const id = createTokenId(event.collectionId, event.sn) + // const entity = await getWith(context.store, NE, id, { collection: true }) - entity.price = event.price + // entity.price = event.price - if (entity.price && (entity.collection.floor === 0n || entity.price < entity.collection.floor)) { - entity.collection.floor = entity.price - } - eventHandlers.listHandler(context, entity) + // if (entity.price && (entity.collection.floor === 0n || entity.price < entity.collection.floor)) { + // entity.collection.floor = entity.price + // } + // await listHandler(context, entity) - success(OPERATION, `${id} by ${event.caller}} for ${String(event.price)}`) - await context.store.save(entity) - const meta = String(event.price || '') - const interaction = event.price ? OPERATION : UNLIST - await createEvent(entity, interaction, event, meta, context.store) + // success(OPERATION, `${id} by ${event.caller}} for ${String(event.price)}`) + // await context.store.save(entity) + // const meta = String(event.price || '') + // const interaction = event.price ? OPERATION : UNLIST + // await createEvent(entity, interaction, event, meta, context.store) } diff --git a/src/mappings/uniques/mint.ts b/src/mappings/uniques/mint.ts index a704e8d..2f27cff 100644 --- a/src/mappings/uniques/mint.ts +++ b/src/mappings/uniques/mint.ts @@ -7,8 +7,8 @@ import { handleMetadata } from '../shared/metadata' import { unwrap } from '../utils/extract' import { debug, pending, success } from '../utils/logger' import { Action, Context, createTokenId } from '../utils/types' -import { versionOf , calculateCollectionOwnerCountAndDistribution } from '../utils/helper' -import { eventHandlers } from '../shared/handleTokenEntity' +import { versionOf, calculateCollectionOwnerCountAndDistribution } from '../utils/helper' +import { mintHandler } from '../shared/token' import { getCreateTokenEvent } from './getters' const OPERATION = Action.MINT @@ -63,8 +63,7 @@ export async function handleTokenCreate(context: Context): Promise { final.media = metadata?.animationUrl } - await eventHandlers.mintHandler(context, collection, final) - + await mintHandler(context, collection, final) success(OPERATION, `${final.id}`) await context.store.save(final) diff --git a/src/mappings/uniques/setMetadata.ts b/src/mappings/uniques/setMetadata.ts index 26edcaf..765d750 100644 --- a/src/mappings/uniques/setMetadata.ts +++ b/src/mappings/uniques/setMetadata.ts @@ -1,4 +1,4 @@ -import { get, getOptional, getWith } from '@kodadot1/metasquid/entity' +import { get, getOptional } from '@kodadot1/metasquid/entity' import { isFetchable } from '@kodadot1/minipfs' import { unwrap } from '../utils/extract' import { Context, isNFT } from '../utils/types' @@ -6,7 +6,7 @@ import { CollectionEntity, NFTEntity } from '../../model' import { handleMetadata } from '../shared/metadata' import { debug, warn } from '../utils/logger' import { updateItemMetadataByCollection } from '../utils/cache' -import { eventHandlers } from '../shared/handleTokenEntity' +import { setMetadataHandler } from '../shared/token' import { tokenIdOf } from './types' import { getMetadataEvent } from './getters' @@ -53,7 +53,7 @@ export async function handleMetadataSet(context: Context): Promise { return } - await eventHandlers.setMetadataHandler(context, collection, final as NFTEntity) + await setMetadataHandler(context, collection, final as NFTEntity) } } diff --git a/src/mappings/uniques/transfer.ts b/src/mappings/uniques/transfer.ts index 9e16e5c..ce397af 100644 --- a/src/mappings/uniques/transfer.ts +++ b/src/mappings/uniques/transfer.ts @@ -10,28 +10,28 @@ import { getTransferTokenEvent } from './getters' const OPERATION = Action.SEND export async function handleTokenTransfer(context: Context): Promise { - pending(OPERATION, `${context.block.height}`) - const event = unwrap(context, getTransferTokenEvent) - debug(OPERATION, event) + // pending(OPERATION, `${context.block.height}`) + // const event = unwrap(context, getTransferTokenEvent) + // debug(OPERATION, event) - const id = createTokenId(event.collectionId, event.sn) - const entity = await getWith(context.store, NE, id, { collection: true }) + // const id = createTokenId(event.collectionId, event.sn) + // const entity = await getWith(context.store, NE, id, { collection: true }) - const oldOwner = entity.currentOwner - entity.price = BigInt(0) - entity.currentOwner = event.to - entity.updatedAt = event.timestamp + // const oldOwner = entity.currentOwner + // entity.price = BigInt(0) + // entity.currentOwner = event.to + // entity.updatedAt = event.timestamp - const { ownerCount, distribution } = await calculateCollectionOwnerCountAndDistribution( - context.store, - entity.collection.id, - entity.currentOwner, - oldOwner - ) - entity.collection.ownerCount = ownerCount - entity.collection.distribution = distribution + // const { ownerCount, distribution } = await calculateCollectionOwnerCountAndDistribution( + // context.store, + // entity.collection.id, + // entity.currentOwner, + // oldOwner + // ) + // entity.collection.ownerCount = ownerCount + // entity.collection.distribution = distribution - success(OPERATION, `${id} from ${event.caller} to ${event.to}`) - await context.store.save(entity) - await createEvent(entity, OPERATION, event, event.to, context.store, oldOwner) + // success(OPERATION, `${id} from ${event.caller} to ${event.to}`) + // await context.store.save(entity) + // await createEvent(entity, OPERATION, event, event.to, context.store, oldOwner) } From 9a4aa70fc440428dcee92bd2cd41b56c9f78505d Mon Sep 17 00:00:00 2001 From: daiagi Date: Sun, 8 Oct 2023 16:55:40 +0700 Subject: [PATCH 14/30] schema name changes --- src/model/generated/tokenEntity.model.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/model/generated/tokenEntity.model.ts b/src/model/generated/tokenEntity.model.ts index 482789d..21a0ea8 100644 --- a/src/model/generated/tokenEntity.model.ts +++ b/src/model/generated/tokenEntity.model.ts @@ -22,7 +22,7 @@ export class TokenEntity { @Index_() @ManyToOne_(() => NFTEntity, {nullable: true}) - cheapestNFT!: NFTEntity | undefined | null + cheapest!: NFTEntity | undefined | null @OneToMany_(() => NFTEntity, e => e.token) nfts!: NFTEntity[] @@ -55,8 +55,8 @@ export class TokenEntity { createdAt!: Date @Column_("int4", {nullable: false}) - count!: number + supply!: number @Column_("int4", {nullable: false}) - totalCount!: number + count!: number } From 45788686a9fbbedd41a10381ef63057e47b21692 Mon Sep 17 00:00:00 2001 From: daiagi Date: Sun, 8 Oct 2023 16:56:10 +0700 Subject: [PATCH 15/30] name changes- resolver --- .../model/tokenEntity.model.ts | 28 ++++----- .../query/tokenEntityByOwner.ts | 59 ++++++++++--------- .../resolvers/tokenEntityByOwner.ts | 31 ++++++---- 3 files changed, 63 insertions(+), 55 deletions(-) diff --git a/src/server-extension/model/tokenEntity.model.ts b/src/server-extension/model/tokenEntity.model.ts index 8247936..b8d4171 100644 --- a/src/server-extension/model/tokenEntity.model.ts +++ b/src/server-extension/model/tokenEntity.model.ts @@ -20,7 +20,7 @@ registerEnumType(OrderBy, { @ObjectType() -class CheapestNFT { +class Cheapest { @Field(() => String, { nullable: true }) id!: string; @@ -85,8 +85,8 @@ export class TokenEntityByOwner { @Field(() => Number, { nullable: false }) count!: number; - @Field(() => CheapestNFT) - cheapestNFT!: CheapestNFT; + @Field(() => Cheapest) + cheapest!: Cheapest; @Field(() => Collection) collection!: Collection; @@ -112,40 +112,40 @@ export class TokenEntityByOwnerQueryResult { media!: string; @Field(() => Date, { nullable: false }) - createdAt!: Date; + created_at!: Date; @Field(() => Date, { nullable: false }) - updatedAt!: Date; + updated_at!: Date; @Field(() => BigInt, { nullable: false }) - blockNumber!: bigint; + block_number!: bigint; @Field(() => Number, { nullable: false }) count!: number; @Field(() => String, { nullable: true }) - cheapestNFTId!: string; + cheapest_id!: string; @Field(() => BigInt, { nullable: true }) - cheapestNFTPrice!: bigint; + cheapest_price!: bigint; @Field(() => String, { nullable: false }) - collectionId!: string; + collection_id!: string; @Field(() => String, { nullable: false }) - collectionName!: string; + collection_name!: string; @Field(() => String, { nullable: false }) - metaId!: string; + meta_id!: string; @Field(() => String, { nullable: true }) - metaDescription?: string; + meta_description?: string; @Field(() => String, { nullable: true }) - metaImage?: string; + meta_image?: string; @Field(() => String, { nullable: true }) - metaAnimationUrl?: string; + meta_animation_url?: string; diff --git a/src/server-extension/query/tokenEntityByOwner.ts b/src/server-extension/query/tokenEntityByOwner.ts index 062268d..b49cf58 100644 --- a/src/server-extension/query/tokenEntityByOwner.ts +++ b/src/server-extension/query/tokenEntityByOwner.ts @@ -1,13 +1,18 @@ -export const tokenEntityByOwner = `WITH CheapestNFT AS ( +export const tokenEntityByOwner = `WITH cheapest_nft AS ( SELECT ne.token_id, - MIN(ne.price) AS "cheapestNFTPrice" + ne.id AS nft_id, + ne.price AS cheapest, + ROW_NUMBER() OVER(PARTITION BY ne.token_id ORDER BY ne.price ASC) AS rnk FROM nft_entity ne WHERE ne.current_owner = $1 - GROUP BY - ne.token_id +), +nft_count AS ( + SELECT token_id, COUNT(*) as count + FROM nft_entity + GROUP BY token_id ) SELECT @@ -16,33 +21,29 @@ SELECT t.image as image, t.media as media, t.metadata as metadata, - me.id as "metaId", - me.description as "metaDescription", - me.image as "metaImage", - me.animation_url as "metaAnimationUrl", - t.block_number as "blockNumber", - t.created_at AS "createdAt", - t.updated_at AS "updatedAt", - COUNT(ne.id) as count, - - c."cheapestNFTPrice", - - col.id AS "collectionId", - col.name AS "collectionName" + me.id as meta_id, + me.description as meta_description, + me.image as meta_image, + me.animation_url as meta_animation_url, + t.block_number as block_number, + t.created_at AS created_at, + t.updated_at AS updated_at, + nft_count.count as count, + c.nft_id as cheapest_id, + c.cheapest as cheapest_price, + col.id AS collection_id, + col.name AS collection_name FROM token_entity as t - LEFT JOIN nft_entity as ne ON t.id = ne.token_id AND ne.current_owner = $1 - LEFT JOIN CheapestNFT as c ON t.id = c.token_id - LEFT JOIN collection_entity as col ON t.collection_id = col.id - LEFT JOIN metadata_entity as me ON t.meta_id = me.id -GROUP BY - t.id, c."cheapestNFTPrice", col.id, me.id -HAVING - COUNT(ne.id) > 0 AND - ($5::bigint IS NULL OR c."cheapestNFTPrice" >= $5::bigint) AND - ($6::bigint IS NULL OR c."cheapestNFTPrice" > $6::bigint) AND - ($7::bigint IS NULL OR c."cheapestNFTPrice" <= $7::bigint) + JOIN cheapest_nft as c ON t.id = c.token_id AND c.rnk = 1 + JOIN collection_entity as col ON t.collection_id = col.id + JOIN metadata_entity as me ON t.meta_id = me.id + JOIN nft_count ON t.id = nft_count.token_id +WHERE + ($5::bigint IS NULL OR c.cheapest >= $5::bigint) AND + ($6::bigint IS NULL OR c.cheapest > $6::bigint) AND + ($7::bigint IS NULL OR c.cheapest <= $7::bigint) ORDER BY $4 LIMIT $2 OFFSET $3; -` \ No newline at end of file +` diff --git a/src/server-extension/resolvers/tokenEntityByOwner.ts b/src/server-extension/resolvers/tokenEntityByOwner.ts index 616890a..9ac1d22 100644 --- a/src/server-extension/resolvers/tokenEntityByOwner.ts +++ b/src/server-extension/resolvers/tokenEntityByOwner.ts @@ -31,23 +31,30 @@ export class TokenResolver { price_gt, price_lte, ]) - return result.map((row) => ({ + return result.map(this.mapRowToTokenEntityByOwner) + } + + private mapRowToTokenEntityByOwner(row: TokenEntityByOwnerQueryResult): TokenEntityByOwner { + return { ...row, - cheapestNFT: { - id: row.cheapestNFTId, - price: row.cheapestNFTPrice, + blockNumber: row.block_number, + createdAt: row.created_at, + updatedAt: row.updated_at, + cheapest: { + id: row.cheapest_id, + price: row.cheapest_price, }, collection: { - id: row.collectionId, - name: row.collectionName, + id: row.collection_id, + name: row.collection_name, }, meta: { - id: row.metaId, - description: row.metaDescription, - animationUrl: row.metaAnimationUrl, - image: row.metaImage, - } - })) + id: row.meta_id, + description: row.meta_description, + animationUrl: row.meta_animation_url, + image: row.meta_image, + }, + } } private getOrderByQuery(orderBy?: OrderBy): string { From 96036d34b7010c1f614792384ce860d76779f195 Mon Sep 17 00:00:00 2001 From: daiagi Date: Sun, 8 Oct 2023 16:57:47 +0700 Subject: [PATCH 16/30] oops - undo comment out of unique handlers --- src/mappings/uniques/burn.ts | 42 +++++++++++------------ src/mappings/uniques/buy.ts | 58 ++++++++++++++++---------------- src/mappings/uniques/destroy.ts | 14 ++++---- src/mappings/uniques/list.ts | 30 ++++++++--------- src/mappings/uniques/transfer.ts | 40 +++++++++++----------- 5 files changed, 92 insertions(+), 92 deletions(-) diff --git a/src/mappings/uniques/burn.ts b/src/mappings/uniques/burn.ts index 2815866..2be4c25 100644 --- a/src/mappings/uniques/burn.ts +++ b/src/mappings/uniques/burn.ts @@ -11,31 +11,31 @@ import { getBurnTokenEvent } from './getters' const OPERATION = Action.BURN export async function handleTokenBurn(context: Context): Promise { - // pending(OPERATION, `${context.block.height}`) - // const event = unwrap(context, getBurnTokenEvent) - // debug(OPERATION, event) + pending(OPERATION, `${context.block.height}`) + const event = unwrap(context, getBurnTokenEvent) + debug(OPERATION, event) - // const id = createTokenId(event.collectionId, event.sn) - // const entity = await getWith(context.store, NE, id, { collection: true }) + const id = createTokenId(event.collectionId, event.sn) + const entity = await getWith(context.store, NE, id, { collection: true }) - // const { ownerCount, distribution } = await calculateCollectionOwnerCountAndDistribution( - // context.store, - // entity.collection.id, - // entity.currentOwner - // ) + const { ownerCount, distribution } = await calculateCollectionOwnerCountAndDistribution( + context.store, + entity.collection.id, + entity.currentOwner + ) - // entity.burned = true - // entity.updatedAt = event.timestamp + entity.burned = true + entity.updatedAt = event.timestamp - // entity.collection.updatedAt = event.timestamp - // entity.collection.supply -= 1 - // entity.collection.ownerCount = ownerCount - // entity.collection.distribution = distribution + entity.collection.updatedAt = event.timestamp + entity.collection.supply -= 1 + entity.collection.ownerCount = ownerCount + entity.collection.distribution = distribution - // await burnHandler(context, entity) + await burnHandler(context, entity) - // success(OPERATION, `${id} by ${event.caller}}`) - // await context.store.save(entity) - // const meta = entity.metadata ?? '' - // await createEvent(entity, OPERATION, event, meta, context.store) + success(OPERATION, `${id} by ${event.caller}}`) + await context.store.save(entity) + const meta = entity.metadata ?? '' + await createEvent(entity, OPERATION, event, meta, context.store) } diff --git a/src/mappings/uniques/buy.ts b/src/mappings/uniques/buy.ts index 3d22853..05f0ff7 100644 --- a/src/mappings/uniques/buy.ts +++ b/src/mappings/uniques/buy.ts @@ -11,38 +11,38 @@ import { getBuyTokenEvent } from './getters' const OPERATION = Action.BUY export async function handleTokenBuy(context: Context): Promise { - // pending(OPERATION, `${context.block.height}`) - // const event = unwrap(context, getBuyTokenEvent) - // debug(OPERATION, event, true) + pending(OPERATION, `${context.block.height}`) + const event = unwrap(context, getBuyTokenEvent) + debug(OPERATION, event, true) - // const id = createTokenId(event.collectionId, event.sn) - // const entity = await getWith(context.store, NE, id, { collection: true }) + const id = createTokenId(event.collectionId, event.sn) + const entity = await getWith(context.store, NE, id, { collection: true }) - // const originalPrice = entity.price - // const originalOwner = entity.currentOwner ?? undefined + const originalPrice = entity.price + const originalOwner = entity.currentOwner ?? undefined - // entity.price = BigInt(0) - // entity.currentOwner = event.caller - // entity.updatedAt = event.timestamp - // if (originalPrice) { - // entity.collection.volume += originalPrice - // if (originalPrice > entity.collection.highestSale) { - // entity.collection.highestSale = originalPrice - // } - // } - // const { ownerCount, distribution } = await calculateCollectionOwnerCountAndDistribution( - // context.store, - // entity.collection.id, - // entity.currentOwner, - // originalOwner - // ) - // entity.collection.ownerCount = ownerCount - // entity.collection.distribution = distribution + entity.price = BigInt(0) + entity.currentOwner = event.caller + entity.updatedAt = event.timestamp + if (originalPrice) { + entity.collection.volume += originalPrice + if (originalPrice > entity.collection.highestSale) { + entity.collection.highestSale = originalPrice + } + } + const { ownerCount, distribution } = await calculateCollectionOwnerCountAndDistribution( + context.store, + entity.collection.id, + entity.currentOwner, + originalOwner + ) + entity.collection.ownerCount = ownerCount + entity.collection.distribution = distribution - // await buyHandler(context, entity) + await buyHandler(context, entity) - // success(OPERATION, `${id} by ${event.caller} for ${String(event.price)}`) - // await context.store.save(entity) - // const meta = String(event.price || '') - // await createEvent(entity, OPERATION, event, meta, context.store, event.currentOwner) + success(OPERATION, `${id} by ${event.caller} for ${String(event.price)}`) + await context.store.save(entity) + const meta = String(event.price || '') + await createEvent(entity, OPERATION, event, meta, context.store, event.currentOwner) } diff --git a/src/mappings/uniques/destroy.ts b/src/mappings/uniques/destroy.ts index 4642612..a69833f 100644 --- a/src/mappings/uniques/destroy.ts +++ b/src/mappings/uniques/destroy.ts @@ -8,13 +8,13 @@ import { getDestroyCollectionEvent } from './getters' const OPERATION = Action.DESTROY export async function handleCollectionDestroy(context: Context): Promise { - // pending(OPERATION, `${context.block.height}`) - // const event = unwrap(context, getDestroyCollectionEvent) - // debug(OPERATION, event) + pending(OPERATION, `${context.block.height}`) + const event = unwrap(context, getDestroyCollectionEvent) + debug(OPERATION, event) - // const entity = await get(context.store, CE, event.id) - // entity.burned = true + const entity = await get(context.store, CE, event.id) + entity.burned = true - // success(OPERATION, `${event.id} by ${event.caller}}`) - // await context.store.save(entity) + success(OPERATION, `${event.id} by ${event.caller}}`) + await context.store.save(entity) } diff --git a/src/mappings/uniques/list.ts b/src/mappings/uniques/list.ts index 7488181..8992079 100644 --- a/src/mappings/uniques/list.ts +++ b/src/mappings/uniques/list.ts @@ -11,23 +11,23 @@ const OPERATION = Action.LIST const UNLIST = Action.UNLIST export async function handleTokenList(context: Context): Promise { - // pending(OPERATION, `${context.block.height}`) - // const event = unwrap(context, getPriceTokenEvent) - // debug(OPERATION, event, true) + pending(OPERATION, `${context.block.height}`) + const event = unwrap(context, getPriceTokenEvent) + debug(OPERATION, event, true) - // const id = createTokenId(event.collectionId, event.sn) - // const entity = await getWith(context.store, NE, id, { collection: true }) + const id = createTokenId(event.collectionId, event.sn) + const entity = await getWith(context.store, NE, id, { collection: true }) - // entity.price = event.price + entity.price = event.price - // if (entity.price && (entity.collection.floor === 0n || entity.price < entity.collection.floor)) { - // entity.collection.floor = entity.price - // } - // await listHandler(context, entity) + if (entity.price && (entity.collection.floor === 0n || entity.price < entity.collection.floor)) { + entity.collection.floor = entity.price + } + await listHandler(context, entity) - // success(OPERATION, `${id} by ${event.caller}} for ${String(event.price)}`) - // await context.store.save(entity) - // const meta = String(event.price || '') - // const interaction = event.price ? OPERATION : UNLIST - // await createEvent(entity, interaction, event, meta, context.store) + success(OPERATION, `${id} by ${event.caller}} for ${String(event.price)}`) + await context.store.save(entity) + const meta = String(event.price || '') + const interaction = event.price ? OPERATION : UNLIST + await createEvent(entity, interaction, event, meta, context.store) } diff --git a/src/mappings/uniques/transfer.ts b/src/mappings/uniques/transfer.ts index ce397af..9e16e5c 100644 --- a/src/mappings/uniques/transfer.ts +++ b/src/mappings/uniques/transfer.ts @@ -10,28 +10,28 @@ import { getTransferTokenEvent } from './getters' const OPERATION = Action.SEND export async function handleTokenTransfer(context: Context): Promise { - // pending(OPERATION, `${context.block.height}`) - // const event = unwrap(context, getTransferTokenEvent) - // debug(OPERATION, event) + pending(OPERATION, `${context.block.height}`) + const event = unwrap(context, getTransferTokenEvent) + debug(OPERATION, event) - // const id = createTokenId(event.collectionId, event.sn) - // const entity = await getWith(context.store, NE, id, { collection: true }) + const id = createTokenId(event.collectionId, event.sn) + const entity = await getWith(context.store, NE, id, { collection: true }) - // const oldOwner = entity.currentOwner - // entity.price = BigInt(0) - // entity.currentOwner = event.to - // entity.updatedAt = event.timestamp + const oldOwner = entity.currentOwner + entity.price = BigInt(0) + entity.currentOwner = event.to + entity.updatedAt = event.timestamp - // const { ownerCount, distribution } = await calculateCollectionOwnerCountAndDistribution( - // context.store, - // entity.collection.id, - // entity.currentOwner, - // oldOwner - // ) - // entity.collection.ownerCount = ownerCount - // entity.collection.distribution = distribution + const { ownerCount, distribution } = await calculateCollectionOwnerCountAndDistribution( + context.store, + entity.collection.id, + entity.currentOwner, + oldOwner + ) + entity.collection.ownerCount = ownerCount + entity.collection.distribution = distribution - // success(OPERATION, `${id} from ${event.caller} to ${event.to}`) - // await context.store.save(entity) - // await createEvent(entity, OPERATION, event, event.to, context.store, oldOwner) + success(OPERATION, `${id} from ${event.caller} to ${event.to}`) + await context.store.save(entity) + await createEvent(entity, OPERATION, event, event.to, context.store, oldOwner) } From 3a97b0ddc4e35272beb7e76b79547a02c297d0c2 Mon Sep 17 00:00:00 2001 From: daiagi Date: Sun, 8 Oct 2023 17:01:34 +0700 Subject: [PATCH 17/30] undo migration change --- db/migrations/1689623692588-Data.js | 44 ++++++++++++++--------------- 1 file changed, 22 insertions(+), 22 deletions(-) diff --git a/db/migrations/1689623692588-Data.js b/db/migrations/1689623692588-Data.js index 94becdb..6f8d7e4 100644 --- a/db/migrations/1689623692588-Data.js +++ b/db/migrations/1689623692588-Data.js @@ -1,25 +1,25 @@ module.exports = class Data1689623692588 { - name = 'Data1689623692588' + name = 'Data1689623692588' - async up(db) { - await db.query(`CREATE TABLE "token_entity" ("id" character varying NOT NULL, "block_number" numeric, "hash" text NOT NULL, "image" text, "media" text, "name" text, "updated_at" TIMESTAMP WITH TIME ZONE NOT NULL, "created_at" TIMESTAMP WITH TIME ZONE NOT NULL, "count" integer NOT NULL, "collection_id" character varying, CONSTRAINT "PK_687443f2a51af49b5472e2c5ddc" PRIMARY KEY ("id"))`) - await db.query(`CREATE INDEX "IDX_0eb2ed7929c3e81941fa1b51b3" ON "token_entity" ("collection_id") `) - await db.query(`CREATE INDEX "IDX_40d6049fd30532dada71922792" ON "token_entity" ("hash") `) - await db.query(`CREATE INDEX "IDX_47b385945a425667b9e690bc02" ON "token_entity" ("name") `) - await db.query(`ALTER TABLE "nft_entity" ADD "token_id" character varying`) - await db.query(`CREATE INDEX "IDX_060d0f515d293fac1d81ee61a7" ON "nft_entity" ("token_id") `) - await db.query(`ALTER TABLE "token_entity" ADD CONSTRAINT "FK_0eb2ed7929c3e81941fa1b51b35" FOREIGN KEY ("collection_id") REFERENCES "collection_entity"("id") ON DELETE NO ACTION ON UPDATE NO ACTION`) - await db.query(`ALTER TABLE "nft_entity" ADD CONSTRAINT "FK_060d0f515d293fac1d81ee61a79" FOREIGN KEY ("token_id") REFERENCES "token_entity"("id") ON DELETE NO ACTION ON UPDATE NO ACTION`) - } + async up(db) { + await db.query(`CREATE TABLE "token_entity" ("id" character varying NOT NULL, "block_number" numeric, "hash" text NOT NULL, "image" text, "media" text, "name" text, "updated_at" TIMESTAMP WITH TIME ZONE NOT NULL, "created_at" TIMESTAMP WITH TIME ZONE NOT NULL, "count" integer NOT NULL, "collection_id" character varying, CONSTRAINT "PK_687443f2a51af49b5472e2c5ddc" PRIMARY KEY ("id"))`) + await db.query(`CREATE INDEX "IDX_0eb2ed7929c3e81941fa1b51b3" ON "token_entity" ("collection_id") `) + await db.query(`CREATE INDEX "IDX_40d6049fd30532dada71922792" ON "token_entity" ("hash") `) + await db.query(`CREATE INDEX "IDX_47b385945a425667b9e690bc02" ON "token_entity" ("name") `) + await db.query(`ALTER TABLE "nft_entity" ADD "token_id" character varying`) + await db.query(`CREATE INDEX "IDX_060d0f515d293fac1d81ee61a7" ON "nft_entity" ("token_id") `) + await db.query(`ALTER TABLE "token_entity" ADD CONSTRAINT "FK_0eb2ed7929c3e81941fa1b51b35" FOREIGN KEY ("collection_id") REFERENCES "collection_entity"("id") ON DELETE NO ACTION ON UPDATE NO ACTION`) + await db.query(`ALTER TABLE "nft_entity" ADD CONSTRAINT "FK_060d0f515d293fac1d81ee61a79" FOREIGN KEY ("token_id") REFERENCES "token_entity"("id") ON DELETE NO ACTION ON UPDATE NO ACTION`) + } - async down(db) { - await db.query(`DROP TABLE "token_entity"`) - await db.query(`DROP INDEX "public"."IDX_0eb2ed7929c3e81941fa1b51b3"`) - await db.query(`DROP INDEX "public"."IDX_40d6049fd30532dada71922792"`) - await db.query(`DROP INDEX "public"."IDX_47b385945a425667b9e690bc02"`) - await db.query(`ALTER TABLE "nft_entity" DROP COLUMN "token_id"`) - await db.query(`DROP INDEX "public"."IDX_060d0f515d293fac1d81ee61a7"`) - await db.query(`ALTER TABLE "token_entity" DROP CONSTRAINT "FK_0eb2ed7929c3e81941fa1b51b35"`) - await db.query(`ALTER TABLE "nft_entity" DROP CONSTRAINT "FK_060d0f515d293fac1d81ee61a79"`) - } -} \ No newline at end of file + async down(db) { + await db.query(`DROP TABLE "token_entity"`) + await db.query(`DROP INDEX "public"."IDX_0eb2ed7929c3e81941fa1b51b3"`) + await db.query(`DROP INDEX "public"."IDX_40d6049fd30532dada71922792"`) + await db.query(`DROP INDEX "public"."IDX_47b385945a425667b9e690bc02"`) + await db.query(`ALTER TABLE "nft_entity" DROP COLUMN "token_id"`) + await db.query(`DROP INDEX "public"."IDX_060d0f515d293fac1d81ee61a7"`) + await db.query(`ALTER TABLE "token_entity" DROP CONSTRAINT "FK_0eb2ed7929c3e81941fa1b51b35"`) + await db.query(`ALTER TABLE "nft_entity" DROP CONSTRAINT "FK_060d0f515d293fac1d81ee61a79"`) + } +} From cbeda9030ae8729304f95d55285ab9940fdccaef Mon Sep 17 00:00:00 2001 From: daiagi Date: Sun, 8 Oct 2023 22:15:31 +0700 Subject: [PATCH 18/30] supply on resolver --- src/server-extension/model/tokenEntity.model.ts | 6 ++++++ src/server-extension/query/tokenEntityByOwner.ts | 14 +++++++++++--- 2 files changed, 17 insertions(+), 3 deletions(-) diff --git a/src/server-extension/model/tokenEntity.model.ts b/src/server-extension/model/tokenEntity.model.ts index b8d4171..92b3247 100644 --- a/src/server-extension/model/tokenEntity.model.ts +++ b/src/server-extension/model/tokenEntity.model.ts @@ -85,6 +85,9 @@ export class TokenEntityByOwner { @Field(() => Number, { nullable: false }) count!: number; + @Field(() => Number, { nullable: false }) + supply!: number; + @Field(() => Cheapest) cheapest!: Cheapest; @@ -123,6 +126,9 @@ export class TokenEntityByOwnerQueryResult { @Field(() => Number, { nullable: false }) count!: number; + @Field(() => Number, { nullable: false }) + supply!: number; + @Field(() => String, { nullable: true }) cheapest_id!: string; diff --git a/src/server-extension/query/tokenEntityByOwner.ts b/src/server-extension/query/tokenEntityByOwner.ts index b49cf58..0a19ce4 100644 --- a/src/server-extension/query/tokenEntityByOwner.ts +++ b/src/server-extension/query/tokenEntityByOwner.ts @@ -10,9 +10,16 @@ export const tokenEntityByOwner = `WITH cheapest_nft AS ( ne.current_owner = $1 ), nft_count AS ( - SELECT token_id, COUNT(*) as count - FROM nft_entity - GROUP BY token_id + SELECT + token_id, + COUNT(*) as count, + COUNT(CASE WHEN burned = false THEN 1 END) as supply + FROM + nft_entity + WHERE + current_owner = $1 + GROUP BY + token_id ) SELECT @@ -29,6 +36,7 @@ SELECT t.created_at AS created_at, t.updated_at AS updated_at, nft_count.count as count, + nft_count.supply as supply, c.nft_id as cheapest_id, c.cheapest as cheapest_price, col.id AS collection_id, From 8402c5eb2b8e33de83952846dd64daaa0c26e7d1 Mon Sep 17 00:00:00 2001 From: daiagi Date: Mon, 9 Oct 2023 18:42:15 +0700 Subject: [PATCH 19/30] use more utils --- src/mappings/shared/token/tokenAPI.ts | 12 +++++------- src/mappings/shared/token/utils.ts | 3 +++ 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/src/mappings/shared/token/tokenAPI.ts b/src/mappings/shared/token/tokenAPI.ts index c7073b4..0c3b45e 100644 --- a/src/mappings/shared/token/tokenAPI.ts +++ b/src/mappings/shared/token/tokenAPI.ts @@ -2,26 +2,24 @@ import { create as createEntity } from '@kodadot1/metasquid/entity' import md5 from 'md5' import { Store } from '../../utils/types' import { CollectionEntity as CE, NFTEntity as NE, TokenEntity as TE } from '../../../model' -import { debug, warn } from '../../utils/logger' -import { OPERATION, generateTokenId } from './utils' +import { debug } from '../../utils/logger' +import { OPERATION, generateTokenId, mediaOf, tokenName } from './utils' export class TokenAPI { constructor(private store: Store) {} async create(collection: CE, nft: NE): Promise { - const nftMedia = nft.image ?? nft.media - if (!nftMedia || nftMedia === '') { - warn(OPERATION, `MISSING NFT MEDIA ${nft.id}`) + const nftMedia = mediaOf(nft) + if (!nftMedia) { return } const tokenId = generateTokenId(collection.id, nftMedia) debug(OPERATION, { createToken: `Create TOKEN ${tokenId} for NFT ${nft.id}` }) - const tokenName = typeof nft.name === 'string' ? nft.name?.replace(/([#_]\d+$)/g, '').trim() : '' const token = createEntity(TE, tokenId, { createdAt: nft.createdAt, collection, - name: tokenName, + name: tokenName(nft.name), count: 1, supply: 1, hash: md5(tokenId), diff --git a/src/mappings/shared/token/utils.ts b/src/mappings/shared/token/utils.ts index e300042..89c0bc3 100644 --- a/src/mappings/shared/token/utils.ts +++ b/src/mappings/shared/token/utils.ts @@ -18,3 +18,6 @@ export const mediaOf = (nft: NE): string | undefined => { return nftMedia } + +export const tokenName = (nftName: string | undefined | null): string => + typeof nftName === 'string' ? nftName?.replace(/([#_]\d+$)/g, '').trim() : '' From 9eae7d8fc05fef8f5c254e3a0b2294ff7375329b Mon Sep 17 00:00:00 2001 From: daiagi Date: Tue, 10 Oct 2023 10:57:57 +0700 Subject: [PATCH 20/30] fix orderBy, add denyList, owner can be null, --- .../model/tokenEntity.model.ts | 8 +- ...tokenEntityByOwner.ts => tokenEntities.ts} | 19 ++--- src/server-extension/resolvers/index.ts | 2 +- .../resolvers/tokenEntities.ts | 79 ++++++++++++++++++ .../resolvers/tokenEntityByOwner.ts | 82 ------------------- 5 files changed, 93 insertions(+), 97 deletions(-) rename src/server-extension/query/{tokenEntityByOwner.ts => tokenEntities.ts} (71%) create mode 100644 src/server-extension/resolvers/tokenEntities.ts delete mode 100644 src/server-extension/resolvers/tokenEntityByOwner.ts diff --git a/src/server-extension/model/tokenEntity.model.ts b/src/server-extension/model/tokenEntity.model.ts index 92b3247..0c2357d 100644 --- a/src/server-extension/model/tokenEntity.model.ts +++ b/src/server-extension/model/tokenEntity.model.ts @@ -54,7 +54,7 @@ class Collection { } @ObjectType() -export class TokenEntityByOwner { +export class TokenEntityModel { @Field(() => String, { nullable: false }) id!: string; @@ -95,13 +95,13 @@ export class TokenEntityByOwner { collection!: Collection; - constructor(props: Partial) { + constructor(props: Partial) { Object.assign(this, props); } } @ObjectType() -export class TokenEntityByOwnerQueryResult { +export class TokenEntityQueryResult { @Field(() => String, { nullable: false }) id!: string; @@ -156,7 +156,7 @@ export class TokenEntityByOwnerQueryResult { - constructor(props: Partial) { + constructor(props: Partial) { Object.assign(this, props); } } diff --git a/src/server-extension/query/tokenEntityByOwner.ts b/src/server-extension/query/tokenEntities.ts similarity index 71% rename from src/server-extension/query/tokenEntityByOwner.ts rename to src/server-extension/query/tokenEntities.ts index 0a19ce4..3809c64 100644 --- a/src/server-extension/query/tokenEntityByOwner.ts +++ b/src/server-extension/query/tokenEntities.ts @@ -1,4 +1,4 @@ -export const tokenEntityByOwner = `WITH cheapest_nft AS ( +export const tokenEntities = `WITH cheapest_nft AS ( SELECT ne.token_id, ne.id AS nft_id, @@ -7,7 +7,8 @@ export const tokenEntityByOwner = `WITH cheapest_nft AS ( FROM nft_entity ne WHERE - ne.current_owner = $1 + ($1::text IS NULL OR ne.current_owner = $1) AND + ($7::text[] IS NULL OR ne.issuer NOT IN (SELECT unnest($7))) ), nft_count AS ( SELECT @@ -15,9 +16,10 @@ nft_count AS ( COUNT(*) as count, COUNT(CASE WHEN burned = false THEN 1 END) as supply FROM - nft_entity + nft_entity ne WHERE - current_owner = $1 + ($1::text IS NULL OR ne.current_owner = $1) AND + ($7::text[] IS NULL OR ne.issuer NOT IN (SELECT unnest($7))) GROUP BY token_id ) @@ -48,10 +50,7 @@ FROM JOIN metadata_entity as me ON t.meta_id = me.id JOIN nft_count ON t.id = nft_count.token_id WHERE - ($5::bigint IS NULL OR c.cheapest >= $5::bigint) AND - ($6::bigint IS NULL OR c.cheapest > $6::bigint) AND - ($7::bigint IS NULL OR c.cheapest <= $7::bigint) - -ORDER BY $4 LIMIT $2 OFFSET $3; - + ($4::bigint IS NULL OR c.cheapest >= $4::bigint) AND + ($5::bigint IS NULL OR c.cheapest > $5::bigint) AND + ($6::bigint IS NULL OR c.cheapest <= $6::bigint) ` diff --git a/src/server-extension/resolvers/index.ts b/src/server-extension/resolvers/index.ts index d290e97..26f1d27 100644 --- a/src/server-extension/resolvers/index.ts +++ b/src/server-extension/resolvers/index.ts @@ -3,4 +3,4 @@ export { WalletResolver } from './walletResolver' export { EventResolver } from './event' export { SeriesResolver } from './series' export { SpotlightResolver } from './spotlight' -export { TokenResolver } from './tokenEntityByOwner' +export { TokenResolver } from './tokenEntities' diff --git a/src/server-extension/resolvers/tokenEntities.ts b/src/server-extension/resolvers/tokenEntities.ts new file mode 100644 index 0000000..25be791 --- /dev/null +++ b/src/server-extension/resolvers/tokenEntities.ts @@ -0,0 +1,79 @@ +/* eslint-disable camelcase */ +import { Arg, Query, Resolver } from 'type-graphql' +import { EntityManager } from 'typeorm' +import { OrderBy, TokenEntityModel, TokenEntityQueryResult } from '../model/tokenEntity.model' +import { makeQuery } from '../utils' +import { tokenEntities } from '../query/tokenEntities' + +@Resolver() +export class TokenResolver { + constructor(private tx: () => Promise) {} + + @Query(() => [TokenEntityModel]) + async tokenEntityList( + @Arg('owner', { nullable: true }) owner?: string, + @Arg('limit', { nullable: true }) limit?: number, + @Arg('offset', { nullable: true, defaultValue: 0 }) offset?: number, + @Arg('orderBy', { nullable: true, defaultValue: OrderBy.blockNumber_DESC }) orderBy?: OrderBy, + @Arg('price_gte', { nullable: true }) price_gte?: number, + @Arg('price_gt', { nullable: true }) price_gt?: number, + @Arg('price_lte', { nullable: true }) price_lte?: number, + @Arg('denyList', () => [String], { nullable: true }) denyList?: string[] + ): Promise { + const orderQuery = this.getOrderByQuery(orderBy) + + const fullSQLQuery = ` +${tokenEntities} +ORDER BY ${orderQuery} LIMIT $2 OFFSET $3; +` + + const result: TokenEntityQueryResult[] = await makeQuery(this.tx, TokenEntityModel, fullSQLQuery, [ + owner, + limit, + offset, + price_gte, + price_gt, + price_lte, + denyList, + ]) + return result.map(this.mapRowToTokenEntityByOwner) + } + + private mapRowToTokenEntityByOwner(row: TokenEntityQueryResult): TokenEntityModel { + return { + ...row, + blockNumber: row.block_number, + createdAt: row.created_at, + updatedAt: row.updated_at, + cheapest: { + id: row.cheapest_id, + price: row.cheapest_price, + }, + collection: { + id: row.collection_id, + name: row.collection_name, + }, + meta: { + id: row.meta_id, + description: row.meta_description, + animationUrl: row.meta_animation_url, + image: row.meta_image, + }, + } + } + + ORDER_BY_MAPPING: Record = { + [OrderBy.blockNumber_ASC]: 'block_number ASC', + [OrderBy.blockNumber_DESC]: 'block_number DESC', + [OrderBy.createdAt_ASC]: 'created_at ASC', + [OrderBy.createdAt_DESC]: 'created_at DESC', + [OrderBy.updatedAt_ASC]: 'updated_at ASC', + [OrderBy.updatedAt_DESC]: 'updated_at DESC', + [OrderBy.price_ASC]: 'cheapest_price ASC', + [OrderBy.price_DESC]: 'cheapest_price DESC', + } + + private getOrderByQuery(orderBy: OrderBy = OrderBy.blockNumber_DESC): string { + return this.ORDER_BY_MAPPING[orderBy] || 'block_number DESC' // default to "block_number DESC" if not found + } +} diff --git a/src/server-extension/resolvers/tokenEntityByOwner.ts b/src/server-extension/resolvers/tokenEntityByOwner.ts deleted file mode 100644 index 9ac1d22..0000000 --- a/src/server-extension/resolvers/tokenEntityByOwner.ts +++ /dev/null @@ -1,82 +0,0 @@ -/* eslint-disable camelcase */ -import { Arg, Query, Resolver } from 'type-graphql' -import { EntityManager } from 'typeorm' -import { OrderBy, TokenEntityByOwner, TokenEntityByOwnerQueryResult } from '../model/tokenEntity.model' -import { makeQuery } from '../utils' -import { tokenEntityByOwner } from '../query/tokenEntityByOwner' -import { TokenEntity } from '../../model/generated' - -@Resolver() -export class TokenResolver { - constructor(private tx: () => Promise) {} - - @Query(() => [TokenEntityByOwner]) - async tokenEntitiesByOwner( - @Arg('owner', { nullable: false }) owner: string, - @Arg('limit', { nullable: true }) limit?: number, - @Arg('offset', { nullable: true, defaultValue: 0 }) offset?: number, - @Arg('orderBy', { nullable: true, defaultValue: OrderBy.blockNumber_DESC }) orderBy?: OrderBy, - @Arg('price_gte', { nullable: true }) price_gte?: number, - @Arg('price_gt', { nullable: true }) price_gt?: number, - @Arg('price_lte', { nullable: true }) price_lte?: number - ): Promise { - const orderQuery = this.getOrderByQuery(orderBy) - - const result: TokenEntityByOwnerQueryResult[] = await makeQuery(this.tx, TokenEntity, tokenEntityByOwner, [ - owner, - limit, - offset, - orderQuery, - price_gte, - price_gt, - price_lte, - ]) - return result.map(this.mapRowToTokenEntityByOwner) - } - - private mapRowToTokenEntityByOwner(row: TokenEntityByOwnerQueryResult): TokenEntityByOwner { - return { - ...row, - blockNumber: row.block_number, - createdAt: row.created_at, - updatedAt: row.updated_at, - cheapest: { - id: row.cheapest_id, - price: row.cheapest_price, - }, - collection: { - id: row.collection_id, - name: row.collection_name, - }, - meta: { - id: row.meta_id, - description: row.meta_description, - animationUrl: row.meta_animation_url, - image: row.meta_image, - }, - } - } - - private getOrderByQuery(orderBy?: OrderBy): string { - switch (orderBy) { - case OrderBy.blockNumber_ASC: - return 't.block_number ASC' - case OrderBy.blockNumber_DESC: - return 't.block_number DESC' - case OrderBy.createdAt_ASC: - return 't.created_at ASC' - case OrderBy.createdAt_DESC: - return 't.created_at DESC' - case OrderBy.updatedAt_ASC: - return 't.updated_at ASC' - case OrderBy.updatedAt_DESC: - return 't.updated_at DESC' - case OrderBy.price_ASC: - return 'c."cheapestNFTPrice" ASC' - case OrderBy.price_DESC: - return 'c."cheapestNFTPrice" DESC' - default: - return 't.block_number DESC' - } - } -} From eb9a4dd074d2e270b229e40a03dba1f448ff6fa3 Mon Sep 17 00:00:00 2001 From: daiagi Date: Tue, 10 Oct 2023 14:59:37 +0700 Subject: [PATCH 21/30] add collection floor price, fix typings --- .../model/tokenEntity.model.ts | 6 ++++++ .../resolvers/tokenEntities.ts | 19 ++++++++++--------- 2 files changed, 16 insertions(+), 9 deletions(-) diff --git a/src/server-extension/model/tokenEntity.model.ts b/src/server-extension/model/tokenEntity.model.ts index 0c2357d..12d3843 100644 --- a/src/server-extension/model/tokenEntity.model.ts +++ b/src/server-extension/model/tokenEntity.model.ts @@ -35,6 +35,9 @@ class Collection { @Field(() => String, { nullable: true }) name!: string; + + @Field(() => String, { nullable: true }) + floorPrice!: string; } @@ -141,6 +144,9 @@ export class TokenEntityQueryResult { @Field(() => String, { nullable: false }) collection_name!: string; + @Field(() => String, { nullable: false }) + collection_floor_price!: string; + @Field(() => String, { nullable: false }) meta_id!: string; diff --git a/src/server-extension/resolvers/tokenEntities.ts b/src/server-extension/resolvers/tokenEntities.ts index 25be791..21dda7a 100644 --- a/src/server-extension/resolvers/tokenEntities.ts +++ b/src/server-extension/resolvers/tokenEntities.ts @@ -1,5 +1,5 @@ /* eslint-disable camelcase */ -import { Arg, Query, Resolver } from 'type-graphql' +import { Arg, Int, Query, Resolver } from 'type-graphql' import { EntityManager } from 'typeorm' import { OrderBy, TokenEntityModel, TokenEntityQueryResult } from '../model/tokenEntity.model' import { makeQuery } from '../utils' @@ -11,22 +11,22 @@ export class TokenResolver { @Query(() => [TokenEntityModel]) async tokenEntityList( + @Arg('limit', () => Int, { nullable: false, defaultValue: 40 }) limit: number, + @Arg('owner', { nullable: true }) owner?: string, - @Arg('limit', { nullable: true }) limit?: number, - @Arg('offset', { nullable: true, defaultValue: 0 }) offset?: number, - @Arg('orderBy', { nullable: true, defaultValue: OrderBy.blockNumber_DESC }) orderBy?: OrderBy, + @Arg('offset', () => Int, { nullable: true, defaultValue: 0 }) offset?: number, + @Arg('orderBy', () => [String], { nullable: true, defaultValue: [OrderBy.blockNumber_DESC] }) orderBy?: string[], @Arg('price_gte', { nullable: true }) price_gte?: number, @Arg('price_gt', { nullable: true }) price_gt?: number, @Arg('price_lte', { nullable: true }) price_lte?: number, @Arg('denyList', () => [String], { nullable: true }) denyList?: string[] - ): Promise { + ): Promise { const orderQuery = this.getOrderByQuery(orderBy) const fullSQLQuery = ` ${tokenEntities} ORDER BY ${orderQuery} LIMIT $2 OFFSET $3; ` - const result: TokenEntityQueryResult[] = await makeQuery(this.tx, TokenEntityModel, fullSQLQuery, [ owner, limit, @@ -52,6 +52,7 @@ ORDER BY ${orderQuery} LIMIT $2 OFFSET $3; collection: { id: row.collection_id, name: row.collection_name, + floorPrice: row.collection_floor_price, }, meta: { id: row.meta_id, @@ -62,7 +63,7 @@ ORDER BY ${orderQuery} LIMIT $2 OFFSET $3; } } - ORDER_BY_MAPPING: Record = { + ORDER_BY_MAPPING: Record = { [OrderBy.blockNumber_ASC]: 'block_number ASC', [OrderBy.blockNumber_DESC]: 'block_number DESC', [OrderBy.createdAt_ASC]: 'created_at ASC', @@ -73,7 +74,7 @@ ORDER BY ${orderQuery} LIMIT $2 OFFSET $3; [OrderBy.price_DESC]: 'cheapest_price DESC', } - private getOrderByQuery(orderBy: OrderBy = OrderBy.blockNumber_DESC): string { - return this.ORDER_BY_MAPPING[orderBy] || 'block_number DESC' // default to "block_number DESC" if not found + private getOrderByQuery(orderBy: string[] = [OrderBy.blockNumber_DESC]): string { + return orderBy.map((order) => this.ORDER_BY_MAPPING[order]).join(', ') } } From b5963dc43ce648a558d478a805fbcba223213362 Mon Sep 17 00:00:00 2001 From: daiagi Date: Tue, 10 Oct 2023 15:00:03 +0700 Subject: [PATCH 22/30] cheapest is already on tokenEntity --- src/server-extension/query/tokenEntities.ts | 40 +++++++++++---------- 1 file changed, 21 insertions(+), 19 deletions(-) diff --git a/src/server-extension/query/tokenEntities.ts b/src/server-extension/query/tokenEntities.ts index 3809c64..3b9e2ac 100644 --- a/src/server-extension/query/tokenEntities.ts +++ b/src/server-extension/query/tokenEntities.ts @@ -1,15 +1,4 @@ -export const tokenEntities = `WITH cheapest_nft AS ( - SELECT - ne.token_id, - ne.id AS nft_id, - ne.price AS cheapest, - ROW_NUMBER() OVER(PARTITION BY ne.token_id ORDER BY ne.price ASC) AS rnk - FROM - nft_entity ne - WHERE - ($1::text IS NULL OR ne.current_owner = $1) AND - ($7::text[] IS NULL OR ne.issuer NOT IN (SELECT unnest($7))) -), +export const tokenEntities = `WITH nft_count AS ( SELECT token_id, @@ -22,6 +11,17 @@ nft_count AS ( ($7::text[] IS NULL OR ne.issuer NOT IN (SELECT unnest($7))) GROUP BY token_id +), + collection_floor_price AS ( + SELECT + collection_id, + MIN(price) as floor_price + FROM + nft_entity + WHERE + burned = false + GROUP BY + collection_id ) SELECT @@ -37,20 +37,22 @@ SELECT t.block_number as block_number, t.created_at AS created_at, t.updated_at AS updated_at, + c.id as cheapest_id, + c.price as cheapest_price, nft_count.count as count, nft_count.supply as supply, - c.nft_id as cheapest_id, - c.cheapest as cheapest_price, col.id AS collection_id, - col.name AS collection_name + col.name AS collection_name, + cfp.floor_price AS collection_floor_price FROM token_entity as t - JOIN cheapest_nft as c ON t.id = c.token_id AND c.rnk = 1 JOIN collection_entity as col ON t.collection_id = col.id JOIN metadata_entity as me ON t.meta_id = me.id JOIN nft_count ON t.id = nft_count.token_id + JOIN collection_floor_price as cfp ON t.collection_id = cfp.collection_id + LEFT JOIN nft_entity as c ON t.cheapest_id = c.id WHERE - ($4::bigint IS NULL OR c.cheapest >= $4::bigint) AND - ($5::bigint IS NULL OR c.cheapest > $5::bigint) AND - ($6::bigint IS NULL OR c.cheapest <= $6::bigint) + ($4::bigint IS NULL OR c.price >= $4::bigint) AND + ($5::bigint IS NULL OR c.price > $5::bigint) AND + ($6::bigint IS NULL OR c.price <= $6::bigint) ` From c684a662c1f69b66d5878fe963ba28dfb8de786a Mon Sep 17 00:00:00 2001 From: daiagi Date: Tue, 10 Oct 2023 18:16:35 +0700 Subject: [PATCH 23/30] total count resolver --- src/server-extension/model/count.model.ts | 12 ++++++++ .../query/totalTokenEntities.ts | 10 +++++++ src/server-extension/resolvers/index.ts | 1 + .../resolvers/totalTokenEntities.ts | 29 +++++++++++++++++++ 4 files changed, 52 insertions(+) create mode 100644 src/server-extension/query/totalTokenEntities.ts create mode 100644 src/server-extension/resolvers/totalTokenEntities.ts diff --git a/src/server-extension/model/count.model.ts b/src/server-extension/model/count.model.ts index 79214a1..135b723 100644 --- a/src/server-extension/model/count.model.ts +++ b/src/server-extension/model/count.model.ts @@ -9,3 +9,15 @@ export class CountEntity { this.total = totalCount; } } + + +@ObjectType() +export class CountEntityQueryResult { + @Field(() => Number, { nullable: false }) + total_count!: number; + + + constructor(props: Partial) { + Object.assign(this, props); + } +} diff --git a/src/server-extension/query/totalTokenEntities.ts b/src/server-extension/query/totalTokenEntities.ts new file mode 100644 index 0000000..72ce67d --- /dev/null +++ b/src/server-extension/query/totalTokenEntities.ts @@ -0,0 +1,10 @@ +export const totalTokenEntities = ` +SELECT COUNT(id) as total_count +FROM nft_entity +WHERE + ($1::text IS NULL OR current_owner = $1) AND + ($5::text[] IS NULL OR issuer NOT IN (SELECT unnest($5))) AND + ($2::bigint IS NULL OR price >= $2::bigint) AND + ($3::bigint IS NULL OR price > $3::bigint) AND + ($4::bigint IS NULL OR price <= $4::bigint); +` \ No newline at end of file diff --git a/src/server-extension/resolvers/index.ts b/src/server-extension/resolvers/index.ts index 26f1d27..47e36c9 100644 --- a/src/server-extension/resolvers/index.ts +++ b/src/server-extension/resolvers/index.ts @@ -4,3 +4,4 @@ export { EventResolver } from './event' export { SeriesResolver } from './series' export { SpotlightResolver } from './spotlight' export { TokenResolver } from './tokenEntities' +export { TokenCountResolver } from './totalTokenEntities' diff --git a/src/server-extension/resolvers/totalTokenEntities.ts b/src/server-extension/resolvers/totalTokenEntities.ts new file mode 100644 index 0000000..ec8b8ac --- /dev/null +++ b/src/server-extension/resolvers/totalTokenEntities.ts @@ -0,0 +1,29 @@ +/* eslint-disable camelcase */ +import { Arg, Query, Resolver } from 'type-graphql' +import { EntityManager } from 'typeorm' +import { makeQuery } from '../utils' +import { totalTokenEntities } from '../query/totalTokenEntities' +import { CountEntity, CountEntityQueryResult } from '../model/count.model' + +@Resolver() +export class TokenCountResolver { + constructor(private tx: () => Promise) {} + + @Query(() => CountEntity) + async tokenEntityCount( + @Arg('owner', { nullable: true }) owner?: string, + @Arg('price_gte', { nullable: true }) price_gte?: number, + @Arg('price_gt', { nullable: true }) price_gt?: number, + @Arg('price_lte', { nullable: true }) price_lte?: number, + @Arg('denyList', () => [String], { nullable: true }) denyList?: string[] + ): Promise { + const rawData: CountEntityQueryResult[] = await makeQuery(this.tx, CountEntity, totalTokenEntities, [ + owner, + price_gte, + price_gt, + price_lte, + denyList, + ]) + return new CountEntity(rawData[0].total_count) + } +} From 7060884a7755020b7dae721b8950f5404be5325f Mon Sep 17 00:00:00 2001 From: daiagi Date: Tue, 10 Oct 2023 18:16:55 +0700 Subject: [PATCH 24/30] drop collection floor price --- .../model/tokenEntity.model.ts | 5 --- src/server-extension/query/tokenEntities.ts | 45 ++++++++----------- .../resolvers/tokenEntities.ts | 1 - 3 files changed, 19 insertions(+), 32 deletions(-) diff --git a/src/server-extension/model/tokenEntity.model.ts b/src/server-extension/model/tokenEntity.model.ts index 12d3843..e94c80f 100644 --- a/src/server-extension/model/tokenEntity.model.ts +++ b/src/server-extension/model/tokenEntity.model.ts @@ -35,9 +35,6 @@ class Collection { @Field(() => String, { nullable: true }) name!: string; - - @Field(() => String, { nullable: true }) - floorPrice!: string; } @@ -144,8 +141,6 @@ export class TokenEntityQueryResult { @Field(() => String, { nullable: false }) collection_name!: string; - @Field(() => String, { nullable: false }) - collection_floor_price!: string; @Field(() => String, { nullable: false }) meta_id!: string; diff --git a/src/server-extension/query/tokenEntities.ts b/src/server-extension/query/tokenEntities.ts index 3b9e2ac..7146226 100644 --- a/src/server-extension/query/tokenEntities.ts +++ b/src/server-extension/query/tokenEntities.ts @@ -1,27 +1,24 @@ export const tokenEntities = `WITH +filters_applied AS ( + SELECT * + FROM nft_entity c + WHERE + ($1::text IS NULL OR c.current_owner = $1) AND + ($7::text[] IS NULL OR c.issuer NOT IN (SELECT unnest($7))) AND + ($4::bigint IS NULL OR c.price >= $4::bigint) AND + ($5::bigint IS NULL OR c.price > $5::bigint) AND + ($6::bigint IS NULL OR c.price <= $6::bigint) +), nft_count AS ( SELECT token_id, COUNT(*) as count, COUNT(CASE WHEN burned = false THEN 1 END) as supply FROM - nft_entity ne - WHERE - ($1::text IS NULL OR ne.current_owner = $1) AND - ($7::text[] IS NULL OR ne.issuer NOT IN (SELECT unnest($7))) + filters_applied + GROUP BY token_id -), - collection_floor_price AS ( - SELECT - collection_id, - MIN(price) as floor_price - FROM - nft_entity - WHERE - burned = false - GROUP BY - collection_id ) SELECT @@ -37,22 +34,18 @@ SELECT t.block_number as block_number, t.created_at AS created_at, t.updated_at AS updated_at, - c.id as cheapest_id, - c.price as cheapest_price, - nft_count.count as count, - nft_count.supply as supply, + cheapest.id as cheapest_id, + cheapest.price as cheapest_price, + nc.count as count, + nc.supply as supply, col.id AS collection_id, col.name AS collection_name, - cfp.floor_price AS collection_floor_price FROM token_entity as t JOIN collection_entity as col ON t.collection_id = col.id JOIN metadata_entity as me ON t.meta_id = me.id - JOIN nft_count ON t.id = nft_count.token_id - JOIN collection_floor_price as cfp ON t.collection_id = cfp.collection_id - LEFT JOIN nft_entity as c ON t.cheapest_id = c.id + JOIN nft_count as nc ON t.id = nc.token_id + LEFT JOIN filters_applied cheapest ON t.cheapest_id = fa.id WHERE - ($4::bigint IS NULL OR c.price >= $4::bigint) AND - ($5::bigint IS NULL OR c.price > $5::bigint) AND - ($6::bigint IS NULL OR c.price <= $6::bigint) + nc.supply > 0 ` diff --git a/src/server-extension/resolvers/tokenEntities.ts b/src/server-extension/resolvers/tokenEntities.ts index 21dda7a..f17ae27 100644 --- a/src/server-extension/resolvers/tokenEntities.ts +++ b/src/server-extension/resolvers/tokenEntities.ts @@ -52,7 +52,6 @@ ORDER BY ${orderQuery} LIMIT $2 OFFSET $3; collection: { id: row.collection_id, name: row.collection_name, - floorPrice: row.collection_floor_price, }, meta: { id: row.meta_id, From 789ff7bbd8e315ee2a58d3b54dba96798fcef162 Mon Sep 17 00:00:00 2001 From: daiagi Date: Tue, 10 Oct 2023 18:24:47 +0700 Subject: [PATCH 25/30] syntax fix --- src/server-extension/query/tokenEntities.ts | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/server-extension/query/tokenEntities.ts b/src/server-extension/query/tokenEntities.ts index 7146226..7f645b0 100644 --- a/src/server-extension/query/tokenEntities.ts +++ b/src/server-extension/query/tokenEntities.ts @@ -1,13 +1,13 @@ export const tokenEntities = `WITH filters_applied AS ( SELECT * - FROM nft_entity c + FROM nft_entity ne WHERE - ($1::text IS NULL OR c.current_owner = $1) AND - ($7::text[] IS NULL OR c.issuer NOT IN (SELECT unnest($7))) AND - ($4::bigint IS NULL OR c.price >= $4::bigint) AND - ($5::bigint IS NULL OR c.price > $5::bigint) AND - ($6::bigint IS NULL OR c.price <= $6::bigint) + ($1::text IS NULL OR ne.current_owner = $1) AND + ($7::text[] IS NULL OR ne.issuer NOT IN (SELECT unnest($7))) AND + ($4::bigint IS NULL OR ne.price >= $4::bigint) AND + ($5::bigint IS NULL OR ne.price > $5::bigint) AND + ($6::bigint IS NULL OR ne.price <= $6::bigint) ), nft_count AS ( SELECT @@ -39,13 +39,13 @@ SELECT nc.count as count, nc.supply as supply, col.id AS collection_id, - col.name AS collection_name, + col.name AS collection_name FROM token_entity as t JOIN collection_entity as col ON t.collection_id = col.id JOIN metadata_entity as me ON t.meta_id = me.id JOIN nft_count as nc ON t.id = nc.token_id - LEFT JOIN filters_applied cheapest ON t.cheapest_id = fa.id + LEFT JOIN filters_applied cheapest ON t.cheapest_id = cheapest.id WHERE nc.supply > 0 ` From 6671e29d75acbcde3e893ff8cadbd1e4b1acb5eb Mon Sep 17 00:00:00 2001 From: daiagi Date: Wed, 11 Oct 2023 14:40:28 +0700 Subject: [PATCH 26/30] get 'cheapest' in resolver, drop it from schema --- schema.graphql | 1 - src/mappings/nfts/buy.ts | 3 -- src/mappings/nfts/list.ts | 3 -- src/mappings/shared/token/buy.ts | 1 - src/mappings/shared/token/index.ts | 2 - src/mappings/shared/token/list.ts | 30 -------------- src/mappings/uniques/buy.ts | 2 - src/mappings/uniques/list.ts | 2 - src/mappings/uniques/transfer.ts | 40 +++++++++---------- src/model/generated/tokenEntity.model.ts | 4 -- src/server-extension/query/tokenEntities.ts | 24 ++++++++++- .../resolvers/tokenEntities.ts | 2 +- 12 files changed, 44 insertions(+), 70 deletions(-) delete mode 100644 src/mappings/shared/token/buy.ts delete mode 100644 src/mappings/shared/token/list.ts diff --git a/schema.graphql b/schema.graphql index 39d8af4..3ea7674 100644 --- a/schema.graphql +++ b/schema.graphql @@ -30,7 +30,6 @@ type TokenEntity @entity { id: ID! blockNumber: BigInt collection: CollectionEntity - cheapest: NFTEntity nfts: [NFTEntity!] @derivedFrom(field: "token") hash: String! @index image: String diff --git a/src/mappings/nfts/buy.ts b/src/mappings/nfts/buy.ts index 4521d39..396cf8b 100644 --- a/src/mappings/nfts/buy.ts +++ b/src/mappings/nfts/buy.ts @@ -4,7 +4,6 @@ import { createEvent } from '../shared/event' import { unwrap } from '../utils/extract' import { debug, pending, success } from '../utils/logger' import { Action, Context, createTokenId } from '../utils/types' -import { buyHandler } from '../shared/token' import { calculateCollectionOwnerCountAndDistribution } from '../utils/helper' import { getBuyTokenEvent } from './getters' @@ -39,8 +38,6 @@ export async function handleTokenBuy(context: Context): Promise { entity.collection.ownerCount = ownerCount entity.collection.distribution = distribution - await buyHandler(context, entity) - success(OPERATION, `${id} by ${event.caller} for ${String(event.price)}`) await context.store.save(entity) const meta = String(event.price || '') diff --git a/src/mappings/nfts/list.ts b/src/mappings/nfts/list.ts index 8992079..230bdee 100644 --- a/src/mappings/nfts/list.ts +++ b/src/mappings/nfts/list.ts @@ -4,7 +4,6 @@ import { unwrap } from '../utils/extract' import { debug, pending, success } from '../utils/logger' import { Action, Context, createTokenId } from '../utils/types' import { createEvent } from '../shared/event' -import { listHandler } from '../shared/token' import { getPriceTokenEvent } from './getters' const OPERATION = Action.LIST @@ -23,8 +22,6 @@ export async function handleTokenList(context: Context): Promise { if (entity.price && (entity.collection.floor === 0n || entity.price < entity.collection.floor)) { entity.collection.floor = entity.price } - await listHandler(context, entity) - success(OPERATION, `${id} by ${event.caller}} for ${String(event.price)}`) await context.store.save(entity) const meta = String(event.price || '') diff --git a/src/mappings/shared/token/buy.ts b/src/mappings/shared/token/buy.ts deleted file mode 100644 index 7f24955..0000000 --- a/src/mappings/shared/token/buy.ts +++ /dev/null @@ -1 +0,0 @@ -export { listHandler as buyHandler } from './list' diff --git a/src/mappings/shared/token/index.ts b/src/mappings/shared/token/index.ts index b8feba8..0de96c1 100644 --- a/src/mappings/shared/token/index.ts +++ b/src/mappings/shared/token/index.ts @@ -1,5 +1,3 @@ export * from './mint' -export * from './list' export * from './setMetadata' export * from './burn' -export * from './buy' diff --git a/src/mappings/shared/token/list.ts b/src/mappings/shared/token/list.ts deleted file mode 100644 index 963dab4..0000000 --- a/src/mappings/shared/token/list.ts +++ /dev/null @@ -1,30 +0,0 @@ -import { getWith } from '@kodadot1/metasquid/entity' -import { Context } from '../../utils/types' -import { NFTEntity as NE, TokenEntity as TE } from '../../../model' -import { warn } from '../../utils/logger' -import { OPERATION, mediaOf } from './utils' - -export async function listHandler(context: Context, nft: NE): Promise { - const nftMedia = mediaOf(nft) - if (!nftMedia) { - return - } - - try { - const nftWithToken = await getWith(context.store, NE, nft.id, { token: true }) - if (!nftWithToken.token) { - warn(OPERATION, `nft ${nft.id} is not linked to a token`) - return - } - const cheapest = nftWithToken.token.cheapest - if (!nft.price && !cheapest?.price) { - return - } - - if (!cheapest?.price || (nft.price && nft.price < cheapest.price)) { - context.store.update(TE, nftWithToken.token.id, { cheapest: nft, updatedAt: nft.updatedAt }) - } - } catch (error) { - warn(OPERATION, `ERROR ${error}`) - } -} diff --git a/src/mappings/uniques/buy.ts b/src/mappings/uniques/buy.ts index 05f0ff7..2f3752b 100644 --- a/src/mappings/uniques/buy.ts +++ b/src/mappings/uniques/buy.ts @@ -5,7 +5,6 @@ import { unwrap } from '../utils/extract' import { debug, pending, success } from '../utils/logger' import { Action, Context, createTokenId } from '../utils/types' import { calculateCollectionOwnerCountAndDistribution } from '../utils/helper' -import { buyHandler } from '../shared/token' import { getBuyTokenEvent } from './getters' const OPERATION = Action.BUY @@ -39,7 +38,6 @@ export async function handleTokenBuy(context: Context): Promise { entity.collection.ownerCount = ownerCount entity.collection.distribution = distribution - await buyHandler(context, entity) success(OPERATION, `${id} by ${event.caller} for ${String(event.price)}`) await context.store.save(entity) diff --git a/src/mappings/uniques/list.ts b/src/mappings/uniques/list.ts index 8992079..d722e5a 100644 --- a/src/mappings/uniques/list.ts +++ b/src/mappings/uniques/list.ts @@ -4,7 +4,6 @@ import { unwrap } from '../utils/extract' import { debug, pending, success } from '../utils/logger' import { Action, Context, createTokenId } from '../utils/types' import { createEvent } from '../shared/event' -import { listHandler } from '../shared/token' import { getPriceTokenEvent } from './getters' const OPERATION = Action.LIST @@ -23,7 +22,6 @@ export async function handleTokenList(context: Context): Promise { if (entity.price && (entity.collection.floor === 0n || entity.price < entity.collection.floor)) { entity.collection.floor = entity.price } - await listHandler(context, entity) success(OPERATION, `${id} by ${event.caller}} for ${String(event.price)}`) await context.store.save(entity) diff --git a/src/mappings/uniques/transfer.ts b/src/mappings/uniques/transfer.ts index 9e16e5c..ce397af 100644 --- a/src/mappings/uniques/transfer.ts +++ b/src/mappings/uniques/transfer.ts @@ -10,28 +10,28 @@ import { getTransferTokenEvent } from './getters' const OPERATION = Action.SEND export async function handleTokenTransfer(context: Context): Promise { - pending(OPERATION, `${context.block.height}`) - const event = unwrap(context, getTransferTokenEvent) - debug(OPERATION, event) + // pending(OPERATION, `${context.block.height}`) + // const event = unwrap(context, getTransferTokenEvent) + // debug(OPERATION, event) - const id = createTokenId(event.collectionId, event.sn) - const entity = await getWith(context.store, NE, id, { collection: true }) + // const id = createTokenId(event.collectionId, event.sn) + // const entity = await getWith(context.store, NE, id, { collection: true }) - const oldOwner = entity.currentOwner - entity.price = BigInt(0) - entity.currentOwner = event.to - entity.updatedAt = event.timestamp + // const oldOwner = entity.currentOwner + // entity.price = BigInt(0) + // entity.currentOwner = event.to + // entity.updatedAt = event.timestamp - const { ownerCount, distribution } = await calculateCollectionOwnerCountAndDistribution( - context.store, - entity.collection.id, - entity.currentOwner, - oldOwner - ) - entity.collection.ownerCount = ownerCount - entity.collection.distribution = distribution + // const { ownerCount, distribution } = await calculateCollectionOwnerCountAndDistribution( + // context.store, + // entity.collection.id, + // entity.currentOwner, + // oldOwner + // ) + // entity.collection.ownerCount = ownerCount + // entity.collection.distribution = distribution - success(OPERATION, `${id} from ${event.caller} to ${event.to}`) - await context.store.save(entity) - await createEvent(entity, OPERATION, event, event.to, context.store, oldOwner) + // success(OPERATION, `${id} from ${event.caller} to ${event.to}`) + // await context.store.save(entity) + // await createEvent(entity, OPERATION, event, event.to, context.store, oldOwner) } diff --git a/src/model/generated/tokenEntity.model.ts b/src/model/generated/tokenEntity.model.ts index 21a0ea8..46c5d57 100644 --- a/src/model/generated/tokenEntity.model.ts +++ b/src/model/generated/tokenEntity.model.ts @@ -20,10 +20,6 @@ export class TokenEntity { @ManyToOne_(() => CollectionEntity, {nullable: true}) collection!: CollectionEntity | undefined | null - @Index_() - @ManyToOne_(() => NFTEntity, {nullable: true}) - cheapest!: NFTEntity | undefined | null - @OneToMany_(() => NFTEntity, e => e.token) nfts!: NFTEntity[] diff --git a/src/server-extension/query/tokenEntities.ts b/src/server-extension/query/tokenEntities.ts index 7f645b0..289843d 100644 --- a/src/server-extension/query/tokenEntities.ts +++ b/src/server-extension/query/tokenEntities.ts @@ -19,8 +19,30 @@ nft_count AS ( GROUP BY token_id +), +cheapest AS ( + WITH Ranked AS ( + SELECT + token_id, + id, + price, + ROW_NUMBER() OVER(PARTITION BY token_id ORDER BY price ASC, id ASC) AS rnk + FROM + filters_applied + WHERE + burned = false + ) + SELECT + token_id, + id, + price + FROM + Ranked + WHERE + rnk = 1 ) + SELECT t.id as id, t.name as name, @@ -45,7 +67,7 @@ FROM JOIN collection_entity as col ON t.collection_id = col.id JOIN metadata_entity as me ON t.meta_id = me.id JOIN nft_count as nc ON t.id = nc.token_id - LEFT JOIN filters_applied cheapest ON t.cheapest_id = cheapest.id + LEFT JOIN cheapest ON t.id = cheapest.token_id WHERE nc.supply > 0 ` diff --git a/src/server-extension/resolvers/tokenEntities.ts b/src/server-extension/resolvers/tokenEntities.ts index f17ae27..0bb84e1 100644 --- a/src/server-extension/resolvers/tokenEntities.ts +++ b/src/server-extension/resolvers/tokenEntities.ts @@ -11,7 +11,7 @@ export class TokenResolver { @Query(() => [TokenEntityModel]) async tokenEntityList( - @Arg('limit', () => Int, { nullable: false, defaultValue: 40 }) limit: number, + @Arg('limit', () => Int, { nullable: true, defaultValue: 40 }) limit: number, @Arg('owner', { nullable: true }) owner?: string, @Arg('offset', () => Int, { nullable: true, defaultValue: 0 }) offset?: number, From d8585191434d093a53748b728fb700b0f459837b Mon Sep 17 00:00:00 2001 From: daiagi Date: Wed, 11 Oct 2023 17:10:04 +0700 Subject: [PATCH 27/30] add current owner --- src/server-extension/model/tokenEntity.model.ts | 6 ++++++ src/server-extension/query/tokenEntities.ts | 3 +++ src/server-extension/resolvers/tokenEntities.ts | 1 + 3 files changed, 10 insertions(+) diff --git a/src/server-extension/model/tokenEntity.model.ts b/src/server-extension/model/tokenEntity.model.ts index e94c80f..767769c 100644 --- a/src/server-extension/model/tokenEntity.model.ts +++ b/src/server-extension/model/tokenEntity.model.ts @@ -26,6 +26,9 @@ class Cheapest { @Field(() => BigInt, { nullable: true }) price!: bigint; + + @Field(() => String, { nullable: true }) + currentOwner!: string; } @ObjectType() @@ -132,6 +135,9 @@ export class TokenEntityQueryResult { @Field(() => String, { nullable: true }) cheapest_id!: string; + @Field(() => String, { nullable: true }) + cheapest_current_owner!: string; + @Field(() => BigInt, { nullable: true }) cheapest_price!: bigint; diff --git a/src/server-extension/query/tokenEntities.ts b/src/server-extension/query/tokenEntities.ts index 289843d..a0584fd 100644 --- a/src/server-extension/query/tokenEntities.ts +++ b/src/server-extension/query/tokenEntities.ts @@ -26,6 +26,7 @@ cheapest AS ( token_id, id, price, + current_owner, ROW_NUMBER() OVER(PARTITION BY token_id ORDER BY price ASC, id ASC) AS rnk FROM filters_applied @@ -33,6 +34,7 @@ cheapest AS ( burned = false ) SELECT + current_owner, token_id, id, price @@ -58,6 +60,7 @@ SELECT t.updated_at AS updated_at, cheapest.id as cheapest_id, cheapest.price as cheapest_price, + cheapest.current_owner as cheapest_current_owner, nc.count as count, nc.supply as supply, col.id AS collection_id, diff --git a/src/server-extension/resolvers/tokenEntities.ts b/src/server-extension/resolvers/tokenEntities.ts index 0bb84e1..5121f76 100644 --- a/src/server-extension/resolvers/tokenEntities.ts +++ b/src/server-extension/resolvers/tokenEntities.ts @@ -48,6 +48,7 @@ ORDER BY ${orderQuery} LIMIT $2 OFFSET $3; cheapest: { id: row.cheapest_id, price: row.cheapest_price, + currentOwner: row.cheapest_current_owner, }, collection: { id: row.collection_id, From 40a43d452dc1c852f7ffff389a4e993efdc12b17 Mon Sep 17 00:00:00 2001 From: daiagi Date: Wed, 11 Oct 2023 17:44:57 +0700 Subject: [PATCH 28/30] `SELECT DISTINCT` to improve query performance --- src/server-extension/query/tokenEntities.ts | 21 ++++----------------- 1 file changed, 4 insertions(+), 17 deletions(-) diff --git a/src/server-extension/query/tokenEntities.ts b/src/server-extension/query/tokenEntities.ts index a0584fd..a55ab28 100644 --- a/src/server-extension/query/tokenEntities.ts +++ b/src/server-extension/query/tokenEntities.ts @@ -21,27 +21,14 @@ nft_count AS ( token_id ), cheapest AS ( - WITH Ranked AS ( - SELECT - token_id, - id, - price, - current_owner, - ROW_NUMBER() OVER(PARTITION BY token_id ORDER BY price ASC, id ASC) AS rnk - FROM - filters_applied - WHERE - burned = false - ) - SELECT + SELECT DISTINCT ON (token_id) current_owner, token_id, id, price - FROM - Ranked - WHERE - rnk = 1 + FROM filters_applied + WHERE burned = false AND price > 0 + ORDER BY token_id, price ASC, id ASC ) From 23ac70ab77e7d4e4cf31fba43b7fee9832a3f4ef Mon Sep 17 00:00:00 2001 From: daiagi Date: Thu, 12 Oct 2023 17:02:59 +0700 Subject: [PATCH 29/30] cheapest can have price:0 --- src/server-extension/query/tokenEntities.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/server-extension/query/tokenEntities.ts b/src/server-extension/query/tokenEntities.ts index a55ab28..ba965c5 100644 --- a/src/server-extension/query/tokenEntities.ts +++ b/src/server-extension/query/tokenEntities.ts @@ -27,7 +27,7 @@ cheapest AS ( id, price FROM filters_applied - WHERE burned = false AND price > 0 + WHERE burned = false ORDER BY token_id, price ASC, id ASC ) From 76692c842b6aa967f11a7a5eb23289ca0ffe532e Mon Sep 17 00:00:00 2001 From: daiagi Date: Thu, 12 Oct 2023 18:49:57 +0700 Subject: [PATCH 30/30] =?UTF-8?q?=F0=9F=98=B1=20uncomment=20unique=20tranf?= =?UTF-8?q?er?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/mappings/uniques/transfer.ts | 40 ++++++++++++++++---------------- 1 file changed, 20 insertions(+), 20 deletions(-) diff --git a/src/mappings/uniques/transfer.ts b/src/mappings/uniques/transfer.ts index ce397af..9e16e5c 100644 --- a/src/mappings/uniques/transfer.ts +++ b/src/mappings/uniques/transfer.ts @@ -10,28 +10,28 @@ import { getTransferTokenEvent } from './getters' const OPERATION = Action.SEND export async function handleTokenTransfer(context: Context): Promise { - // pending(OPERATION, `${context.block.height}`) - // const event = unwrap(context, getTransferTokenEvent) - // debug(OPERATION, event) + pending(OPERATION, `${context.block.height}`) + const event = unwrap(context, getTransferTokenEvent) + debug(OPERATION, event) - // const id = createTokenId(event.collectionId, event.sn) - // const entity = await getWith(context.store, NE, id, { collection: true }) + const id = createTokenId(event.collectionId, event.sn) + const entity = await getWith(context.store, NE, id, { collection: true }) - // const oldOwner = entity.currentOwner - // entity.price = BigInt(0) - // entity.currentOwner = event.to - // entity.updatedAt = event.timestamp + const oldOwner = entity.currentOwner + entity.price = BigInt(0) + entity.currentOwner = event.to + entity.updatedAt = event.timestamp - // const { ownerCount, distribution } = await calculateCollectionOwnerCountAndDistribution( - // context.store, - // entity.collection.id, - // entity.currentOwner, - // oldOwner - // ) - // entity.collection.ownerCount = ownerCount - // entity.collection.distribution = distribution + const { ownerCount, distribution } = await calculateCollectionOwnerCountAndDistribution( + context.store, + entity.collection.id, + entity.currentOwner, + oldOwner + ) + entity.collection.ownerCount = ownerCount + entity.collection.distribution = distribution - // success(OPERATION, `${id} from ${event.caller} to ${event.to}`) - // await context.store.save(entity) - // await createEvent(entity, OPERATION, event, event.to, context.store, oldOwner) + success(OPERATION, `${id} from ${event.caller} to ${event.to}`) + await context.store.save(entity) + await createEvent(entity, OPERATION, event, event.to, context.store, oldOwner) }