Skip to content

Commit

Permalink
feat(partners): setup graphql server
Browse files Browse the repository at this point in the history
  • Loading branch information
joshxfi committed Aug 18, 2024
1 parent 8ce4195 commit cc70adc
Show file tree
Hide file tree
Showing 12 changed files with 280 additions and 32 deletions.
4 changes: 3 additions & 1 deletion apps/partners/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,9 @@
"dev": "next dev",
"build": "next build",
"start": "next start",
"lint": "next lint"
"lint": "next lint",
"gql:generate-persisted": "gql.tada generate-persisted",
"gql:generate-schema": "gql.tada generate-schema http://localhost:3000/api/graphql"
},
"dependencies": {
"@graphql-yoga/plugin-csrf-prevention": "^3.6.2",
Expand Down
97 changes: 97 additions & 0 deletions apps/partners/src/app/api/graphql/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
import { cookies } from "next/headers";
import { createYoga } from "graphql-yoga";
import { getSession, lucia } from "@/lib/auth";
import persistedOperations from "@/persisted-operations.json";
import { partners_schema, initContextCache } from "@umamin/gql";
import { useResponseCache } from "@graphql-yoga/plugin-response-cache";
import { useCSRFPrevention } from "@graphql-yoga/plugin-csrf-prevention";
import { usePersistedOperations } from "@graphql-yoga/plugin-persisted-operations";
import { useDisableIntrospection } from "@graphql-yoga/plugin-disable-introspection";

const { handleRequest } = createYoga({
schema: partners_schema,
context: async () => {
const { session } = await getSession();

return {
...initContextCache(),
userId: session?.userId,
};
},
graphqlEndpoint: "/api/graphql",
graphiql: process.env.NODE_ENV === "development",
fetchAPI: { Response },
cors: {
origin:
process.env.NODE_ENV === "production"
? "https://www.umamin.link"
: "http://localhost:3000",
credentials: true,
methods: ["POST", "GET", "OPTIONS"],
allowedHeaders: ["Content-Type", "Authorization"],
},
plugins: [
useCSRFPrevention({
requestHeaders: ["x-graphql-yoga-csrf"],
}),
useResponseCache({
session: () => cookies().get(lucia.sessionCookieName)?.value,
invalidateViaMutation: false,
scopePerSchemaCoordinate: {
"Query.user": "PRIVATE",
"Query.note": "PRIVATE",
"Query.messages": "PRIVATE",
"Query.messagesFromCursor": "PRIVATE",
},
ttl: 30_000,
ttlPerSchemaCoordinate: {
"Query.notes": 120_000,
"Query.notesFromCursor": 120_000,
"Query.userByUsername": 120_000,
},
}),
useDisableIntrospection({
isDisabled: () => process.env.NODE_ENV === "production",
}),
usePersistedOperations({
allowArbitraryOperations: process.env.NODE_ENV === "development",
customErrors: {
notFound: {
message: "Operation is not found",
extensions: {
http: {
status: 404,
},
},
},
keyNotFound: {
message: "Key is not found",
extensions: {
http: {
status: 404,
},
},
},
persistedQueryOnly: {
message: "Operation is not allowed",
extensions: {
http: {
status: 403,
},
},
},
},
skipDocumentValidation: true,
async getPersistedOperation(key: string) {
// @ts-ignore
return persistedOperations[key];
},
}),
],
});

export {
handleRequest as GET,
handleRequest as POST,
handleRequest as OPTIONS,
};
41 changes: 41 additions & 0 deletions apps/partners/src/graphql-env.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
/* eslint-disable */
/* prettier-ignore */

export type introspection_types = {
'Boolean': unknown;
'CreateMessageInput': { kind: 'INPUT_OBJECT'; name: 'CreateMessageInput'; isOneOf: false; inputFields: [{ name: 'content'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'String'; ofType: null; }; }; defaultValue: null }, { name: 'question'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'String'; ofType: null; }; }; defaultValue: null }, { name: 'receiverId'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'String'; ofType: null; }; }; defaultValue: null }, { name: 'senderId'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; }; defaultValue: null }]; };
'CursorInput': { kind: 'INPUT_OBJECT'; name: 'CursorInput'; isOneOf: false; inputFields: [{ name: 'createdAt'; type: { kind: 'SCALAR'; name: 'Int'; ofType: null; }; defaultValue: null }, { name: 'id'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; }; defaultValue: null }]; };
'ID': unknown;
'Int': unknown;
'Message': { kind: 'OBJECT'; name: 'Message'; fields: { 'content': { name: 'content'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'String'; ofType: null; }; } }; 'createdAt': { name: 'createdAt'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'Int'; ofType: null; }; } }; 'id': { name: 'id'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'ID'; ofType: null; }; } }; 'question': { name: 'question'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'String'; ofType: null; }; } }; 'receiverId': { name: 'receiverId'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'String'; ofType: null; }; } }; 'reply': { name: 'reply'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; } }; 'updatedAt': { name: 'updatedAt'; type: { kind: 'SCALAR'; name: 'Int'; ofType: null; } }; }; };
'MessageCursor': { kind: 'OBJECT'; name: 'MessageCursor'; fields: { 'createdAt': { name: 'createdAt'; type: { kind: 'SCALAR'; name: 'Int'; ofType: null; } }; 'id': { name: 'id'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; } }; }; };
'MessagesFromCursorInput': { kind: 'INPUT_OBJECT'; name: 'MessagesFromCursorInput'; isOneOf: false; inputFields: [{ name: 'cursor'; type: { kind: 'INPUT_OBJECT'; name: 'CursorInput'; ofType: null; }; defaultValue: null }, { name: 'limit'; type: { kind: 'SCALAR'; name: 'Int'; ofType: null; }; defaultValue: null }, { name: 'type'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'String'; ofType: null; }; }; defaultValue: null }]; };
'MessagesWithCursor': { kind: 'OBJECT'; name: 'MessagesWithCursor'; fields: { 'cursor': { name: 'cursor'; type: { kind: 'OBJECT'; name: 'MessageCursor'; ofType: null; } }; 'data': { name: 'data'; type: { kind: 'LIST'; name: never; ofType: { kind: 'NON_NULL'; name: never; ofType: { kind: 'OBJECT'; name: 'Message'; ofType: null; }; }; } }; 'hasMore': { name: 'hasMore'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'Boolean'; ofType: null; }; } }; }; };
'Mutation': { kind: 'OBJECT'; name: 'Mutation'; fields: { 'createMessage': { name: 'createMessage'; type: { kind: 'OBJECT'; name: 'Message'; ofType: null; } }; 'deleteMessage': { name: 'deleteMessage'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; } }; }; };
'Query': { kind: 'OBJECT'; name: 'Query'; fields: { 'messages': { name: 'messages'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'LIST'; name: never; ofType: { kind: 'NON_NULL'; name: never; ofType: { kind: 'OBJECT'; name: 'Message'; ofType: null; }; }; }; } }; 'messagesFromCursor': { name: 'messagesFromCursor'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'OBJECT'; name: 'MessagesWithCursor'; ofType: null; }; } }; }; };
'String': unknown;
};

/** An IntrospectionQuery representation of your schema.
*
* @remarks
* This is an introspection of your schema saved as a file by GraphQLSP.
* It will automatically be used by `gql.tada` to infer the types of your GraphQL documents.
* If you need to reuse this data or update your `scalars`, update `tadaOutputLocation` to
* instead save to a .ts instead of a .d.ts file.
*/
export type introspection = {
name: never;
query: 'Query';
mutation: 'Mutation';
subscription: never;
types: introspection_types;
};

import * as gqlTada from 'gql.tada';

declare module 'gql.tada' {
interface setupSchema {
introspection: introspection
}
}
17 changes: 17 additions & 0 deletions apps/partners/src/lib/gql/client.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { Client, cacheExchange, fetchExchange } from "@urql/core";
import { persistedExchange } from "@urql/exchange-persisted";

const client = new Client({
url: process.env.NEXT_PUBLIC_GQL_URL!,
exchanges: [
cacheExchange,
persistedExchange({
enforcePersistedQueries: true,
enableForMutation: true,
generateHash: (_, document: any) => document.documentId,
}),
fetchExchange,
],
});

export default client;
32 changes: 32 additions & 0 deletions apps/partners/src/lib/gql/rsc.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import { lucia } from "../auth";
import { registerUrql } from "@urql/next/rsc";
import { persistedExchange } from "@urql/exchange-persisted";
import { cacheExchange, createClient, fetchExchange } from "@urql/core";

const getClient = (sessionId?: string) => {
const makeClient = () => {
return createClient({
url: process.env.NEXT_PUBLIC_GQL_URL!,
exchanges: [
cacheExchange,
persistedExchange({
enforcePersistedQueries: true,
enableForMutation: true,
generateHash: (_, document: any) => document.documentId,
}),
fetchExchange,
],
fetchOptions: () => ({
headers: {
cookie: `${lucia.sessionCookieName}=${sessionId}`,
},
}),
});
};

const { getClient: _getClient } = registerUrql(makeClient);

return _getClient();
};

export default getClient;
4 changes: 4 additions & 0 deletions apps/partners/src/persisted-operations.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"f07a17f7e44b839d7a1449115b9810d55447696a558d7416f16dc0b9c978217f": "query ReceivedMessages($type: String!) {\n messages(type: $type, limit: 20) {\n __typename\n id\n createdAt\n ...MessageFragment\n }\n}\n\nfragment MessageFragment on Message {\n id\n question\n content\n reply\n createdAt\n updatedAt\n}",
"10ae521c718fee919520bf95d2cdc74ee1bd0d862d468ca4948ad705bb1e2909": "query ReceivedMessagesFromCursor($input: MessagesFromCursorInput!) {\n messagesFromCursor(input: $input) {\n __typename\n data {\n __typename\n id\n createdAt\n ...MessageFragment\n }\n hasMore\n cursor {\n __typename\n id\n createdAt\n }\n }\n}\n\nfragment MessageFragment on Message {\n id\n question\n content\n reply\n createdAt\n updatedAt\n}"
}
48 changes: 48 additions & 0 deletions apps/partners/src/schema.graphql
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
input CreateMessageInput {
content: String!
question: String!
receiverId: String!
senderId: String
}

input CursorInput {
createdAt: Int
id: String
}

type Message {
content: String!
createdAt: Int!
id: ID!
question: String!
receiverId: String!
reply: String
updatedAt: Int
}

type MessageCursor {
createdAt: Int
id: String
}

input MessagesFromCursorInput {
cursor: CursorInput
limit: Int
type: String!
}

type MessagesWithCursor {
cursor: MessageCursor
data: [Message!]
hasMore: Boolean!
}

type Mutation {
createMessage(input: CreateMessageInput!): Message
deleteMessage(id: String!): String
}

type Query {
messages(limit: Int, type: String!): [Message!]!
messagesFromCursor(input: MessagesFromCursorInput!): MessagesWithCursor!
}
28 changes: 15 additions & 13 deletions apps/partners/tsconfig.json
Original file line number Diff line number Diff line change
@@ -1,26 +1,28 @@
{
"extends": "@umamin/tsconfig/nextjs.json",
"compilerOptions": {
"lib": ["dom", "dom.iterable", "esnext"],
"allowJs": true,
"skipLibCheck": true,
"strict": true,
"noEmit": true,
"esModuleInterop": true,
"module": "esnext",
"moduleResolution": "bundler",
"resolveJsonModule": true,
"isolatedModules": true,
"jsx": "preserve",
"incremental": true,
"plugins": [
{
"name": "next"
},
{
"name": "@0no-co/graphqlsp",
"schema": "./src/schema.graphql",
"tadaOutputLocation": "./src/graphql-env.d.ts",
"tadaPersistedLocation": "./src/persisted-operations.json",
"trackFieldUsage": false
}
],
"paths": {
"@/*": ["./src/*"]
}
},
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
"include": [
"next-env.d.ts",
"next.config.mjs",
"**/*.ts",
"**/*.tsx",
".next/types/**/*.ts"
],
"exclude": ["node_modules"]
}
2 changes: 1 addition & 1 deletion packages/gql/src/builder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ const builder = new SchemaBuilder<{
AuthScopes: {
authenticated: boolean;
};
SubGraphs: "www" | "social";
SubGraphs: "www" | "social" | "partners";
Objects: {
User: SelectUser & {
accounts?: SelectAccount[] | null;
Expand Down
7 changes: 5 additions & 2 deletions packages/gql/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,11 @@ import builder from "./builder";
import { rateLimitDirective } from "graphql-rate-limit-directive";

builder.queryType({
subGraphs: ["www", "social"],
subGraphs: ["www", "social", "partners"],
});

builder.mutationType({
subGraphs: ["www", "social"],
subGraphs: ["www", "social", "partners"],
});

import "./models/user";
Expand All @@ -23,3 +23,6 @@ export const www_schema = rateLimitDirectiveTransformer(
export const social_schema = rateLimitDirectiveTransformer(
builder.toSchema({ subGraph: "social" })
);
export const partners_schema = rateLimitDirectiveTransformer(
builder.toSchema({ subGraph: "partners" })
);
Loading

0 comments on commit cc70adc

Please sign in to comment.