Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

メッセージの通知 subscription #200

Open
wants to merge 8 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 8 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
```
4 changes: 3 additions & 1 deletion api/nexus-typegen.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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!
Expand Down Expand Up @@ -348,6 +349,7 @@ export interface NexusGenFieldTypeNames {
}
Subscription: { // field return type name
waitForMessage: 'Message'
waitForMessageNotification: 'Post'
}
User: { // field return type name
githubBio: 'String'
Expand Down
3 changes: 2 additions & 1 deletion api/schema.graphql
Original file line number Diff line number Diff line change
Expand Up @@ -125,7 +125,8 @@ type Skill {
}

type Subscription {
waitForMessage(postId: String!): Message
waitForMessage(postId: String!): Message!
waitForMessageNotification: Post!
}

type User {
Expand Down
33 changes: 33 additions & 0 deletions api/src/context.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,3 +62,36 @@ export const context = ({ req }: { req: Request }): Context => {
},
};
};

export const contextForWs = async (ctx: any, msg: any, args: any) => {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

wsって何の略ですか?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

web socket ですかね。

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ですです

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,
};
},
};
};
60 changes: 49 additions & 11 deletions api/src/graphql/Message.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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" },
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

createdAtが直感的かなと思ったのですが、変更した理由はなんです?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

indexつけるのidになるので、そっちの方が効率良いかと思いました!(ですよね?) auto incrementなので、これで順序変わることはないはずですし!

});
},
});
Expand All @@ -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: {
Expand All @@ -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;
},
});
Expand All @@ -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({
Expand All @@ -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<Message>) {
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<Post>) {
return await postPromise;
},
});
},
});

const validateOwnership = (profileId: number, post: Post) => {
if (profileId != post.driverId && profileId != post.navigatorId) {
throw new Error("no right to see messages");
}
Comment on lines +189 to +192
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

good!

};
4 changes: 2 additions & 2 deletions api/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand All @@ -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,
Expand Down