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 ``` diff --git a/api/nexus-typegen.ts b/api/nexus-typegen.ts index 724d78e..ec1198c 100644 --- a/api/nexus-typegen.ts +++ b/api/nexus-typegen.ts @@ -227,7 +227,8 @@ export interface NexusGenFieldTypes { name: string; // String! } Subscription: { // field return type - waitForMessage: NexusGenRootTypes['Message'] | null; // Message + waitForMessage: NexusGenRootTypes['Message']; // Message! + waitForMessageNotification: NexusGenRootTypes['Post']; // Post! } User: { // field return type githubBio: string; // String! @@ -348,6 +349,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 1f2498c..f33e671 100644 --- a/api/schema.graphql +++ b/api/schema.graphql @@ -125,7 +125,8 @@ type Skill { } type Subscription { - waitForMessage(postId: String!): Message + waitForMessage(postId: String!): Message! + waitForMessageNotification: Post! } type User { 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/graphql/Message.ts b/api/src/graphql/Message.ts index 1d69908..759a742 100644 --- a/api/src/graphql/Message.ts +++ b/api/src/graphql/Message.ts @@ -57,15 +57,11 @@ 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 }, - orderBy: { - createdAt: "asc", - }, + orderBy: { id: "asc" }, }); }, }); @@ -85,6 +81,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: { @@ -96,8 +100,16 @@ 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:" + opponentId.toString(), + post + ); return newMessage; }, }); @@ -116,9 +128,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({ @@ -138,18 +148,46 @@ 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()), }, - 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) { return await messagePromise; }, }); + + t.nonNull.field("waitForMessageNotification", { + type: "Post", + subscribe(parent, args, context) { + const { profileId } = context.expectUserJoinedCommunity(); + return context.pubsub.asyncIterator( + "messageNotification:" + profileId.toString() + ); + }, + async resolve(postPromise: Promise) { + return await postPromise; + }, + }); }, }); + +const validateOwnership = (profileId: number, post: Post) => { + if (profileId != post.driverId && profileId != post.navigatorId) { + throw new Error("no right to see messages"); + } +}; 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,