diff --git a/src/DataLoaders/GenTokens.test.ts b/src/DataLoaders/GenTokens.test.ts index b586fc2..b998ce3 100644 --- a/src/DataLoaders/GenTokens.test.ts +++ b/src/DataLoaders/GenTokens.test.ts @@ -4,6 +4,7 @@ import { articleFactory, articleMentionFactory, codexFactory, + collectionOfferFactory, generativeTokenFactory, marketStatsFactory, marketStatsHistoryFactory, @@ -27,6 +28,7 @@ import { createGentkTokSecondarySplitsLoader, createGenTokArticleMentionsLoader, createGenTokCodexLoader, + createGenTokCollectionOffersLoader, createGenTokLoader, createGenTokMarketStatsHistoryLoader, createGenTokMarketStatsLoader, @@ -34,6 +36,7 @@ import { createGenTokObjktFeaturesLoader, createGenTokObjktsCountLoader, createGenTokObjktsLoader, + createGenTokOffersAndCollectionOffersLoader, createGenTokOffersLoader, createGenTokPricingDutchAuctionLoader, createGenTokPricingFixedLoader, @@ -57,6 +60,7 @@ afterAll(() => { const cleanup = async () => { await manager.query("DELETE FROM report") await manager.query("DELETE FROM offer") + await manager.query("DELETE FROM collection_offer") await manager.query("DELETE FROM objkt") await manager.query("DELETE FROM redeemable") await manager.query("DELETE FROM mint_ticket_settings") @@ -65,8 +69,6 @@ const cleanup = async () => { await manager.query("DELETE FROM codex") } -afterEach(cleanup) - const seedTokens = async () => { await generativeTokenFactory(0, GenerativeTokenVersion.PRE_V3) await generativeTokenFactory(1, GenerativeTokenVersion.V3) @@ -77,6 +79,8 @@ describe("GenTokens dataloaders", () => { let dataloader describe("createGenTokLoader", () => { + afterAll(cleanup) + beforeAll(async () => { dataloader = createGenTokLoader() await seedTokens() @@ -95,6 +99,8 @@ describe("GenTokens dataloaders", () => { }) describe("createGenTokObjktsLoader", () => { + afterAll(cleanup) + beforeAll(async () => { dataloader = createGenTokObjktsLoader() @@ -136,6 +142,8 @@ describe("GenTokens dataloaders", () => { }) describe("createGenTokObjktsCountLoader", () => { + afterAll(cleanup) + beforeAll(async () => { dataloader = createGenTokObjktsCountLoader() @@ -155,6 +163,8 @@ describe("GenTokens dataloaders", () => { }) describe("createGenTokPricingFixedLoader", () => { + afterAll(cleanup) + beforeAll(async () => { dataloader = createGenTokPricingFixedLoader() @@ -179,6 +189,8 @@ describe("GenTokens dataloaders", () => { }) describe("createGenTokPricingDutchAuctionLoader", () => { + afterAll(cleanup) + beforeAll(async () => { dataloader = createGenTokPricingDutchAuctionLoader() @@ -203,6 +215,8 @@ describe("GenTokens dataloaders", () => { }) describe("createGentkTokPrimarySplitsLoader", () => { + afterAll(cleanup) + beforeAll(async () => { dataloader = createGentkTokPrimarySplitsLoader() @@ -235,6 +249,8 @@ describe("GenTokens dataloaders", () => { }) describe("createGentkTokSecondarySplitsLoader", () => { + afterAll(cleanup) + beforeAll(async () => { dataloader = createGentkTokSecondarySplitsLoader() @@ -267,6 +283,8 @@ describe("GenTokens dataloaders", () => { }) describe("createGenTokArticleMentionsLoader", () => { + afterAll(cleanup) + beforeAll(async () => { dataloader = createGenTokArticleMentionsLoader() @@ -302,6 +320,8 @@ describe("GenTokens dataloaders", () => { }) describe("createGenTokReportsLoader", () => { + afterAll(cleanup) + beforeAll(async () => { dataloader = createGenTokReportsLoader() @@ -340,6 +360,8 @@ describe("GenTokens dataloaders", () => { }) describe("createGenTokMarketStatsLoader", () => { + afterAll(cleanup) + beforeAll(async () => { dataloader = createGenTokMarketStatsLoader() @@ -374,6 +396,8 @@ describe("GenTokens dataloaders", () => { const config = { from: start, to: middle } + afterAll(cleanup) + beforeAll(async () => { dataloader = createGenTokMarketStatsHistoryLoader() @@ -409,6 +433,8 @@ describe("GenTokens dataloaders", () => { }) describe("createGenTokObjktFeaturesLoader", () => { + afterAll(cleanup) + beforeAll(async () => { dataloader = createGenTokObjktFeaturesLoader() @@ -457,6 +483,8 @@ describe("GenTokens dataloaders", () => { }) describe("createGenTokOffersLoader", () => { + afterAll(cleanup) + beforeAll(async () => { dataloader = createGenTokOffersLoader() @@ -503,7 +531,300 @@ describe("GenTokens dataloaders", () => { }) }) + describe("createGenTokCollectionOffersLoader", () => { + afterAll(cleanup) + + beforeAll(async () => { + dataloader = createGenTokCollectionOffersLoader() + + await seedTokens() + + // create some collection offers + await collectionOfferFactory(0, { tokenId: 0 }) + await collectionOfferFactory(1, { tokenId: 1 }) + }) + + it("should return the correct collection offers", async () => { + const result = await dataloader.loadMany([{ id: 0 }, { id: 1 }]) + + expect(result).toHaveLength(2) + expect(result).toMatchObject([ + [ + { + tokenId: 0, + }, + ], + [ + { + tokenId: 1, + }, + ], + ]) + }) + }) + + describe("createGenTokOffersAndCollectionOffersLoader", () => { + afterAll(cleanup) + + beforeAll(async () => { + dataloader = createGenTokOffersAndCollectionOffersLoader() + + await seedTokens() + + // create some floors + await marketStatsFactory(0, { floor: 1 }) + await marketStatsFactory(1, { floor: 2 }) + + // create some offers + const objkt = await objktFactory(0, GenerativeTokenVersion.PRE_V3, { + tokenId: 0, + }) + await offerFactory(0, objkt.id, objkt.issuerVersion, { + price: 3, + createdAt: new Date("2023-01-02"), + acceptedAt: new Date("2023-01-02"), + }) + + const objkt2 = await objktFactory(1, GenerativeTokenVersion.V3, { + tokenId: 1, + }) + await offerFactory(1, objkt2.id, objkt2.issuerVersion, { + price: 2, + createdAt: new Date("2023-01-04"), + }) + + // create some collection offers + await collectionOfferFactory(2, { + tokenId: 0, + price: 4, + createdAt: new Date("2023-01-01"), + completedAt: new Date("2023-01-03"), + }) + await collectionOfferFactory(3, { + tokenId: 1, + price: 1, + createdAt: new Date("2023-01-03"), + }) + }) + + describe("filters", () => { + describe("active_eq true", () => { + it("should return the correct offers and collection offers", async () => { + const result = await dataloader.loadMany([ + { id: 0, filters: { active_eq: true } }, + { id: 1 }, + ]) + expect(result).toHaveLength(2) + expect(result).toMatchObject([ + [], + [ + { + id: 1, + objkt: { + issuerId: 1, + }, + }, + { + id: 3, + tokenId: 1, + }, + ], + ]) + }) + }) + + describe("active_eq false", () => { + it("should return the correct offers and collection offers", async () => { + const result = await dataloader.loadMany([ + { id: 0, filters: { active_eq: false } }, + { id: 1 }, + ]) + expect(result).toHaveLength(2) + expect(result).toMatchObject([ + [ + { + id: 0, + objkt: { + issuerId: 0, + }, + }, + { + id: 2, + tokenId: 0, + }, + ], + [], + ]) + }) + }) + }) + + describe("sorting", () => { + describe("createdAt", () => { + it("returns offers in the correct order when ASC", async () => { + const result = await dataloader.loadMany([ + { id: 0, sort: { createdAt: "ASC" } }, + { id: 1, sort: { createdAt: "ASC" } }, + ]) + expect(result).toHaveLength(2) + expect(result).toMatchObject([ + [ + { + id: 2, + }, + { + id: 0, + }, + ], + [ + { + id: 3, + }, + { + id: 1, + }, + ], + ]) + }) + it("returns offers in the correct order when DESC", async () => { + const result = await dataloader.loadMany([ + { id: 0, sort: { createdAt: "DESC" } }, + { id: 1, sort: { createdAt: "DESC" } }, + ]) + expect(result).toHaveLength(2) + expect(result).toMatchObject([ + [ + { + id: 0, + }, + { + id: 2, + }, + ], + [ + { + id: 1, + }, + { + id: 3, + }, + ], + ]) + }) + }) + describe("price", () => { + it("returns offers in the correct order when ASC", async () => { + const result = await dataloader.loadMany([ + { id: 0, sort: { price: "ASC" } }, + { id: 1, sort: { price: "ASC" } }, + ]) + expect(result).toHaveLength(2) + expect(result).toMatchObject([ + [ + { + id: 0, + }, + { + id: 2, + }, + ], + [ + { + id: 3, + }, + { + id: 1, + }, + ], + ]) + }) + + it("returns offers in the correct order when DESC", async () => { + const result = await dataloader.loadMany([ + { id: 0, sort: { price: "DESC" } }, + { id: 1, sort: { price: "DESC" } }, + ]) + expect(result).toHaveLength(2) + expect(result).toMatchObject([ + [ + { + id: 2, + }, + { + id: 0, + }, + ], + [ + { + id: 1, + }, + { + id: 3, + }, + ], + ]) + }) + }) + + describe("floorDifference", () => { + it("returns offers in the correct order when ASC", async () => { + const result = await dataloader.loadMany([ + { id: 0, sort: { floorDifference: "ASC" } }, + { id: 1, sort: { floorDifference: "ASC" } }, + ]) + expect(result).toHaveLength(2) + expect(result).toMatchObject([ + [ + { + id: 0, + }, + { + id: 2, + }, + ], + [ + { + id: 3, + }, + { + id: 1, + }, + ], + ]) + }) + + it("returns offers in the correct order when DESC", async () => { + const result = await dataloader.loadMany([ + { id: 0, sort: { floorDifference: "DESC" } }, + { id: 1, sort: { floorDifference: "DESC" } }, + ]) + expect(result).toHaveLength(2) + expect(result).toMatchObject([ + [ + { + id: 2, + }, + { + id: 0, + }, + ], + [ + { + id: 1, + }, + { + id: 3, + }, + ], + ]) + }) + }) + }) + }) + describe("createGenTokReservesLoader", () => { + afterAll(cleanup) + beforeAll(async () => { dataloader = createGenTokReservesLoader() @@ -534,6 +855,8 @@ describe("GenTokens dataloaders", () => { }) describe("createGentkTokRedeemablesLoader", () => { + afterAll(cleanup) + beforeAll(async () => { dataloader = createGentkTokRedeemablesLoader() @@ -564,6 +887,8 @@ describe("GenTokens dataloaders", () => { }) describe("createGenTokMintTicketSettingsLoader", () => { + afterAll(cleanup) + beforeAll(async () => { dataloader = createGenTokMintTicketSettingsLoader() @@ -575,7 +900,7 @@ describe("GenTokens dataloaders", () => { }) it("should return the correct mint ticket settings", async () => { - const result = await dataloader.loadMany([{ id: 0 }, { id: 1 }]) + const result = await dataloader.loadMany([0, 1]) expect(result).toHaveLength(2) expect(result).toMatchObject([ { @@ -590,6 +915,8 @@ describe("GenTokens dataloaders", () => { }) describe("createGenTokCodexLoader", () => { + afterAll(cleanup) + beforeAll(async () => { dataloader = createGenTokCodexLoader() diff --git a/src/DataLoaders/GenTokens.ts b/src/DataLoaders/GenTokens.ts index a42a145..80119f4 100644 --- a/src/DataLoaders/GenTokens.ts +++ b/src/DataLoaders/GenTokens.ts @@ -19,6 +19,7 @@ import { objktQueryFilter } from "../Query/Filters/Objkt" import { collectionOfferQueryFilter, offerQueryFilter, + sortOffersAndCollectionOffers, } from "../Query/Filters/Offer" import { GenerativeTokenVersion } from "../types/GenerativeToken" import { AnyOffer, offerTypeGuard } from "../types/AnyOffer" @@ -333,7 +334,7 @@ const batchGenTokOffers = async (inputs: any) => { const sort = inputs[0]?.sort const query = Offer.createQueryBuilder("offer") - .select() + .select("offer") .leftJoinAndSelect("offer.objkt", "objkt") .leftJoinAndSelect("objkt.issuer", "issuer", "issuer.id IN(:...ids)") .where("issuer.id IN(:...ids)", { ids }) @@ -388,7 +389,7 @@ const batchGenTokOffersAndCollectionOffers = async (inputs: any) => { .leftJoinAndSelect("objkt.issuer", "issuer", "issuer.id IN(:...ids)") .where("issuer.id IN(:...ids)", { ids }) - // get received collection offers + // get collection offers const collectionOffersQuery = CollectionOffer.createQueryBuilder( "collection_offer" ) @@ -405,14 +406,10 @@ const batchGenTokOffersAndCollectionOffers = async (inputs: any) => { collectionOffersQuery.getMany(), ]) - // extract the sort property and direction - const sortProperty = Object.keys(sort)[0] - const sortDirection = sort[sortProperty] - - // combine and sort the results - const offers = [...individualOffers, ...collectionOffers].sort( - sortByProperty(sortProperty, sortDirection) - ) + // combine the results + const offers = sort + ? sortOffersAndCollectionOffers(individualOffers, collectionOffers, sort) + : [...individualOffers, ...collectionOffers] return ids.map(id => offers.filter((offer: AnyOffer) => diff --git a/src/DataLoaders/User.test.ts b/src/DataLoaders/User.test.ts index 8374e14..3ba6dec 100644 --- a/src/DataLoaders/User.test.ts +++ b/src/DataLoaders/User.test.ts @@ -2,17 +2,20 @@ import { Connection, EntityManager } from "typeorm" import { actionFactory, + collectionOfferFactory, generativeTokenFactory, - marketStatsFactory, mintTicketFactory, + objktFactory, + offerFactory, userFactory, } from "../tests/factories" import { GenerativeTokenVersion } from "../types/GenerativeToken" import { createConnection } from "../createConnection" -import { createMarketStatsGenTokLoader } from "./MarketStats" import { createUsersGenerativeTokensLoader, createUsersMintTicketsLoader, + createUsersOffersAndCollectionOffersReceivedLoader, + createUsersOffersAndCollectionOffersSentLoader, createUsersSalesLoader, } from "./User" import { TokenActionType } from "../Entity/Action" @@ -31,11 +34,14 @@ afterAll(() => { }) const cleanup = async () => { + await manager.query("DELETE FROM collection_offer") + await manager.query("DELETE FROM offer") + await manager.query("DELETE FROM objkt") await manager.query("DELETE FROM mint_ticket") await manager.query("DELETE FROM generative_token") } -afterEach(cleanup) +// afterEach(cleanup) describe("User dataloaders", () => { let dataloader @@ -173,4 +179,122 @@ describe("User dataloaders", () => { ) }) }) + + describe("createUsersOffersAndCollectionOffersSentLoader", () => { + beforeAll(async () => { + dataloader = createUsersOffersAndCollectionOffersSentLoader() + + // create some users + const user = await userFactory("tz1") + const user2 = await userFactory("tz2") + + // create some tokens + await generativeTokenFactory(0, GenerativeTokenVersion.PRE_V3) + await generativeTokenFactory(1, GenerativeTokenVersion.V3) + + // create some objkts + await objktFactory(0, GenerativeTokenVersion.PRE_V3, { + tokenId: 0, + buyerId: user.id, + }) + await objktFactory(1, GenerativeTokenVersion.V3, { + tokenId: 1, + buyerId: user2.id, + }) + + // create some offers + await offerFactory(0, 0, GenerativeTokenVersion.PRE_V3, { + buyerId: user.id, + }) + await offerFactory(1, 1, GenerativeTokenVersion.V3, { + buyerId: user2.id, + }) + + // create some collection offers + await collectionOfferFactory(2, { tokenId: 0, buyerId: user.id }) + await collectionOfferFactory(3, { tokenId: 1, buyerId: user2.id }) + }) + + it("returns the correct offers and collection offers", async () => { + const result = await dataloader.loadMany([{ id: "tz1" }, { id: "tz2" }]) + expect(result).toHaveLength(2) + expect(result).toMatchObject([ + [ + { + id: 0, + }, + { + id: 2, + }, + ], + [ + { + id: 1, + }, + { + id: 3, + }, + ], + ]) + }) + }) + + describe.only("createUsersOffersAndCollectionOffersReceivedLoader", () => { + beforeAll(async () => { + dataloader = createUsersOffersAndCollectionOffersReceivedLoader() + + // create some users + const user = await userFactory("tz1") + const user2 = await userFactory("tz2") + + // create some tokens + await generativeTokenFactory(0, GenerativeTokenVersion.PRE_V3) + await generativeTokenFactory(1, GenerativeTokenVersion.V3) + + // create some objkts + await objktFactory(0, GenerativeTokenVersion.PRE_V3, { + tokenId: 0, + ownerId: user.id, + }) + await objktFactory(1, GenerativeTokenVersion.V3, { + tokenId: 1, + ownerId: user2.id, + }) + + // create some offers + await offerFactory(0, 0, GenerativeTokenVersion.PRE_V3, { + buyerId: user2.id, + }) + await offerFactory(1, 1, GenerativeTokenVersion.V3, { + buyerId: user.id, + }) + + // create some collection offers + await collectionOfferFactory(2, { tokenId: 0, buyerId: user2.id }) + await collectionOfferFactory(3, { tokenId: 1, buyerId: user.id }) + }) + + it("returns the correct offers and collection offers", async () => { + const result = await dataloader.loadMany([{ id: "tz1" }, { id: "tz2" }]) + expect(result).toHaveLength(2) + expect(result).toMatchObject([ + [ + { + id: 0, + }, + { + id: 2, + }, + ], + [ + { + id: 1, + }, + { + id: 3, + }, + ], + ]) + }) + }) }) diff --git a/src/DataLoaders/User.ts b/src/DataLoaders/User.ts index d8af02f..e510785 100644 --- a/src/DataLoaders/User.ts +++ b/src/DataLoaders/User.ts @@ -10,6 +10,7 @@ import { Action, TokenActionType } from "../Entity/Action" import { collectionOfferQueryFilter, offerQueryFilter, + sortOffersAndCollectionOffers, } from "../Query/Filters/Offer" import { Article } from "../Entity/Article" import { articleQueryFilter } from "../Query/Filters/Article" @@ -192,14 +193,10 @@ const batchUserOffersAndCollectionOffersSent = async (inputs: any) => { sentCollectionOffersQuery.getMany(), ]) - // extract the sort property and direction - const sortProperty = Object.keys(sort)[0] - const sortDirection = sort[sortProperty] - - // combine and sort the results - const offers = [...sentOffers, ...sentCollectionOffers].sort( - sortByProperty(sortProperty, sortDirection) - ) + // combine the results + const offers = sort + ? sortOffersAndCollectionOffers(sentOffers, sentCollectionOffers, sort) + : [...sentOffers, ...sentCollectionOffers] return ids.map(id => offers.filter(offer => offer.buyerId === id)) } @@ -241,14 +238,14 @@ const batchUserOffersAndCollectionOffersReceived = async (inputs: any) => { receivedCollectionOffersQuery.getMany(), ]) - // extract the sort property and direction - const sortProperty = Object.keys(sort)[0] - const sortDirection = sort[sortProperty] - - // combine and sort the results - const offers = [...receivedOffers, ...receivedCollectionOffers].sort( - sortByProperty(sortProperty, sortDirection) - ) + // combine the results + const offers = sort + ? sortOffersAndCollectionOffers( + receivedOffers, + receivedCollectionOffers, + sort + ) + : [...receivedOffers, ...receivedCollectionOffers] return ids.map(id => offers.filter( diff --git a/src/Query/Filters/Offer.ts b/src/Query/Filters/Offer.ts index 41201dc..438f344 100644 --- a/src/Query/Filters/Offer.ts +++ b/src/Query/Filters/Offer.ts @@ -1,6 +1,8 @@ import { Brackets } from "typeorm" import { OffersSortInput } from "../../Resolver/Arguments/Sort" import { TQueryFilter } from "./QueryFilter" +import { AnyOffer, offerTypeGuard } from "../../types/AnyOffer" +import { sortByProperty } from "../../Utils/Sort" type OfferFilters = Record @@ -30,6 +32,27 @@ const anyOfferQueryFilter = // add the sort arguments if (sort) { for (const field in sort) { + if (field === "floorDifference") { + // use fd_ as a prefix to avoid conflicts with existing table aliases + if (table === "offer") { + query.leftJoinAndSelect("offer.objkt", "fd_objkt") + query.leftJoinAndSelect("fd_objkt.issuer", "fd_token") + query.leftJoinAndSelect("fd_token.marketStats", "fd_stats") + query.addSelect( + "(offer.price / fd_stats.floor) * 100", + "floorDifference" + ) + query.addOrderBy(`"floorDifference"`, sort[field]) + } else { + query.leftJoinAndSelect("collection_offer.token", "fd_token") + query.leftJoinAndSelect("fd_token.marketStats", "fd_stats") + query.addOrderBy( + `(collection_offer.price / fd_stats.floor) * 100`, + sort[field] + ) + } + continue + } query.addOrderBy(`${table}.${field}`, sort[field]) } } @@ -47,3 +70,41 @@ export const collectionOfferQueryFilter: TQueryFilter< OfferFilters, OffersSortInput > = anyOfferQueryFilter("collection_offer") + +export const sortOffersAndCollectionOffers = ( + offersA: AnyOffer[], + offersB: AnyOffer[], + sort: OffersSortInput +) => { + const sortProperty = Object.keys(sort)[0] + const sortDirection = sort[sortProperty] + + // workaround for the floorDifference computed column + if (sortProperty === "floorDifference") { + return [...offersA, ...offersB].sort((a, b) => { + const aFloor = + (offerTypeGuard(a) + ? a.objkt.issuer?.marketStats.floor + : a.token.marketStats.floor) || 0 + const bFloor = + (offerTypeGuard(b) + ? b.objkt.issuer?.marketStats.floor + : b.token.marketStats.floor) || 0 + + const aFloorDifference = (a.price / aFloor) * 100 + const bFloorDifference = (b.price / bFloor) * 100 + + const displayA = offerTypeGuard(a) ? a.objkt.name : a.token.name + const displayB = offerTypeGuard(b) ? b.objkt.name : b.token.name + + return sortDirection === "ASC" + ? aFloorDifference - bFloorDifference + : bFloorDifference - aFloorDifference + }) + } + + // sort the results + return [...offersA, ...offersB].sort( + sortByProperty(sortProperty, sortDirection) + ) +} diff --git a/src/Resolver/Arguments/Sort.ts b/src/Resolver/Arguments/Sort.ts index cff6ae6..8e5a286 100644 --- a/src/Resolver/Arguments/Sort.ts +++ b/src/Resolver/Arguments/Sort.ts @@ -41,6 +41,10 @@ export class OffersSortInput { @IsIn(["ASC", "DESC"]) price?: "ASC" | "DESC" + @Field(type => String, { nullable: true }) + @IsIn(["ASC", "DESC"]) + floorDifference?: "ASC" | "DESC" + @Field(type => String, { nullable: true }) @IsIn(["ASC", "DESC"]) createdAt?: "ASC" | "DESC" diff --git a/src/Resolver/GenTokenResolver.test.ts b/src/Resolver/GenTokenResolver.test.ts index fc1f8ec..f77781d 100644 --- a/src/Resolver/GenTokenResolver.test.ts +++ b/src/Resolver/GenTokenResolver.test.ts @@ -4,7 +4,9 @@ import { createTestServer } from "../tests/apollo" import { actionFactory, generativeTokenFactory, + objktFactory, redeemableFactory, + userFactory, } from "../tests/factories" import { GenerativeTokenVersion } from "../types/GenerativeToken" import { createConnection } from "../createConnection" @@ -27,6 +29,7 @@ afterAll(() => { const cleanup = async () => { await manager.query("DELETE FROM redeemable") + await manager.query("DELETE FROM objkt") await manager.query("DELETE FROM generative_token") await manager.query("DELETE FROM codex") await manager.query("DELETE FROM action") @@ -298,7 +301,6 @@ describe("GenTokenResolver", () => { }) it("returns the correct generative tokens", () => { - console.log(result) expect(result).toMatchObject({ data: { generativeTokens: [ @@ -360,4 +362,55 @@ describe("GenTokenResolver", () => { }) }) }) + + describe("isHolder", () => { + describe("when the user is not a holder", () => { + let result + + beforeAll(async () => { + await generativeTokenFactory(0, GenerativeTokenVersion.V3) + + result = await testServer.executeOperation({ + query: `query TestQuery { generativeToken(id: 0) { isHolder(userId: "tz1") } }`, + }) + }) + + it("returns false", () => { + expect(result).toMatchObject({ + data: { + generativeToken: { + isHolder: false, + }, + }, + }) + }) + }) + + describe("when the user is a holder", () => { + let result + + beforeAll(async () => { + await generativeTokenFactory(0, GenerativeTokenVersion.V3) + await userFactory("tz1") + await objktFactory(0, GenerativeTokenVersion.V3, { + tokenId: 0, + ownerId: "tz1", + }) + + result = await testServer.executeOperation({ + query: `query TestQuery { generativeToken(id: 0) { isHolder(userId: "tz1") } }`, + }) + }) + + it("returns true", () => { + expect(result).toMatchObject({ + data: { + generativeToken: { + isHolder: true, + }, + }, + }) + }) + }) + }) }) diff --git a/src/Resolver/GenTokenResolver.ts b/src/Resolver/GenTokenResolver.ts index abd5e8a..a27861c 100644 --- a/src/Resolver/GenTokenResolver.ts +++ b/src/Resolver/GenTokenResolver.ts @@ -48,7 +48,7 @@ import { ObjktsSortInput, OffersSortInput, } from "./Arguments/Sort" -import { AnyOffer } from "../types/AnyOffer" +import { AnyOfferUnion } from "../types/AnyOffer" import { CollectionOffer } from "../Entity/CollectionOffer" @Resolver(GenerativeToken) @@ -206,7 +206,7 @@ export class GenTokenResolver { }) } - @FieldResolver(() => [AnyOffer], { + @FieldResolver(returns => [AnyOfferUnion], { description: "Returns all the offers and collection offers associated with a generative token", }) diff --git a/src/Resolver/UserResolver.ts b/src/Resolver/UserResolver.ts index 503d9e7..622a691 100644 --- a/src/Resolver/UserResolver.ts +++ b/src/Resolver/UserResolver.ts @@ -38,7 +38,7 @@ import { ArticleLedger } from "../Entity/ArticleLedger" import { MediaImage } from "../Entity/MediaImage" import { MintTicket } from "../Entity/MintTicket" import { Reserve } from "../Entity/Reserve" -import { AnyOffer } from "../types/AnyOffer" +import { AnyOfferUnion } from "../types/AnyOffer" @Resolver(User) export class UserResolver { @@ -293,7 +293,7 @@ export class UserResolver { }) } - @FieldResolver(returns => [AnyOffer], { + @FieldResolver(returns => [AnyOfferUnion], { description: "Returns all the offers and collection offers made by the user. Can be filtered.", }) @@ -341,7 +341,7 @@ export class UserResolver { }) } - @FieldResolver(returns => [AnyOffer], { + @FieldResolver(returns => [AnyOfferUnion], { description: "Returns all the offers and collection offers received by the user. Can be filtered.", }) diff --git a/src/tests/factories.ts b/src/tests/factories.ts index cea256f..2d884d9 100644 --- a/src/tests/factories.ts +++ b/src/tests/factories.ts @@ -23,6 +23,7 @@ import { Reserve } from "../Entity/Reserve" import { Split } from "../Entity/Split" import { User } from "../Entity/User" import { GenerativeTokenVersion } from "../types/GenerativeToken" +import { CollectionOffer } from "../Entity/CollectionOffer" export const generativeTokenFactory = async ( id: number, @@ -107,6 +108,7 @@ export const objktFactory = async ( objkt.issuerVersion = tokenVersion objkt.issuerId = config.tokenId || 0 objkt.features = config.features || [] + objkt.ownerId = config.ownerId || null await objkt.save() return objkt } @@ -320,10 +322,30 @@ export const offerFactory = async ( offer.objktIssuerVersion = objktIssuerVersion offer.price = config.price || 0 offer.createdAt = config.createdAt || new Date() + offer.acceptedAt = config.acceptedAt || null + offer.buyerId = config.buyerId || "tz1" await offer.save() return offer } +export const collectionOfferFactory = async ( + id: number, + config: Partial = {} +) => { + const collectionOffer = new CollectionOffer() + collectionOffer.id = id + collectionOffer.version = 1 + collectionOffer.tokenId = config.tokenId || 0 + collectionOffer.price = config.price || 1000000 + collectionOffer.amount = config.amount || 1 + collectionOffer.initialAmount = config.initialAmount || 1 + collectionOffer.buyerId = config.buyerId || "tz1" + collectionOffer.createdAt = config.createdAt || new Date() + collectionOffer.completedAt = config.completedAt || null + await collectionOffer.save() + return collectionOffer +} + export const reserveFactory = async ( tokenId: number, config: Partial = {} diff --git a/src/types/AnyOffer.ts b/src/types/AnyOffer.ts index 0ed4820..c79012a 100644 --- a/src/types/AnyOffer.ts +++ b/src/types/AnyOffer.ts @@ -4,7 +4,7 @@ import { Offer } from "../Entity/Offer" export type AnyOffer = Offer | CollectionOffer -const AnyOffer = createUnionType({ +const AnyOfferUnion = createUnionType({ name: "AnyOffer", description: "Any offer, either a collection offer or a single gentk offer", types: () => [CollectionOffer, Offer], @@ -23,4 +23,4 @@ const offerTypeGuard = (offer: AnyOffer): offer is Offer => { return (offer as Offer).objkt !== undefined } -export { AnyOffer, offerTypeGuard } +export { AnyOfferUnion, offerTypeGuard }