Skip to content

Commit f91b970

Browse files
Full implementation of 'upvoteTutorial'
1 parent 31cf646 commit f91b970

File tree

6 files changed

+125
-40
lines changed

6 files changed

+125
-40
lines changed

packages/server/.yoga/nexus.ts

Lines changed: 14 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -297,8 +297,8 @@ export interface NexusGenEnums {
297297

298298
export interface NexusGenRootTypes {
299299
AuthenticateUserPayload: { // root type
300-
code: string; // String!
301-
message: string; // String!
300+
code?: string | null; // String
301+
message?: string | null; // String
302302
success: boolean; // Boolean!
303303
token: string; // String!
304304
user: NexusGenRootTypes['User']; // User!
@@ -309,10 +309,10 @@ export interface NexusGenRootTypes {
309309
User: prisma.User;
310310
UserTutorial: prisma.UserTutorial;
311311
UserTutorialPayload: { // root type
312-
code: string; // String!
313-
message: string; // String!
312+
code?: string | null; // String
313+
message?: string | null; // String
314314
success: boolean; // Boolean!
315-
userTutorial: NexusGenRootTypes['UserTutorial']; // UserTutorial!
315+
userTutorial?: NexusGenRootTypes['UserTutorial'] | null; // UserTutorial
316316
}
317317
Viewer: { // root type
318318
id: string; // ID!
@@ -335,15 +335,15 @@ export interface NexusGenAllTypes extends NexusGenRootTypes {
335335

336336
export interface NexusGenFieldTypes {
337337
AuthenticateUserPayload: { // field return type
338-
code: string; // String!
339-
message: string; // String!
338+
code: string | null; // String
339+
message: string | null; // String
340340
success: boolean; // Boolean!
341341
token: string; // String!
342342
user: NexusGenRootTypes['User']; // User!
343343
}
344344
Mutation: { // field return type
345345
authenticate: NexusGenRootTypes['AuthenticateUserPayload'] | null; // AuthenticateUserPayload
346-
upvote: NexusGenRootTypes['UserTutorialPayload']; // UserTutorialPayload!
346+
upvoteTutorial: NexusGenRootTypes['UserTutorialPayload']; // UserTutorialPayload!
347347
}
348348
Query: { // field return type
349349
viewer: NexusGenRootTypes['Viewer'] | null; // Viewer
@@ -384,18 +384,18 @@ export interface NexusGenFieldTypes {
384384
user: NexusGenRootTypes['User'] | null; // User
385385
}
386386
UserTutorialPayload: { // field return type
387-
code: string; // String!
388-
message: string; // String!
387+
code: string | null; // String
388+
message: string | null; // String
389389
success: boolean; // Boolean!
390-
userTutorial: NexusGenRootTypes['UserTutorial']; // UserTutorial!
390+
userTutorial: NexusGenRootTypes['UserTutorial'] | null; // UserTutorial
391391
}
392392
Viewer: { // field return type
393393
id: string; // ID!
394394
user: NexusGenRootTypes['User']; // User!
395395
}
396396
PayloadInterface: { // field return type
397-
code: string; // String!
398-
message: string; // String!
397+
code: string | null; // String
398+
message: string | null; // String
399399
success: boolean; // Boolean!
400400
}
401401
}
@@ -405,7 +405,7 @@ export interface NexusGenArgTypes {
405405
authenticate: { // args
406406
githubCode: string; // String!
407407
}
408-
upvote: { // args
408+
upvoteTutorial: { // args
409409
tutorialId: string; // ID!
410410
}
411411
}

packages/server/src/context.ts

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,23 @@
11
import { prisma, Prisma } from '../.yoga/prisma-client'
22
import { yogaContext } from 'yoga'
3+
import { getUserId } from './utils';
34

45
export interface Context {
56
prisma: Prisma
67
req: any
8+
currentUserId?: string;
79
}
810

9-
export default yogaContext(({ req }) => ({
10-
req,
11-
prisma,
12-
}))
11+
export default yogaContext(({ req }) => {
12+
const context = {
13+
req,
14+
prisma,
15+
currentUserId: null
16+
} as Context;
17+
try {
18+
context.currentUserId = getUserId(context);
19+
} catch (e) {
20+
// no user
21+
}
22+
return context;
23+
})
Lines changed: 74 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
import { prismaObjectType, objectType, mutationField, idArg } from 'yoga'
2-
// import { getUserId } from '../utils';
32
import { PayloadInterface } from './PayloadInterface';
3+
import { authorizeUser } from './auth';
4+
import { Context } from '../context';
5+
import { UserTutorial as UserTutorialType, UserTutorialCreateInput } from '../../.yoga/prisma-client';
46

57
export const UserTutorial = prismaObjectType({
68
name: 'UserTutorial',
@@ -13,30 +15,93 @@ export const UserTutorial = prismaObjectType({
1315

1416
export const UserTutorialPayload = objectType({
1517
name: "UserTutorialPayload",
16-
definition: (t) => {
17-
t.implements(PayloadInterface);
18-
t.field("userTutorial", {
18+
definition: (type) => {
19+
type.implements(PayloadInterface);
20+
type.field("userTutorial", {
1921
type: UserTutorial,
2022
nullable: true
2123
});
2224
}
2325
})
2426

25-
export const upvote = mutationField("upvote", {
27+
export const upvoteTutorial = mutationField("upvoteTutorial", {
2628
type: UserTutorialPayload,
27-
description: "A user can upvote a tutorial.",
29+
description: "An authenticated user can upvote a tutorial.",
2830
args: {
2931
tutorialId: idArg({
3032
required: true
3133
})
3234
},
33-
resolve: async (parent, args, ctx) => {
34-
// const currentUserId = getUserId(ctx);
35+
authorize: authorizeUser(),
36+
resolve: async (_, { tutorialId }, ctx) => {
37+
const userId = ctx.currentUserId;
38+
const existingUserTutorial = await getUserTutorial({
39+
userId,
40+
tutorialId
41+
}, ctx);
42+
let upsertedUserTutorial = await upsertUserTutorial({
43+
userId,
44+
tutorialId,
45+
userTutorialId: existingUserTutorial && existingUserTutorial.id,
46+
updates: {
47+
upvoted: !existingUserTutorial.upvoted,
48+
},
49+
}, ctx);
3550
return ({
3651
code: "200",
3752
success: true,
3853
message: null,
39-
userTutorial: null
54+
userTutorial: upsertedUserTutorial
4055
})
4156
}
4257
})
58+
59+
60+
async function upsertUserTutorial(args: { userTutorialId?: string, updates: UserTutorialCreateInput, userId: string, tutorialId: any }, ctx: Context): Promise<UserTutorialType> {
61+
const { userTutorialId, updates, userId, tutorialId } = args;
62+
let upsertedUserTutorial;
63+
if (userTutorialId) {
64+
upsertedUserTutorial = await ctx.prisma.updateUserTutorial({
65+
where: {
66+
id: userTutorialId
67+
},
68+
data: updates
69+
});
70+
}
71+
else {
72+
upsertedUserTutorial = await ctx.prisma.createUserTutorial({
73+
...updates,
74+
user: {
75+
connect: {
76+
id: userId
77+
}
78+
},
79+
tutorial: {
80+
connect: {
81+
id: tutorialId
82+
}
83+
}
84+
});
85+
}
86+
return upsertedUserTutorial;
87+
}
88+
89+
async function getUserTutorial(args: { userId: string, tutorialId: any }, ctx: Context): Promise<null | UserTutorialType> {
90+
const { userId, tutorialId } = args;
91+
const existingUserTutorials = await ctx.prisma.userTutorials({
92+
first: 1,
93+
where: {
94+
user: {
95+
id: userId
96+
},
97+
tutorial: {
98+
id: tutorialId
99+
}
100+
}
101+
});
102+
if (existingUserTutorials.length > 0) {
103+
return existingUserTutorials[0];
104+
}
105+
return null;
106+
}
107+

packages/server/src/graphql/Viewer.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
11
import { queryField, objectType } from "yoga";
2-
import { getUserId } from "../utils";
32

43
export const Viewer = objectType({
54
name: "Viewer",
65
definition: (t) => {
76
t.id("id");
87
t.field("user", {
98
type: "User",
10-
resolve: async ({ id }, args, ctx) => {
9+
resolve: async (parent, _, ctx) => {
10+
const { id } = parent;
1111
return await ctx.prisma.user({ id })
1212
}
1313
})
@@ -19,7 +19,7 @@ export const viewer = queryField("viewer", {
1919
nullable: true,
2020
resolve: (_, args, ctx) => {
2121
try {
22-
const id = getUserId(ctx)
22+
const id = ctx.currentUserId
2323
return {
2424
id
2525
}

packages/server/src/graphql/auth.ts

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ import { Context } from "../context";
55
import { User } from "../../.yoga/prisma-client";
66
import config from "../config";
77
import { PayloadInterface } from "./PayloadInterface";
8+
import { AuthorizeResolver } from "nexus/dist/core";
9+
import { NexusGenRootTypes } from "../../.yoga/nexus";
810

911
export const AuthenticateUserPayload = objectType({
1012
name: "AuthenticateUserPayload",
@@ -61,4 +63,11 @@ async function createPrismaUser(ctx: Context, githubUser: GithubUser): Promise<U
6163
avatarUrl: githubUser.avatar_url
6264
})
6365
return user
64-
}
66+
}
67+
68+
export const authorizeUser = (): AuthorizeResolver<any, any> => (parent, args, ctx) => {
69+
if (ctx.currentUserId) {
70+
return true;
71+
}
72+
return false;
73+
};

packages/server/src/schema.graphql

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,8 @@
33

44

55
type AuthenticateUserPayload implements PayloadInterface {
6-
code: String!
7-
message: String!
6+
code: String
7+
message: String
88
success: Boolean!
99
token: String!
1010
user: User!
@@ -18,14 +18,14 @@ type Mutation {
1818
githubCode: String!
1919
): AuthenticateUserPayload
2020

21-
"""A user can upvote a tutorial."""
22-
upvote(tutorialId: ID!): UserTutorialPayload!
21+
"""An authenticated user can upvote a tutorial."""
22+
upvoteTutorial(tutorialId: ID!): UserTutorialPayload!
2323
}
2424

2525
"""The standard interface for all mutation responses"""
2626
interface PayloadInterface {
27-
code: String!
28-
message: String!
27+
code: String
28+
message: String
2929
success: Boolean!
3030
}
3131

@@ -178,10 +178,10 @@ enum UserTutorialOrderByInput {
178178
}
179179

180180
type UserTutorialPayload implements PayloadInterface {
181-
code: String!
182-
message: String!
181+
code: String
182+
message: String
183183
success: Boolean!
184-
userTutorial: UserTutorial!
184+
userTutorial: UserTutorial
185185
}
186186

187187
input UserTutorialWhereInput {

0 commit comments

Comments
 (0)