Skip to content

Commit fd6d6bb

Browse files
committed
feat: add collections entity
Without this patch the user of our GraphQL endpoints needs to go via hyperboards to access the collections inside. Another drawback is that via this query they can only access the hypercert id, so they'll have to fire a separate query to combine the data. This patch adds collections as a top level entity and exposes full hypercert objects as child entities. This also applies to hyperboards as the standalone collections entity is being reused there too.
1 parent 05b1e8d commit fd6d6bb

File tree

9 files changed

+301
-20
lines changed

9 files changed

+301
-20
lines changed

schema.graphql

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -277,6 +277,7 @@ input BooleanSearchOptions {
277277
"""Collection of hypercerts for reference and display purposes"""
278278
type Collection {
279279
admins: [User!]!
280+
blueprints: [Blueprint!]
280281

281282
"""Chain ID of the collection"""
282283
chain_ids: [EthBigInt!]
@@ -286,12 +287,29 @@ type Collection {
286287

287288
"""Description of the collection"""
288289
description: String!
290+
hypercerts: [Hypercert!]
289291
id: ID
290292

291293
"""Name of the collection"""
292294
name: String!
293295
}
294296

297+
input CollectionFetchInput {
298+
by: CollectionSortOptions
299+
}
300+
301+
input CollectionSortOptions {
302+
created_at: SortOrder
303+
description: SortOrder
304+
name: SortOrder
305+
}
306+
307+
input CollectionWhereInput {
308+
description: StringSearchOptions
309+
id: IdSearchOptions
310+
name: StringSearchOptions
311+
}
312+
295313
"""Pointer to a contract deployed on a chain"""
296314
type Contract {
297315
"""The ID of the chain on which the contract is deployed"""
@@ -413,6 +431,11 @@ type GetBlueprintResponse {
413431
data: [Blueprint!]
414432
}
415433

434+
type GetCollectionsResponse {
435+
count: Int
436+
data: [Collection!]
437+
}
438+
416439
"""Pointer to a contract deployed on a chain"""
417440
type GetContractsResponse {
418441
count: Int
@@ -833,6 +856,7 @@ type Query {
833856
attestationSchemas(first: Int, offset: Int): GetAttestationsSchemaResponse!
834857
attestations(first: Int, offset: Int, sort: AttestationFetchInput, where: AttestationWhereInput): GetAttestationsResponse!
835858
blueprints(first: Int, offset: Int, sort: BlueprintFetchInput, where: BlueprintWhereInput): GetBlueprintResponse!
859+
collections(first: Int, offset: Int, sort: CollectionFetchInput, where: CollectionWhereInput): GetCollectionsResponse!
836860
contracts(first: Int, offset: Int, sort: ContractFetchInput, where: ContractWhereInput): GetContractsResponse!
837861
fractions(first: Int, offset: Int, sort: FractionFetchInput, where: FractionWhereInput): GetFractionsResponse!
838862
hyperboards(first: Int, offset: Int, sort: HyperboardFetchInput, where: HyperboardWhereInput): GetHyperboardsResponse!
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
import { ArgsType, Field, InputType } from "type-graphql";
2+
3+
import { BasicCollectionWhereInput } from "../inputs/collectionInput.js";
4+
import type { OrderOptions } from "../inputs/orderOptions.js";
5+
import { Collection } from "../typeDefs/collectionTypeDefs.js";
6+
import { CollectionSortOptions } from "../inputs/sortOptions.js";
7+
8+
import { withPagination } from "./baseArgs.js";
9+
10+
@InputType()
11+
export class CollectionWhereInput extends BasicCollectionWhereInput {}
12+
13+
@InputType()
14+
export class CollectionFetchInput implements OrderOptions<Collection> {
15+
@Field(() => CollectionSortOptions, { nullable: true })
16+
by?: CollectionSortOptions;
17+
}
18+
19+
@ArgsType()
20+
export class CollectionArgs {
21+
@Field(() => CollectionWhereInput, { nullable: true })
22+
where?: CollectionWhereInput;
23+
@Field(() => CollectionFetchInput, { nullable: true })
24+
sort?: CollectionFetchInput;
25+
}
26+
27+
@ArgsType()
28+
export class GetCollectionsArgs extends withPagination(CollectionArgs) {}
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
import { Field, InputType } from "type-graphql";
2+
3+
import { Collection } from "../typeDefs/collectionTypeDefs.js";
4+
5+
import { IdSearchOptions, StringSearchOptions } from "./searchOptions.js";
6+
import type { WhereOptions } from "./whereOptions.js";
7+
8+
@InputType()
9+
export class BasicCollectionWhereInput implements WhereOptions<Collection> {
10+
@Field(() => IdSearchOptions, { nullable: true })
11+
id?: IdSearchOptions | null;
12+
13+
@Field(() => StringSearchOptions, { nullable: true })
14+
name?: StringSearchOptions;
15+
16+
@Field(() => StringSearchOptions, { nullable: true })
17+
description?: StringSearchOptions;
18+
}

src/graphql/schemas/inputs/sortOptions.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import { Sale } from "../typeDefs/salesTypeDefs.js";
1111
import { Hyperboard } from "../typeDefs/hyperboardTypeDefs.js";
1212
import { Blueprint } from "../typeDefs/blueprintTypeDefs.js";
1313
import { SignatureRequest } from "../typeDefs/signatureRequestTypeDefs.js";
14+
import { Collection } from "../typeDefs/collectionTypeDefs.js";
1415

1516
export type SortOptions<T extends object> = {
1617
[P in keyof T]: SortOrder | null;
@@ -238,3 +239,13 @@ export class SignatureRequestSortOptions
238239
@Field(() => SortOrder, { nullable: true })
239240
purpose?: SortOrder;
240241
}
242+
243+
@InputType()
244+
export class CollectionSortOptions implements SortOptions<Collection> {
245+
@Field(() => SortOrder, { nullable: true })
246+
name?: SortOrder;
247+
@Field(() => SortOrder, { nullable: true })
248+
created_at?: SortOrder;
249+
@Field(() => SortOrder, { nullable: true })
250+
description?: SortOrder;
251+
}
Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
import {
2+
Args,
3+
FieldResolver,
4+
ObjectType,
5+
Query,
6+
Resolver,
7+
Root,
8+
} from "type-graphql";
9+
10+
import { GetCollectionsArgs } from "../args/collectionArgs.js";
11+
import { Collection } from "../typeDefs/collectionTypeDefs.js";
12+
import { Blueprint } from "../typeDefs/blueprintTypeDefs.js";
13+
import { User } from "../typeDefs/userTypeDefs.js";
14+
15+
import { createBaseResolver, DataResponse } from "./baseTypes.js";
16+
import GetHypercertsResponse from "./hypercertResolver.js";
17+
18+
@ObjectType()
19+
class GetCollectionsResponse extends DataResponse(Collection) {}
20+
21+
const CollectionBaseResolver = createBaseResolver("collection");
22+
23+
@Resolver(() => Collection)
24+
class CollectionResolver extends CollectionBaseResolver {
25+
@Query(() => GetCollectionsResponse)
26+
async collections(@Args() args: GetCollectionsArgs) {
27+
try {
28+
const res = await this.supabaseDataService.getCollections(args);
29+
30+
return {
31+
data: res.data,
32+
count: res.count,
33+
};
34+
} catch (e) {
35+
console.error("[CollectionResolver::collections] Error:", e);
36+
throw new Error(`Error fetching collections: ${(e as Error).message}`);
37+
}
38+
}
39+
40+
@FieldResolver(() => GetHypercertsResponse)
41+
async hypercerts(@Root() collection: Collection) {
42+
if (!collection.id) {
43+
console.error(
44+
"[CollectionResolver::hypercerts] Collection ID is undefined",
45+
);
46+
return [];
47+
}
48+
49+
const hypercerts = await this.supabaseDataService.getCollectionHypercerts(
50+
collection.id,
51+
);
52+
53+
if (!hypercerts?.length) {
54+
return [];
55+
}
56+
57+
const hypercertIds = hypercerts
58+
.map((h) => h.hypercert_id)
59+
.filter((id): id is string => id !== undefined);
60+
61+
if (hypercertIds.length === 0) {
62+
return [];
63+
}
64+
65+
const hypercertsData = await this.getHypercerts({
66+
where: { hypercert_id: { in: hypercertIds } },
67+
});
68+
69+
return hypercertsData.data || [];
70+
}
71+
72+
@FieldResolver(() => [User])
73+
async admins(@Root() collection: Collection) {
74+
if (!collection.id) {
75+
console.error("[CollectionResolver::admins] Collection ID is undefined");
76+
return [];
77+
}
78+
79+
const admins = await this.supabaseDataService.getCollectionAdmins(
80+
collection.id,
81+
);
82+
return admins || [];
83+
}
84+
85+
@FieldResolver(() => [Blueprint])
86+
async blueprints(@Root() collection: Collection) {
87+
if (!collection.id) {
88+
console.error(
89+
"[CollectionResolver::blueprints] Collection ID is undefined",
90+
);
91+
return [];
92+
}
93+
94+
const blueprints = await this.supabaseDataService.getCollectionBlueprints(
95+
collection.id,
96+
);
97+
return blueprints || [];
98+
}
99+
}
100+
101+
export { CollectionResolver };

src/graphql/schemas/resolvers/composed.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import { SalesResolver } from "./salesResolver.js";
1111
import { UserResolver } from "./userResolver.js";
1212
import { BlueprintResolver } from "./blueprintResolver.js";
1313
import { SignatureRequestResolver } from "./signatureRequestResolver.js";
14+
import { CollectionResolver } from "./collectionResolver.js";
1415

1516
export const resolvers = [
1617
ContractResolver,
@@ -26,4 +27,5 @@ export const resolvers = [
2627
UserResolver,
2728
BlueprintResolver,
2829
SignatureRequestResolver,
30+
CollectionResolver,
2931
] as const;
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
import { Field, ObjectType } from "type-graphql";
2+
3+
import { EthBigInt } from "../../scalars/ethBigInt.js";
4+
5+
import { BasicTypeDef } from "./baseTypes/basicTypeDef.js";
6+
import { User } from "./userTypeDefs.js";
7+
import { Hypercert } from "./hypercertTypeDefs.js";
8+
import { Blueprint } from "./blueprintTypeDefs.js";
9+
10+
@ObjectType({
11+
description: "Collection of hypercerts for reference and display purposes",
12+
})
13+
export class Collection extends BasicTypeDef {
14+
@Field({ description: "Creation timestamp of the collection" })
15+
created_at?: string;
16+
@Field({ description: "Name of the collection" })
17+
name?: string;
18+
@Field({ description: "Description of the collection" })
19+
description?: string;
20+
@Field(() => [EthBigInt], {
21+
nullable: true,
22+
description: "Chain ID of the collection",
23+
})
24+
chain_ids?: (bigint | number | string)[];
25+
26+
@Field(() => [User])
27+
admins?: User[];
28+
29+
@Field(() => [Hypercert], { nullable: true })
30+
hypercerts?: Hypercert[];
31+
32+
@Field(() => [Blueprint], { nullable: true })
33+
blueprints?: Blueprint[];
34+
}

src/graphql/schemas/typeDefs/hyperboardTypeDefs.ts

Lines changed: 1 addition & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { BasicTypeDef } from "./baseTypes/basicTypeDef.js";
33
import { EthBigInt } from "../../scalars/ethBigInt.js";
44
import { User } from "./userTypeDefs.js";
55
import { GraphQLBigInt } from "graphql-scalars";
6+
import { Collection } from "./collectionTypeDefs.js";
67

78
@ObjectType({
89
description: "Hyperboard of hypercerts for reference and display purposes",
@@ -48,26 +49,6 @@ class SectionResponseType {
4849
count?: number;
4950
}
5051

51-
@ObjectType({
52-
description: "Collection of hypercerts for reference and display purposes",
53-
})
54-
class Collection extends BasicTypeDef {
55-
@Field({ description: "Creation timestamp of the collection" })
56-
created_at?: string;
57-
@Field({ description: "Name of the collection" })
58-
name?: string;
59-
@Field({ description: "Description of the collection" })
60-
description?: string;
61-
@Field(() => [EthBigInt], {
62-
nullable: true,
63-
description: "Chain ID of the collection",
64-
})
65-
chain_ids?: (bigint | number | string)[];
66-
67-
@Field(() => [User])
68-
admins?: User[];
69-
}
70-
7152
@ObjectType({
7253
description: "Section representing a collection within a hyperboard",
7354
})

0 commit comments

Comments
 (0)