From bec934fed484fecb901e0c2c15b1bd1440cc69f2 Mon Sep 17 00:00:00 2001 From: Takumi Hara Date: Thu, 25 Aug 2022 20:22:20 +0900 Subject: [PATCH 1/7] feat: subscription for notification --- api/nexus-typegen.ts | 2 ++ api/schema.graphql | 1 + api/src/graphql/Message.ts | 14 ++++++++++++++ 3 files changed, 17 insertions(+) diff --git a/api/nexus-typegen.ts b/api/nexus-typegen.ts index 400d26c..67bf544 100644 --- a/api/nexus-typegen.ts +++ b/api/nexus-typegen.ts @@ -219,6 +219,7 @@ export interface NexusGenFieldTypes { } Subscription: { // field return type waitForMessage: NexusGenRootTypes['Message'] | null; // Message + waitForMessageNotification: NexusGenRootTypes['Post'] | null; // Post } User: { // field return type githubBio: string; // String! @@ -334,6 +335,7 @@ export interface NexusGenFieldTypeNames { } Subscription: { // field return type name waitForMessage: 'Message' + waitForMessageNotification: 'Post' } User: { // field return type name githubBio: 'String' diff --git a/api/schema.graphql b/api/schema.graphql index b8532a2..c7eb313 100644 --- a/api/schema.graphql +++ b/api/schema.graphql @@ -120,6 +120,7 @@ type Skill { type Subscription { waitForMessage(postId: String!): Message + waitForMessageNotification: Post } type User { diff --git a/api/src/graphql/Message.ts b/api/src/graphql/Message.ts index ef3d9b1..9bafdec 100644 --- a/api/src/graphql/Message.ts +++ b/api/src/graphql/Message.ts @@ -92,9 +92,13 @@ export const MessageMutation = extendType({ connect: { id: profileId }, }, }, + include: { + post: true, + }, }); // todo: is 'await' necessary? (https://codesandbox.io/s/nexus-example-subscriptions-59kdb?file=/src/schema/index.ts) await context.pubsub.publish(postId.toString(), newMessage); + await context.pubsub.publish("messageNotification", newMessage.post); return newMessage; }, }); @@ -148,5 +152,15 @@ export const MessageSubscription = subscriptionType({ return await messagePromise; }, }); + + t.field("waitForMessageNotification", { + type: "Post", + subscribe(parent, args, context) { + return context.pubsub.asyncIterator("messageNotification"); + }, + async resolve(postPromise: Promise) { + return await postPromise; + }, + }); }, }); From 7b6ca2959ed427bdd6d22d0dfb79b44c3977ef10 Mon Sep 17 00:00:00 2001 From: Takumi Hara Date: Mon, 29 Aug 2022 20:28:02 +0900 Subject: [PATCH 2/7] feat: message notification subscribe --- api/src/graphql/Message.ts | 47 ++++++++++++++++++++++++++++---------- 1 file changed, 35 insertions(+), 12 deletions(-) diff --git a/api/src/graphql/Message.ts b/api/src/graphql/Message.ts index 9bafdec..22b0aaf 100644 --- a/api/src/graphql/Message.ts +++ b/api/src/graphql/Message.ts @@ -57,9 +57,7 @@ export const MessageQuery = extendType({ if (!post) { throw new Error("post doesn't exist"); } - if (profileId != post.driverId && profileId != post.navigatorId) { - throw new Error("no right to see messages"); - } + validateOwnership(profileId, post); return await context.prisma.message.findMany({ where: { postId }, @@ -82,6 +80,14 @@ export const MessageMutation = extendType({ const { postId, content } = args; const { profileId } = context.expectUserJoinedCommunity(); + const post = await context.prisma.post.findUnique({ + where: { id: postId }, + }); + if (!post) { + throw new Error("post doesn't exist"); + } + validateOwnership(profileId, post); + const newMessage = await context.prisma.message.create({ data: { post: { @@ -92,13 +98,14 @@ export const MessageMutation = extendType({ connect: { id: profileId }, }, }, - include: { - post: true, - }, }); + // todo: is 'await' necessary? (https://codesandbox.io/s/nexus-example-subscriptions-59kdb?file=/src/schema/index.ts) await context.pubsub.publish(postId.toString(), newMessage); - await context.pubsub.publish("messageNotification", newMessage.post); + await context.pubsub.publish( + "messageNotification:" + profileId.toString(), + post + ); return newMessage; }, }); @@ -117,9 +124,7 @@ export const MessageMutation = extendType({ if (!post) { throw new Error("post doesn't exist"); } - if (profileId != post.driverId && profileId != post.navigatorId) { - throw new Error("no right to see messages"); - } + validateOwnership(profileId, post); const where = { postId, createdById: { not: profileId } }; await context.prisma.message.updateMany({ @@ -144,8 +149,17 @@ export const MessageSubscription = subscriptionType({ args: { postId: nonNull(stringArg()), }, - subscribe(parent, args, context) { + async subscribe(parent, args, context) { const { postId } = args; + const { profileId } = context.expectUserJoinedCommunity(); + const post = await context.prisma.post.findUnique({ + where: { id: postId }, + }); + if (!post) { + throw new Error("post doesn't exist"); + } + validateOwnership(profileId, post); + return context.pubsub.asyncIterator(postId); }, async resolve(messagePromise: Promise) { @@ -156,7 +170,10 @@ export const MessageSubscription = subscriptionType({ t.field("waitForMessageNotification", { type: "Post", subscribe(parent, args, context) { - return context.pubsub.asyncIterator("messageNotification"); + const { profileId } = context.expectUserJoinedCommunity(); + return context.pubsub.asyncIterator( + "messageNotification:" + profileId.toString() + ); }, async resolve(postPromise: Promise) { return await postPromise; @@ -164,3 +181,9 @@ export const MessageSubscription = subscriptionType({ }); }, }); + +const validateOwnership = (profileId: number, post: Post) => { + if (profileId != post.driverId && profileId != post.navigatorId) { + throw new Error("no right to see messages"); + } +}; From e4b04c0111674f4c93825c298318c80c7c26c48e Mon Sep 17 00:00:00 2001 From: Takumi Hara Date: Thu, 1 Sep 2022 20:50:45 +0900 Subject: [PATCH 3/7] chore: more token --- README.md | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 95f6c85..3093436 100644 --- a/README.md +++ b/README.md @@ -53,8 +53,14 @@ $ npx prisma db seed ## テスト環境でのgithubログインの省略 ``` # user1 -eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOjEsImlhdCI6MTY1OTc3MDY1OX0.lpGGDVJ0y-IfAViLy5D8m8GY965ZqTxQ-TZgikrQ5ME +Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOjEsImlhdCI6MTY1OTc3MDY1OX0.lpGGDVJ0y-IfAViLy5D8m8GY965ZqTxQ-TZgikrQ5ME # user2 -eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOjIsImlhdCI6MTY1OTc3MDY1OX0.tzLuKarW0wCOz-hI4xTQ5Q6S08NMZx3VWeKb4-9Cq4U +Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOjIsImlhdCI6MTY1OTc3MDY1OX0.tzLuKarW0wCOz-hI4xTQ5Q6S08NMZx3VWeKb4-9Cq4U + +# profile1 +Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOjEsImNvbW11bml0eUlkIjoiY29tbXVuaXR5SWQxIiwicHJvZmlsZUlkIjoxLCJpYXQiOjE2NjIwMzI3Njh9.jtP9f1HtmTOaoE8o73jyRvsmp7LSaHxJoFv6dPYwD-c + +# profile4 +Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOjIsImNvbW11bml0eUlkIjoiY29tbXVuaXR5SWQyIiwicHJvZmlsZUlkIjo0LCJpYXQiOjE2NjA0NzY0NTB9.5DCEP8ckjnagldmWWEPpUzGcuwNdDHVSa8xlCXXxoxs ``` From dc695b4110d7396ad24137b30d95c01fec73bdf6 Mon Sep 17 00:00:00 2001 From: Takumi Hara Date: Thu, 1 Sep 2022 20:51:04 +0900 Subject: [PATCH 4/7] feat: validation for ws --- api/src/context.ts | 33 +++++++++++++++++++++++++++++++++ api/src/index.ts | 4 ++-- 2 files changed, 35 insertions(+), 2 deletions(-) diff --git a/api/src/context.ts b/api/src/context.ts index 0c5d0aa..e179817 100644 --- a/api/src/context.ts +++ b/api/src/context.ts @@ -62,3 +62,36 @@ export const context = ({ req }: { req: Request }): Context => { }, }; }; + +export const contextForWs = async (ctx: any, msg: any, args: any) => { + const token = + ctx && ctx.connectionParams && ctx.connectionParams.Authorization + ? decodeAuthHeader(ctx.connectionParams.Authorization) + : null; + + return { + prisma, + userId: token?.userId, + profileId: token?.profileId, + communityId: token?.communityId, + pubsub, + expectUserLoggedIn(): UserLoggedInContext { + if (!this.userId) { + throw new Error("You have to log in"); + } + return { prisma: this.prisma, pubsub: this.pubsub, userId: this.userId }; + }, + expectUserJoinedCommunity(): UserJoinedCommunityContext { + if (!this.userId || !this.profileId || !this.communityId) { + throw new Error("You have to join a community"); + } + return { + prisma: this.prisma, + pubsub: this.pubsub, + userId: this.userId, + profileId: this.profileId, + communityId: this.communityId, + }; + }, + }; +}; diff --git a/api/src/index.ts b/api/src/index.ts index ab6468c..ec61966 100644 --- a/api/src/index.ts +++ b/api/src/index.ts @@ -7,7 +7,7 @@ import { WebSocketServer } from "ws"; import { useServer } from "graphql-ws/lib/use/ws"; import { ApolloServerPluginLandingPageLocalDefault } from "apollo-server-core"; -import { context } from "./context"; +import { context, contextForWs } from "./context"; import { schema } from "./schema"; const app = express(); @@ -18,7 +18,7 @@ const wsServer = new WebSocketServer({ path: "/graphql", }); -const serverCleanup = useServer({ schema, context }, wsServer); +const serverCleanup = useServer({ schema, context: contextForWs }, wsServer); const server = new ApolloServer({ schema, From deabb7d073220329723a73f394b9d7e50b2a3f29 Mon Sep 17 00:00:00 2001 From: Takumi Hara Date: Thu, 1 Sep 2022 20:51:50 +0900 Subject: [PATCH 5/7] fix: ignore my message notification --- api/src/graphql/Message.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/api/src/graphql/Message.ts b/api/src/graphql/Message.ts index 22b0aaf..051bcf4 100644 --- a/api/src/graphql/Message.ts +++ b/api/src/graphql/Message.ts @@ -99,11 +99,14 @@ export const MessageMutation = extendType({ }, }, }); + const opponentId = ( + post.driverId == profileId ? post.navigatorId : post.driverId + ) as number; // todo: is 'await' necessary? (https://codesandbox.io/s/nexus-example-subscriptions-59kdb?file=/src/schema/index.ts) await context.pubsub.publish(postId.toString(), newMessage); await context.pubsub.publish( - "messageNotification:" + profileId.toString(), + "messageNotification:" + opponentId.toString(), post ); return newMessage; From faa7afc7c3ec2a84ae90db1bfad5c8a754b7c5af Mon Sep 17 00:00:00 2001 From: Takumi Hara Date: Thu, 1 Sep 2022 20:53:47 +0900 Subject: [PATCH 6/7] fix: order of messagesByPostId --- api/src/graphql/Message.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/api/src/graphql/Message.ts b/api/src/graphql/Message.ts index 051bcf4..6ecdbfa 100644 --- a/api/src/graphql/Message.ts +++ b/api/src/graphql/Message.ts @@ -61,6 +61,7 @@ export const MessageQuery = extendType({ return await context.prisma.message.findMany({ where: { postId }, + orderBy: { id: "asc" }, }); }, }); From 48afd96e3a3560c5123db113fa0aa83f15ca8bba Mon Sep 17 00:00:00 2001 From: Takumi Hara Date: Thu, 1 Sep 2022 20:59:24 +0900 Subject: [PATCH 7/7] fix: nonNull for message subscription --- api/nexus-typegen.ts | 4 ++-- api/schema.graphql | 4 ++-- api/src/graphql/Message.ts | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/api/nexus-typegen.ts b/api/nexus-typegen.ts index 67bf544..7377ae5 100644 --- a/api/nexus-typegen.ts +++ b/api/nexus-typegen.ts @@ -218,8 +218,8 @@ export interface NexusGenFieldTypes { name: string; // String! } Subscription: { // field return type - waitForMessage: NexusGenRootTypes['Message'] | null; // Message - waitForMessageNotification: NexusGenRootTypes['Post'] | null; // Post + waitForMessage: NexusGenRootTypes['Message']; // Message! + waitForMessageNotification: NexusGenRootTypes['Post']; // Post! } User: { // field return type githubBio: string; // String! diff --git a/api/schema.graphql b/api/schema.graphql index c7eb313..8e421a6 100644 --- a/api/schema.graphql +++ b/api/schema.graphql @@ -119,8 +119,8 @@ type Skill { } type Subscription { - waitForMessage(postId: String!): Message - waitForMessageNotification: Post + waitForMessage(postId: String!): Message! + waitForMessageNotification: Post! } type User { diff --git a/api/src/graphql/Message.ts b/api/src/graphql/Message.ts index 6ecdbfa..759a742 100644 --- a/api/src/graphql/Message.ts +++ b/api/src/graphql/Message.ts @@ -148,7 +148,7 @@ export const MessageMutation = extendType({ export const MessageSubscription = subscriptionType({ definition(t) { // todo: should it be non-nullable? - t.field("waitForMessage", { + t.nonNull.field("waitForMessage", { type: "Message", args: { postId: nonNull(stringArg()), @@ -171,7 +171,7 @@ export const MessageSubscription = subscriptionType({ }, }); - t.field("waitForMessageNotification", { + t.nonNull.field("waitForMessageNotification", { type: "Post", subscribe(parent, args, context) { const { profileId } = context.expectUserJoinedCommunity();