Skip to content

Commit

Permalink
Merge pull request #37 from preetjdp/Implement-Dataloader
Browse files Browse the repository at this point in the history
Implement dataloader
  • Loading branch information
preetjdp authored May 21, 2020
2 parents a00c460 + d3168e3 commit 4b74e1f
Show file tree
Hide file tree
Showing 12 changed files with 138 additions and 60 deletions.
22 changes: 16 additions & 6 deletions server/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 3 additions & 1 deletion server/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,15 @@
"apollo-server": "^2.13.1",
"axios": "^0.19.2",
"class-validator": "^0.12.2",
"dataloader": "^2.0.0",
"dotenv": "^8.2.0",
"express": "^4.17.1",
"firebase-admin": "^8.12.1",
"graphql": "^15.0.0",
"graphql-firestore-subscriptions": "^1.0.1",
"reflect-metadata": "^0.1.13",
"type-graphql": "^1.0.0-rc.1"
"type-graphql": "^1.0.0-rc.2",
"typedi": "^0.8.0"
},
"devDependencies": {
"@types/express": "^4.17.6",
Expand Down
24 changes: 21 additions & 3 deletions server/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ import "./utils/envConfig"


import { generateSchema } from "./schema"
import { ApplicationContext } from "./utils/appContext"
import { Container } from "typedi"

const main = async () => {
const schema = await generateSchema()
Expand All @@ -12,11 +14,27 @@ const main = async () => {
playground: true,
tracing: true,
context: ({ req, connection }) => {
const requestId = Math.floor(Math.random() * Number.MAX_SAFE_INTEGER);
console.log('Creating Container', requestId)
let context: ApplicationContext = {
req,
requestId
}
if (connection) {
return connection.context
context = { ...context, ...connection.context }
}
return { req }
}
return context
},
plugins: [
{
requestDidStart: () => ({
willSendResponse(requestContext) {
console.log('Disposing Container', requestContext.context.requestId)
Container.reset(requestContext.context.requestId);
},
}),
},
],
});

server.listen({ port: process.env.PORT || 4000 }, () =>
Expand Down
20 changes: 15 additions & 5 deletions server/src/modules/Owe/OweResolver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ import { Owe, OweState } from "../../models/Owe";
import { User } from "../../models/User";
import { UserResolver } from "../User/UserResolver";
import { DocumentReference } from "@google-cloud/firestore"
import { RequestContainer, UserDataLoader } from "../User/userResolver/userLoader";
import { mapUserSnapshot } from "../User/userResolver/userSnapshotMap";

@Resolver(Owe)
export class OweResolver {
Expand Down Expand Up @@ -30,20 +32,28 @@ export class OweResolver {
@FieldResolver(() => User, {
name: "issuedBy",
})
async issuedByFieldResolver(@Root() owe: Owe) {
async issuedByFieldResolver(@Root() owe: Owe, @RequestContainer() userDataLoader: UserDataLoader) {
const oweRef = owe.documenmentRef
const issuedByRef = oweRef.parent.parent
const issedByUserId = issuedByRef!.id
const issuedByUser = await new UserResolver().getUser(issedByUserId)
const issuedByUserSnapshot = await userDataLoader.load(issedByUserId)
if (!issuedByUserSnapshot) {
throw Error("User Snapshot from loader is Null in oweResolver")
}
const issuedByUser = mapUserSnapshot(issuedByUserSnapshot)
return issuedByUser
}

@FieldResolver(() => User, {
name: "issuedTo",
})
async issuedToFieldResolver(@Root() owe: Owe) {
async issuedToFieldResolver(@Root() owe: Owe, @RequestContainer() userDataLoader: UserDataLoader) {
const userId: string = owe.issuedToID
const user = await new UserResolver().getUser(userId)
return user
const issuedToUserSnapshot = await userDataLoader.load(userId)
if (!issuedToUserSnapshot) {
throw Error("User Snapshot from loader is Null in oweResolver")
}
const issuedToUser = mapUserSnapshot(issuedToUserSnapshot)
return issuedToUser
}
}
13 changes: 9 additions & 4 deletions server/src/modules/User/MeResolver.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
import { Resolver, Query, Authorized, Ctx, Subscription, Publisher, PubSub, Root } from "type-graphql";
import { Resolver, Query, Authorized, Ctx, Subscription, Publisher, PubSub, Root, Info } from "type-graphql";
import { ApplicationContext } from "../../utils/appContext";
import { firestore } from "../../db/firebase";
import { User } from "../../models/User";
import { Timestamp } from "@google-cloud/firestore";
import { UserResolver } from "./UserResolver";
import { userTopicGenerator } from "./userResolver/userTopic";
import { RequestContainer, UserDataLoader } from "./userResolver/userLoader";
import { mapUserSnapshot } from "./userResolver/userSnapshotMap";


@Resolver()
Expand All @@ -13,10 +15,13 @@ export class MeResolver {
@Query(() => User, {
name: "Me"
})
async getMe(@Ctx() context: ApplicationContext): Promise<User> {
console.log(context.req.headers.authorization + "wpwza")
async getMe(@Ctx() context: ApplicationContext, @RequestContainer() userDataLoader: UserDataLoader): Promise<User> {
const userId = context.req.headers.authorization!
const user = await new UserResolver().getUser(userId)
const userSnapshot = await userDataLoader.load(userId)
if (!userSnapshot) {
throw Error("User Snapshot from loader is Null in MeResolver")
}
const user = mapUserSnapshot(userSnapshot)
return user
}

Expand Down
34 changes: 7 additions & 27 deletions server/src/modules/User/UserResolver.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
import { firestore } from "../../db/firebase"
import { Query, Resolver, FieldResolver, Root, Arg, Authorized, Int } from "type-graphql"
import { User } from "../../models/User"
import { DocumentReference, Timestamp } from "@google-cloud/firestore"
import { DocumentReference, Timestamp, DocumentSnapshot } from "@google-cloud/firestore"
import { Owe, OweState } from "../../models/Owe"
import { RequestContainer, UserDataLoader } from "./userResolver/userLoader"
import { mapUserSnapshot } from "./userResolver/userSnapshotMap"

@Resolver(User)
export class UserResolver {
Expand All @@ -13,16 +15,7 @@ export class UserResolver {
const usersRef = firestore.collection('users')
const usersSnapshot = await usersRef.get()
const users = usersSnapshot.docs.map((userSnapshot) => {
const userData = userSnapshot.data()
const created: Timestamp = userData!.created;
const user: User = {
id: userSnapshot.id,
name: userData.name,
image: userData.image,
mobileNo: userData.mobile_no,
fcmToken: userData.fcm_token,
created: created.toDate()
}
const user: User = mapUserSnapshot(userSnapshot)
return user
})
return users
Expand All @@ -31,22 +24,9 @@ export class UserResolver {
@Query(() => User, {
nullable: true
})
async getUser(@Arg("id") id: string): Promise<User> {
const userRef = firestore.collection('users').doc(id)
const userSnapshot = await userRef.get()
const userData = userSnapshot.data()
if (!userSnapshot.exists) {
throw Error("User Does Not Exist")
}
const created: Timestamp = userData!.created
const user: User = {
id: userSnapshot.id,
name: userData!.name,
image: userData!.image,
mobileNo: userData!.mobile_no,
fcmToken: userData!.fcm_token,
created: created.toDate()
}
async getUser(@Arg("id") id: string, @RequestContainer() userDataLoader: UserDataLoader): Promise<User> {
const userSnapshot = await userDataLoader.load(id) as DocumentSnapshot
const user: User = mapUserSnapshot(userSnapshot)
return user
}

Expand Down
10 changes: 8 additions & 2 deletions server/src/modules/User/updateUserResolver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,16 @@ import { User } from "../../models/User";
import { UpdateUserInputType } from "./updateUser/updateUserInputType";
import { firestore } from "../../db/firebase";
import { UserResolver } from "./UserResolver"
import { RequestContainer, UserDataLoader } from "./userResolver/userLoader";
import { mapUserSnapshot } from "./userResolver/userSnapshotMap";

@Resolver(User)
export class UpdateUserResolver {
@Authorized()
@Mutation(() => User, {
description: "This Mutation gives one the ability to update values of a `User`. Currently only supports updating the name and the fcm_token",
})
async updateUser(@Arg("data") data: UpdateUserInputType) {
async updateUser(@Arg("data") data: UpdateUserInputType, @RequestContainer() userDataLoader: UserDataLoader) {
const userId = data.id
const userRef = firestore.collection('users').doc(userId)
try {
Expand All @@ -23,7 +25,11 @@ export class UpdateUserResolver {
...(fcmToken && { fcm_token: fcmToken }),
...updateData
})
let user = await new UserResolver().getUser(userId)
let userSnapshot = await userDataLoader.load(userId)
if(!userSnapshot) {
throw Error("User Snapshot from loader is Null in updateResolver")
}
let user = mapUserSnapshot(userSnapshot)
return user
} catch (e) {
throw e
Expand Down
31 changes: 31 additions & 0 deletions server/src/modules/User/userResolver/userLoader.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import Container, { Service } from "typedi";
import DataLoader from "dataloader"
import { firestore } from "../../../db/firebase";
import { FieldPath, DocumentSnapshot, DocumentData } from "@google-cloud/firestore";
import { createParamDecorator } from "type-graphql";
import { ApplicationContext } from "../../../utils/appContext";

@Service()
export class UserDataLoader extends DataLoader<string, DocumentSnapshot | undefined> {
constructor() {
super(async (ids) => {
console.log("Asked For ID's ", ids)
const usersPromise: Promise<DocumentSnapshot<DocumentData>>[] = ids.map(async (id) => {
return await firestore.collection('users').doc(id).get()
})
const users = await Promise.all(usersPromise)
console.log("Dataloader Response", users.length)
return ids.map((id) => users.find((user) => user.id === id));
});
}
}

export function RequestContainer(): ParameterDecorator {
return function (target: Object, propertyName: string | symbol, index: number) {
return createParamDecorator<ApplicationContext>(({ context }) => {
const paramtypes = Reflect.getMetadata('design:paramtypes', target, propertyName);
const requestContainer = Container.of(context.requestId);
return requestContainer.get(paramtypes[index]);
})(target, propertyName, index);
};
}
20 changes: 20 additions & 0 deletions server/src/modules/User/userResolver/userSnapshotMap.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { DocumentSnapshot, Timestamp } from "@google-cloud/firestore";
import { User } from "../../../models/User";

type userSnapshotMapType = (snapshot: DocumentSnapshot) => User;

export const mapUserSnapshot: userSnapshotMapType = snapshot => {
const userData = snapshot.data()
if (!snapshot.exists) {
throw Error("User Does Not Exist")
}
const created: Timestamp = userData!.created
return {
id: snapshot.id,
name: userData!.name,
image: userData!.image,
mobileNo: userData!.mobile_no,
fcmToken: userData!.fcm_token,
created: created.toDate()
}
}
12 changes: 2 additions & 10 deletions server/src/modules/User/userResolver/userTopic.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,21 +2,13 @@ import { PubSubFire } from "../../../db/pubSubFire"
import { Timestamp } from "@google-cloud/firestore"
import { User } from "../../../models/User"
import { firestore } from "../../../db/firebase"
import { mapUserSnapshot } from "./userSnapshotMap"

const userTopicGenerator = (id: string) => {
try {
PubSubFire.registerHandler(`user_subscription_${id}`.toString(), broadcast => {
return firestore.collection('users').doc(id).onSnapshot((snapshot) => {
const userData = snapshot.data()
const created: Timestamp = userData!.created
const user: User = {
id: snapshot.id,
name: userData!.name,
image: userData!.image,
fcmToken: userData!.fcm_token,
mobileNo: userData!.mobile_no,
created: created.toDate()
}
const user: User = mapUserSnapshot(snapshot)
broadcast(user)
})
})
Expand Down
7 changes: 5 additions & 2 deletions server/src/schema.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,19 @@
import { buildSchema } from "type-graphql"
import { buildSchema, ResolverData } from "type-graphql"
import "reflect-metadata"

import { customAuthChecker } from "./utils/authChecker"
import { PubSubFire } from "./db/pubSubFire"
import { ApplicationContext } from "./utils/appContext"
import { Container } from "typedi"


const generateSchema = async () => {
return await buildSchema({
resolvers: [__dirname + '/modules/**/*.{ts,js}'],
authChecker: customAuthChecker,
dateScalarMode: "timestamp",
pubSub: PubSubFire
pubSub: PubSubFire,
container: (({ context }: ResolverData<ApplicationContext>) => Container.of(context.requestId))
})
}

Expand Down
1 change: 1 addition & 0 deletions server/src/utils/appContext.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,5 @@ import { Request } from 'express'

export interface ApplicationContext {
req: Request;
requestId: number
}

0 comments on commit 4b74e1f

Please sign in to comment.