Skip to content

Commit de0569e

Browse files
committed
Improve type safety for mutations
Fixes #5
1 parent d5a2ee5 commit de0569e

File tree

4 files changed

+410
-27
lines changed

4 files changed

+410
-27
lines changed

packages/graphql-authentication/package.json

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,15 +28,17 @@
2828
"start": "nodemon -e ts,graphql -x ts-node example/index.ts",
2929
"test": "jest --watch",
3030
"test-coverage": "jest --coverage",
31-
"ci": "npm run -s lint && npm run -s build && npm run -s test-coverage && codecov"
31+
"ci": "npm run -s lint && npm run -s build && npm run -s test-coverage && codecov",
32+
"graphql-types": "gql-gen --template graphql-codegen-typescript-template --out=src/schema.ts --schema http://localhost:4000"
3233
},
3334
"devDependencies": {
3435
"@types/email-templates": "^3.5.0",
3536
"@types/jest": "^23.1.0",
3637
"@volst/tslint-config": "^0.2.1",
3738
"codecov": "^3.0.2",
3839
"email-templates": "^4.0.1",
39-
"graphql-cli": "^2.15.13",
40+
"graphql-code-generator": "^0.9.4",
41+
"graphql-codegen-typescript-template": "^0.9.4",
4042
"graphql-request": "^1.6.0",
4143
"graphql-yoga": "1.14.10",
4244
"jest": "^23.1.0",

packages/graphql-authentication/src/mutations.ts

Lines changed: 36 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,17 @@ import {
1818
InvalidEmailConfirmToken,
1919
UserEmailUnconfirmedError
2020
} from './errors';
21+
import {
22+
SignupByInviteMutationArgs,
23+
SignupMutationArgs,
24+
ConfirmEmailMutationArgs,
25+
LoginMutationArgs,
26+
ChangePasswordMutationArgs,
27+
InviteUserMutationArgs,
28+
TriggerPasswordResetMutationArgs,
29+
PasswordResetMutationArgs,
30+
UpdateCurrentUserMutationArgs
31+
} from './schema';
2132

2233
function generateToken(user: User, ctx: Context) {
2334
return jwt.sign({ userId: user.id }, ctx.graphqlAuthentication.secret);
@@ -34,7 +45,11 @@ function getHashedPassword(value: string) {
3445
}
3546

3647
export const mutations = {
37-
async signupByInvite(parent: any, { data }: { data: User }, ctx: Context) {
48+
async signupByInvite(
49+
parent: any,
50+
{ data }: SignupByInviteMutationArgs,
51+
ctx: Context
52+
) {
3853
// Important first check, because i.e. the `inviteToken` could be an empty string
3954
// and in that case the find query beneath would find any user with any given email,
4055
// allowing you to change the password of everybody.
@@ -72,7 +87,7 @@ export const mutations = {
7287
};
7388
},
7489

75-
async signup(parent: any, { data }: { data: User }, ctx: Context) {
90+
async signup(parent: any, { data }: SignupMutationArgs, ctx: Context) {
7691
if (!data.email) {
7792
throw new MissingDataError();
7893
}
@@ -122,7 +137,7 @@ export const mutations = {
122137

123138
async confirmEmail(
124139
parent: any,
125-
{ emailConfirmToken, email }: { emailConfirmToken: string; email: string },
140+
{ emailConfirmToken, email }: ConfirmEmailMutationArgs,
126141
ctx: Context
127142
) {
128143
if (!emailConfirmToken || !email) {
@@ -154,7 +169,11 @@ export const mutations = {
154169
};
155170
},
156171

157-
async login(parent: any, { email, password }: User, ctx: Context) {
172+
async login(
173+
parent: any,
174+
{ email, password }: LoginMutationArgs,
175+
ctx: Context
176+
) {
158177
const user = await ctx.graphqlAuthentication.adapter.findUserByEmail(
159178
ctx,
160179
email
@@ -196,7 +215,7 @@ export const mutations = {
196215

197216
async changePassword(
198217
parent: any,
199-
{ oldPassword, newPassword }: { oldPassword: string; newPassword: string },
218+
{ oldPassword, newPassword }: ChangePasswordMutationArgs,
200219
ctx: Context
201220
) {
202221
const user = await getUser(ctx);
@@ -222,13 +241,7 @@ export const mutations = {
222241

223242
async inviteUser(
224243
parent: any,
225-
{
226-
data
227-
}: {
228-
data: {
229-
email: string;
230-
};
231-
},
244+
{ data }: InviteUserMutationArgs,
232245
ctx: Context
233246
) {
234247
await getUser(ctx);
@@ -298,7 +311,11 @@ export const mutations = {
298311
};
299312
},
300313

301-
async triggerPasswordReset(parent: any, { email }: User, ctx: Context) {
314+
async triggerPasswordReset(
315+
parent: any,
316+
{ email }: TriggerPasswordResetMutationArgs,
317+
ctx: Context
318+
) {
302319
if (!validator.isEmail(email)) {
303320
throw new InvalidEmailError();
304321
}
@@ -345,7 +362,7 @@ export const mutations = {
345362

346363
async passwordReset(
347364
parent: any,
348-
{ email, resetToken, password }: User,
365+
{ email, resetToken, password }: PasswordResetMutationArgs,
349366
ctx: Context
350367
) {
351368
if (!resetToken || !password) {
@@ -378,7 +395,11 @@ export const mutations = {
378395
};
379396
},
380397

381-
async updateCurrentUser(parent: any, { data }: { data: any }, ctx: Context) {
398+
async updateCurrentUser(
399+
parent: any,
400+
{ data }: UpdateCurrentUserMutationArgs,
401+
ctx: Context
402+
) {
382403
const user = await getUser(ctx);
383404

384405
await ctx.graphqlAuthentication.adapter.updateUserInfo(ctx, user.id, data);
Lines changed: 242 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,242 @@
1+
import { GraphQLResolveInfo } from 'graphql';
2+
3+
type Resolver<Result, Args = any> = (
4+
parent: any,
5+
args: Args,
6+
context: any,
7+
info: GraphQLResolveInfo
8+
) => Promise<Result> | Result;
9+
10+
export type DateTime = Date | string;
11+
12+
export interface Query {
13+
currentUser?: User | null;
14+
}
15+
16+
export interface User {
17+
id: string;
18+
email: string;
19+
name: string;
20+
inviteAccepted: boolean;
21+
emailConfirmed: boolean;
22+
deletedAt?: DateTime | null;
23+
lastLogin?: DateTime | null;
24+
joinedAt: DateTime;
25+
isSuper: boolean;
26+
}
27+
28+
export interface Mutation {
29+
signupByInvite: AuthPayload;
30+
signup: AuthPayload;
31+
confirmEmail: AuthPayload;
32+
inviteUser: UserIdPayload;
33+
login: AuthPayload;
34+
changePassword: UserIdPayload;
35+
updateCurrentUser?: User | null;
36+
triggerPasswordReset: TriggerPasswordResetPayload;
37+
passwordReset: UserIdPayload;
38+
}
39+
40+
export interface AuthPayload {
41+
token: string;
42+
user: User;
43+
}
44+
45+
export interface UserIdPayload {
46+
id: string;
47+
}
48+
49+
export interface TriggerPasswordResetPayload {
50+
ok: boolean;
51+
}
52+
53+
export namespace QueryResolvers {
54+
export interface Resolvers {
55+
currentUser?: CurrentUserResolver;
56+
}
57+
58+
export type CurrentUserResolver = Resolver<User | null>;
59+
}
60+
61+
export namespace UserResolvers {
62+
export interface Resolvers {
63+
id?: IdResolver;
64+
email?: EmailResolver;
65+
name?: NameResolver;
66+
inviteAccepted?: InviteAcceptedResolver;
67+
emailConfirmed?: EmailConfirmedResolver;
68+
deletedAt?: DeletedAtResolver;
69+
lastLogin?: LastLoginResolver;
70+
joinedAt?: JoinedAtResolver;
71+
isSuper?: IsSuperResolver;
72+
}
73+
74+
export type IdResolver = Resolver<string>;
75+
export type EmailResolver = Resolver<string>;
76+
export type NameResolver = Resolver<string>;
77+
export type InviteAcceptedResolver = Resolver<boolean>;
78+
export type EmailConfirmedResolver = Resolver<boolean>;
79+
export type DeletedAtResolver = Resolver<DateTime | null>;
80+
export type LastLoginResolver = Resolver<DateTime | null>;
81+
export type JoinedAtResolver = Resolver<DateTime>;
82+
export type IsSuperResolver = Resolver<boolean>;
83+
}
84+
85+
export namespace MutationResolvers {
86+
export interface Resolvers {
87+
signupByInvite?: SignupByInviteResolver;
88+
signup?: SignupResolver;
89+
confirmEmail?: ConfirmEmailResolver;
90+
inviteUser?: InviteUserResolver;
91+
login?: LoginResolver;
92+
changePassword?: ChangePasswordResolver;
93+
updateCurrentUser?: UpdateCurrentUserResolver;
94+
triggerPasswordReset?: TriggerPasswordResetResolver;
95+
passwordReset?: PasswordResetResolver;
96+
}
97+
98+
export type SignupByInviteResolver = Resolver<
99+
AuthPayload,
100+
SignupByInviteArgs
101+
>;
102+
export interface SignupByInviteArgs {
103+
data: SignupByInviteInput;
104+
}
105+
106+
export type SignupResolver = Resolver<AuthPayload, SignupArgs>;
107+
export interface SignupArgs {
108+
data: SignupInput;
109+
}
110+
111+
export type ConfirmEmailResolver = Resolver<AuthPayload, ConfirmEmailArgs>;
112+
export interface ConfirmEmailArgs {
113+
email: string;
114+
emailConfirmToken: string;
115+
}
116+
117+
export type InviteUserResolver = Resolver<UserIdPayload, InviteUserArgs>;
118+
export interface InviteUserArgs {
119+
data: InviteUserInput;
120+
}
121+
122+
export type LoginResolver = Resolver<AuthPayload, LoginArgs>;
123+
export interface LoginArgs {
124+
email: string;
125+
password: string;
126+
}
127+
128+
export type ChangePasswordResolver = Resolver<
129+
UserIdPayload,
130+
ChangePasswordArgs
131+
>;
132+
export interface ChangePasswordArgs {
133+
oldPassword: string;
134+
newPassword: string;
135+
}
136+
137+
export type UpdateCurrentUserResolver = Resolver<
138+
User | null,
139+
UpdateCurrentUserArgs
140+
>;
141+
export interface UpdateCurrentUserArgs {
142+
data: UserUpdateInput;
143+
}
144+
145+
export type TriggerPasswordResetResolver = Resolver<
146+
TriggerPasswordResetPayload,
147+
TriggerPasswordResetArgs
148+
>;
149+
export interface TriggerPasswordResetArgs {
150+
email: string;
151+
}
152+
153+
export type PasswordResetResolver = Resolver<
154+
UserIdPayload,
155+
PasswordResetArgs
156+
>;
157+
export interface PasswordResetArgs {
158+
email: string;
159+
resetToken: string;
160+
password: string;
161+
}
162+
}
163+
164+
export namespace AuthPayloadResolvers {
165+
export interface Resolvers {
166+
token?: TokenResolver;
167+
user?: UserResolver;
168+
}
169+
170+
export type TokenResolver = Resolver<string>;
171+
export type UserResolver = Resolver<User>;
172+
}
173+
174+
export namespace UserIdPayloadResolvers {
175+
export interface Resolvers {
176+
id?: IdResolver;
177+
}
178+
179+
export type IdResolver = Resolver<string>;
180+
}
181+
182+
export namespace TriggerPasswordResetPayloadResolvers {
183+
export interface Resolvers {
184+
ok?: OkResolver;
185+
}
186+
187+
export type OkResolver = Resolver<boolean>;
188+
}
189+
190+
export interface SignupByInviteInput {
191+
email: string;
192+
inviteToken: string;
193+
password: string;
194+
name: string;
195+
}
196+
197+
export interface SignupInput {
198+
email: string;
199+
password: string;
200+
name: string;
201+
}
202+
203+
export interface InviteUserInput {
204+
email: string;
205+
}
206+
207+
export interface UserUpdateInput {
208+
email?: string | null;
209+
name?: string | null;
210+
}
211+
export interface SignupByInviteMutationArgs {
212+
data: SignupByInviteInput;
213+
}
214+
export interface SignupMutationArgs {
215+
data: SignupInput;
216+
}
217+
export interface ConfirmEmailMutationArgs {
218+
email: string;
219+
emailConfirmToken: string;
220+
}
221+
export interface InviteUserMutationArgs {
222+
data: InviteUserInput;
223+
}
224+
export interface LoginMutationArgs {
225+
email: string;
226+
password: string;
227+
}
228+
export interface ChangePasswordMutationArgs {
229+
oldPassword: string;
230+
newPassword: string;
231+
}
232+
export interface UpdateCurrentUserMutationArgs {
233+
data: UserUpdateInput;
234+
}
235+
export interface TriggerPasswordResetMutationArgs {
236+
email: string;
237+
}
238+
export interface PasswordResetMutationArgs {
239+
email: string;
240+
resetToken: string;
241+
password: string;
242+
}

0 commit comments

Comments
 (0)