Skip to content

Commit 3a0ca3a

Browse files
committed
refactor(user): implement comprehensive user resolver and related components
Restructured user-related code to improve organization and maintainability: - Moved UserResolver from graphql to services directory - Updated import paths in composed resolver - Enhanced UsersEntityService with comprehensive documentation - Added detailed JSDoc comments for UserService, UsersQueryStrategy, and UserResolver - Introduced comprehensive test coverage for UsersService, UsersQueryStrategy, and UserResolver - Improved error handling to return null for failed resolver queries - Standardized code structure with other similar resolvers - Added test utilities for generating mock user and signature request data
1 parent 073b341 commit 3a0ca3a

File tree

10 files changed

+632
-73
lines changed

10 files changed

+632
-73
lines changed

src/graphql/schemas/resolvers/composed.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import { OrderResolver } from "./orderResolver.js";
88
import { HyperboardResolver } from "./hyperboardResolver.js";
99
import { AllowlistRecordResolver } from "../../../services/graphql/resolvers/allowlistRecordResolver.js";
1010
import { SalesResolver } from "../../../services/graphql/resolvers/salesResolver.js";
11-
import { UserResolver } from "./userResolver.js";
11+
import { UserResolver } from "../../../services/graphql/resolvers/userResolver.js";
1212
import { BlueprintResolver } from "../../../services/graphql/resolvers/blueprintResolver.js";
1313
import { SignatureRequestResolver } from "./signatureRequestResolver.js";
1414
import { CollectionResolver } from "./collectionResolver.js";

src/graphql/schemas/resolvers/userResolver.ts

Lines changed: 0 additions & 42 deletions
This file was deleted.

src/services/database/entities/BlueprintsEntityService.ts

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -23,11 +23,6 @@ export type BlueprintAdminSelect = Selectable<DataDatabase["users"]>;
2323
* - Handle blueprint minting and collection updates
2424
* - Transaction support for complex operations
2525
*
26-
* Error Handling:
27-
* - All database operations are wrapped in try-catch blocks
28-
* - Errors are logged and rethrown for proper error propagation
29-
* - Transaction rollback on failure for multi-step operations
30-
*
3126
* @singleton Marks the class as a singleton for dependency injection
3227
*/
3328
@singleton()

src/services/database/strategies/UsersQueryStrategy.ts

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,34 @@ import { Kysely } from "kysely";
22
import { DataDatabase } from "../../../types/kyselySupabaseData.js";
33
import { QueryStrategy } from "./QueryStrategy.js";
44

5+
/**
6+
* Strategy for building queries related to user data.
7+
* Implements the QueryStrategy interface for the users table.
8+
*
9+
* This strategy extends the base QueryStrategy to provide user-specific query building.
10+
* It handles:
11+
* - Basic data retrieval from the users table
12+
* - Counting operations with appropriate joins
13+
*
14+
* @template DataDatabase - The database type containing the users table
15+
*/
516
export class UsersQueryStrategy extends QueryStrategy<DataDatabase, "users"> {
617
protected readonly tableName = "users" as const;
718

19+
/**
20+
* Builds a query to select all user data.
21+
* @param db - Database connection
22+
* @returns Query builder for selecting user data
23+
*/
824
buildDataQuery(db: Kysely<DataDatabase>) {
925
return db.selectFrom(this.tableName).selectAll();
1026
}
1127

28+
/**
29+
* Builds a query to count total number of users.
30+
* @param db - Database connection
31+
* @returns Query builder for counting users
32+
*/
1233
buildCountQuery(db: Kysely<DataDatabase>) {
1334
return db.selectFrom(this.tableName).select((eb) => {
1435
return eb.fn.countAll().as("count");
Lines changed: 138 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,138 @@
1+
import { Args, FieldResolver, Query, Resolver, Root } from "type-graphql";
2+
3+
import { GetUsersArgs } from "../../../graphql/schemas/args/userArgs.js";
4+
import { SignatureRequest } from "../../../graphql/schemas/typeDefs/signatureRequestTypeDefs.js";
5+
import GetUsersResponse, {
6+
User,
7+
} from "../../../graphql/schemas/typeDefs/userTypeDefs.js";
8+
9+
import { inject, injectable } from "tsyringe";
10+
import { SignatureRequestsService } from "../../database/entities/SignatureRequestsEntityService.js";
11+
import { UsersService } from "../../database/entities/UsersEntityService.js";
12+
13+
/**
14+
* GraphQL resolver for User operations.
15+
* Handles queries for users and resolves related fields.
16+
*
17+
* This resolver provides:
18+
* - Query for fetching users with optional filtering
19+
* - Field resolution for signature requests associated with a user
20+
*
21+
* Error Handling:
22+
* If an operation fails, it will:
23+
* - Log the error internally for monitoring
24+
* - Return null/empty data to the client
25+
* - Include error information in the GraphQL response errors array
26+
*
27+
* @injectable Marks the class as injectable for dependency injection with tsyringe
28+
* @resolver Marks the class as a GraphQL resolver for the User type
29+
*/
30+
@injectable()
31+
@Resolver(() => User)
32+
class UserResolver {
33+
/**
34+
* Creates a new instance of UserResolver.
35+
*
36+
* @param usersService - Service for handling user operations
37+
* @param signatureRequestsService - Service for handling signature request operations
38+
*/
39+
constructor(
40+
@inject(UsersService)
41+
private usersService: UsersService,
42+
@inject(SignatureRequestsService)
43+
private signatureRequestsService: SignatureRequestsService,
44+
) {}
45+
46+
/**
47+
* Queries users based on provided arguments.
48+
* Returns both the matching users and a total count.
49+
*
50+
* @param args - Query arguments for filtering users
51+
* @returns A promise that resolves to an object containing:
52+
* - data: Array of users matching the query
53+
* - count: Total number of matching users
54+
*
55+
* @example
56+
* ```graphql
57+
* query {
58+
* users(
59+
* where: {
60+
* address: { eq: "0x..." },
61+
* chain_id: { eq: 1 }
62+
* }
63+
* ) {
64+
* data {
65+
* id
66+
* address
67+
* display_name
68+
* avatar
69+
* }
70+
* count
71+
* }
72+
* }
73+
* ```
74+
*/
75+
@Query(() => GetUsersResponse)
76+
async users(@Args() args: GetUsersArgs) {
77+
try {
78+
return await this.usersService.getUsers(args);
79+
} catch (e) {
80+
console.error(
81+
`[UserResolver::users] Error fetching users: ${(e as Error).message}`,
82+
);
83+
return null;
84+
}
85+
}
86+
87+
/**
88+
* Resolves the signature_requests field for a user.
89+
* This field resolver is called automatically when the signature_requests field is requested in a query.
90+
*
91+
* @param user - The user for which to resolve signature requests
92+
* @returns A promise resolving to:
93+
* - Array of signature requests if found
94+
* - null if:
95+
* - No user address is available
96+
* - An error occurs during retrieval
97+
*
98+
* @example
99+
* ```graphql
100+
* query {
101+
* users {
102+
* data {
103+
* id
104+
* address
105+
* signature_requests {
106+
* id
107+
* message
108+
* status
109+
* }
110+
* }
111+
* }
112+
* }
113+
* ```
114+
*/
115+
@FieldResolver(() => [SignatureRequest])
116+
async signature_requests(@Root() user: User) {
117+
if (!user.address) {
118+
return null;
119+
}
120+
121+
try {
122+
return await this.signatureRequestsService.getSignatureRequests({
123+
where: {
124+
safe_address: {
125+
eq: user.address,
126+
},
127+
},
128+
});
129+
} catch (e) {
130+
console.error(
131+
`[UserResolver::signature_requests] Error fetching signature requests for user ${user.id}: ${(e as Error).message}`,
132+
);
133+
return null;
134+
}
135+
}
136+
}
137+
138+
export { UserResolver };

test/services/database/entities/BlueprintsEntityService.test.ts

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import {
1111
generateHypercertId,
1212
generateMockAddress,
1313
generateMockBlueprint,
14+
generateMockUser,
1415
} from "../../../utils/testUtils.js";
1516

1617
const mockDb = vi.fn();
@@ -148,14 +149,7 @@ describe("BlueprintsService", () => {
148149
const mockBlueprint = generateMockBlueprint();
149150
await db.insertInto("blueprints").values(mockBlueprint).execute();
150151

151-
const mockUser = {
152-
id: "user1",
153-
address: "0x123",
154-
display_name: "Test Admin",
155-
avatar: "test-avatar",
156-
chain_id: 1,
157-
created_at: new Date().toISOString(),
158-
};
152+
const mockUser = generateMockUser();
159153
await db.insertInto("users").values(mockUser).execute();
160154

161155
await db

0 commit comments

Comments
 (0)