Feature/Use fastify instead of express (#1)
- Replaced Apollo Server with Mercurius for GraphQL API - Updated resolvers to use Mercurius-compatible GraphQL implementation - Migrated from Express to Fastify for server framework - Improved error handling with GraphQL error extensions - Added Zod for environment variable validation - Updated Prisma schema and migrations - Configured CORS and WebSocket subscriptions - Simplified GraphQL schema and resolver structure - Enhanced type safety and code organization - Replaced Apollo Server with Mercurius for GraphQL API - Updated resolvers to use Mercurius-compatible GraphQL implementation - Migrated from Express to Fastify for server framework - Improved error handling with GraphQL error extensions - Added Zod for environment variable validation - Updated Prisma schema and migrations - Configured CORS and WebSocket subscriptions - Simplified GraphQL schema and resolver structure - Enhanced type safety and code organization Reviewed-on: #1 Co-authored-by: Jusemon <juansmm@outlook.com> Co-committed-by: Jusemon <juansmm@outlook.com>
This commit is contained in:
parent
b4e5a04126
commit
6214b503bc
47 changed files with 4968 additions and 5424 deletions
562
apps/api/src/generated/graphql.ts
Normal file
562
apps/api/src/generated/graphql.ts
Normal file
|
@ -0,0 +1,562 @@
|
|||
import type {
|
||||
GraphQLResolveInfo,
|
||||
GraphQLScalarType,
|
||||
GraphQLScalarTypeConfig,
|
||||
} from "graphql";
|
||||
import type { MercuriusContext } from "mercurius";
|
||||
export type Maybe<T> = T | null;
|
||||
export type InputMaybe<T> = Maybe<T>;
|
||||
export type Exact<T extends { [key: string]: unknown }> = {
|
||||
[K in keyof T]: T[K];
|
||||
};
|
||||
export type MakeOptional<T, K extends keyof T> = Omit<T, K> & {
|
||||
[SubKey in K]?: Maybe<T[SubKey]>;
|
||||
};
|
||||
export type MakeMaybe<T, K extends keyof T> = Omit<T, K> & {
|
||||
[SubKey in K]: Maybe<T[SubKey]>;
|
||||
};
|
||||
export type ResolverFn<TResult, TParent, TContext, TArgs> = (
|
||||
parent: TParent,
|
||||
args: TArgs,
|
||||
context: TContext,
|
||||
info: GraphQLResolveInfo,
|
||||
) =>
|
||||
| Promise<import("mercurius-codegen").DeepPartial<TResult>>
|
||||
| import("mercurius-codegen").DeepPartial<TResult>;
|
||||
export type RequireFields<T, K extends keyof T> = Omit<T, K> & {
|
||||
[P in K]-?: NonNullable<T[P]>;
|
||||
};
|
||||
/** All built-in and custom scalars, mapped to their actual values */
|
||||
export type Scalars = {
|
||||
ID: string;
|
||||
String: string;
|
||||
Boolean: boolean;
|
||||
Int: number;
|
||||
Float: number;
|
||||
DateTime: Date;
|
||||
_FieldSet: any;
|
||||
};
|
||||
|
||||
export type User = {
|
||||
__typename?: "User";
|
||||
id: Scalars["ID"];
|
||||
email: Scalars["String"];
|
||||
username: Scalars["String"];
|
||||
createdAt: Scalars["DateTime"];
|
||||
updatedAt?: Maybe<Scalars["DateTime"]>;
|
||||
messages?: Maybe<Array<Message>>;
|
||||
rooms?: Maybe<Array<Room>>;
|
||||
ownedRooms?: Maybe<Array<Room>>;
|
||||
};
|
||||
|
||||
export type Room = {
|
||||
__typename?: "Room";
|
||||
id: Scalars["ID"];
|
||||
name: Scalars["String"];
|
||||
description?: Maybe<Scalars["String"]>;
|
||||
isPrivate: Scalars["Boolean"];
|
||||
createdAt: Scalars["DateTime"];
|
||||
updatedAt?: Maybe<Scalars["DateTime"]>;
|
||||
messages?: Maybe<Array<Message>>;
|
||||
members?: Maybe<Array<User>>;
|
||||
owner?: Maybe<User>;
|
||||
};
|
||||
|
||||
export type Message = {
|
||||
__typename?: "Message";
|
||||
id: Scalars["ID"];
|
||||
content: Scalars["String"];
|
||||
createdAt: Scalars["DateTime"];
|
||||
updatedAt?: Maybe<Scalars["DateTime"]>;
|
||||
userId: Scalars["ID"];
|
||||
user?: Maybe<User>;
|
||||
roomId: Scalars["ID"];
|
||||
room?: Maybe<Room>;
|
||||
};
|
||||
|
||||
export type AuthPayload = {
|
||||
__typename?: "AuthPayload";
|
||||
token: Scalars["String"];
|
||||
user: User;
|
||||
};
|
||||
|
||||
export type Query = {
|
||||
__typename?: "Query";
|
||||
me?: Maybe<User>;
|
||||
users: Array<User>;
|
||||
user?: Maybe<User>;
|
||||
rooms: Array<Room>;
|
||||
room?: Maybe<Room>;
|
||||
messages: Array<Message>;
|
||||
};
|
||||
|
||||
export type QueryuserArgs = {
|
||||
id: Scalars["ID"];
|
||||
};
|
||||
|
||||
export type QueryroomArgs = {
|
||||
id: Scalars["ID"];
|
||||
};
|
||||
|
||||
export type QuerymessagesArgs = {
|
||||
roomId: Scalars["ID"];
|
||||
};
|
||||
|
||||
export type Mutation = {
|
||||
__typename?: "Mutation";
|
||||
register: AuthPayload;
|
||||
login: AuthPayload;
|
||||
createRoom: Room;
|
||||
joinRoom: Room;
|
||||
leaveRoom: Scalars["Boolean"];
|
||||
sendMessage: Message;
|
||||
};
|
||||
|
||||
export type MutationregisterArgs = {
|
||||
email: Scalars["String"];
|
||||
username: Scalars["String"];
|
||||
password: Scalars["String"];
|
||||
};
|
||||
|
||||
export type MutationloginArgs = {
|
||||
email: Scalars["String"];
|
||||
password: Scalars["String"];
|
||||
};
|
||||
|
||||
export type MutationcreateRoomArgs = {
|
||||
name: Scalars["String"];
|
||||
description?: InputMaybe<Scalars["String"]>;
|
||||
isPrivate?: InputMaybe<Scalars["Boolean"]>;
|
||||
};
|
||||
|
||||
export type MutationjoinRoomArgs = {
|
||||
roomId: Scalars["ID"];
|
||||
};
|
||||
|
||||
export type MutationleaveRoomArgs = {
|
||||
roomId: Scalars["ID"];
|
||||
};
|
||||
|
||||
export type MutationsendMessageArgs = {
|
||||
content: Scalars["String"];
|
||||
roomId: Scalars["ID"];
|
||||
};
|
||||
|
||||
export type Subscription = {
|
||||
__typename?: "Subscription";
|
||||
messageAdded: Message;
|
||||
roomAdded: Room;
|
||||
roomUpdated: Room;
|
||||
};
|
||||
|
||||
export type SubscriptionmessageAddedArgs = {
|
||||
roomId: Scalars["ID"];
|
||||
};
|
||||
|
||||
export type ResolverTypeWrapper<T> = Promise<T> | T;
|
||||
|
||||
export type ResolverWithResolve<TResult, TParent, TContext, TArgs> = {
|
||||
resolve: ResolverFn<TResult, TParent, TContext, TArgs>;
|
||||
};
|
||||
export type Resolver<TResult, TParent = {}, TContext = {}, TArgs = {}> =
|
||||
| ResolverFn<TResult, TParent, TContext, TArgs>
|
||||
| ResolverWithResolve<TResult, TParent, TContext, TArgs>;
|
||||
|
||||
export type SubscriptionSubscribeFn<TResult, TParent, TContext, TArgs> = (
|
||||
parent: TParent,
|
||||
args: TArgs,
|
||||
context: TContext,
|
||||
info: GraphQLResolveInfo,
|
||||
) => AsyncIterable<TResult> | Promise<AsyncIterable<TResult>>;
|
||||
|
||||
export type SubscriptionResolveFn<TResult, TParent, TContext, TArgs> = (
|
||||
parent: TParent,
|
||||
args: TArgs,
|
||||
context: TContext,
|
||||
info: GraphQLResolveInfo,
|
||||
) => TResult | Promise<TResult>;
|
||||
|
||||
export interface SubscriptionSubscriberObject<
|
||||
TResult,
|
||||
TKey extends string,
|
||||
TParent,
|
||||
TContext,
|
||||
TArgs,
|
||||
> {
|
||||
subscribe: SubscriptionSubscribeFn<
|
||||
{ [key in TKey]: TResult },
|
||||
TParent,
|
||||
TContext,
|
||||
TArgs
|
||||
>;
|
||||
resolve?: SubscriptionResolveFn<
|
||||
TResult,
|
||||
{ [key in TKey]: TResult },
|
||||
TContext,
|
||||
TArgs
|
||||
>;
|
||||
}
|
||||
|
||||
export interface SubscriptionResolverObject<TResult, TParent, TContext, TArgs> {
|
||||
subscribe: SubscriptionSubscribeFn<any, TParent, TContext, TArgs>;
|
||||
resolve: SubscriptionResolveFn<TResult, any, TContext, TArgs>;
|
||||
}
|
||||
|
||||
export type SubscriptionObject<
|
||||
TResult,
|
||||
TKey extends string,
|
||||
TParent,
|
||||
TContext,
|
||||
TArgs,
|
||||
> =
|
||||
| SubscriptionSubscriberObject<TResult, TKey, TParent, TContext, TArgs>
|
||||
| SubscriptionResolverObject<TResult, TParent, TContext, TArgs>;
|
||||
|
||||
export type SubscriptionResolver<
|
||||
TResult,
|
||||
TKey extends string,
|
||||
TParent = {},
|
||||
TContext = {},
|
||||
TArgs = {},
|
||||
> =
|
||||
| ((
|
||||
...args: any[]
|
||||
) => SubscriptionObject<TResult, TKey, TParent, TContext, TArgs>)
|
||||
| SubscriptionObject<TResult, TKey, TParent, TContext, TArgs>;
|
||||
|
||||
export type TypeResolveFn<TTypes, TParent = {}, TContext = {}> = (
|
||||
parent: TParent,
|
||||
context: TContext,
|
||||
info: GraphQLResolveInfo,
|
||||
) => Maybe<TTypes> | Promise<Maybe<TTypes>>;
|
||||
|
||||
export type IsTypeOfResolverFn<T = {}, TContext = {}> = (
|
||||
obj: T,
|
||||
context: TContext,
|
||||
info: GraphQLResolveInfo,
|
||||
) => boolean | Promise<boolean>;
|
||||
|
||||
export type NextResolverFn<T> = () => Promise<T>;
|
||||
|
||||
export type DirectiveResolverFn<
|
||||
TResult = {},
|
||||
TParent = {},
|
||||
TContext = {},
|
||||
TArgs = {},
|
||||
> = (
|
||||
next: NextResolverFn<TResult>,
|
||||
parent: TParent,
|
||||
args: TArgs,
|
||||
context: TContext,
|
||||
info: GraphQLResolveInfo,
|
||||
) => TResult | Promise<TResult>;
|
||||
|
||||
/** Mapping between all available schema types and the resolvers types */
|
||||
export type ResolversTypes = {
|
||||
DateTime: ResolverTypeWrapper<Scalars["DateTime"]>;
|
||||
User: ResolverTypeWrapper<User>;
|
||||
ID: ResolverTypeWrapper<Scalars["ID"]>;
|
||||
String: ResolverTypeWrapper<Scalars["String"]>;
|
||||
Room: ResolverTypeWrapper<Room>;
|
||||
Boolean: ResolverTypeWrapper<Scalars["Boolean"]>;
|
||||
Message: ResolverTypeWrapper<Message>;
|
||||
AuthPayload: ResolverTypeWrapper<AuthPayload>;
|
||||
Query: ResolverTypeWrapper<{}>;
|
||||
Mutation: ResolverTypeWrapper<{}>;
|
||||
Subscription: ResolverTypeWrapper<{}>;
|
||||
};
|
||||
|
||||
/** Mapping between all available schema types and the resolvers parents */
|
||||
export type ResolversParentTypes = {
|
||||
DateTime: Scalars["DateTime"];
|
||||
User: User;
|
||||
ID: Scalars["ID"];
|
||||
String: Scalars["String"];
|
||||
Room: Room;
|
||||
Boolean: Scalars["Boolean"];
|
||||
Message: Message;
|
||||
AuthPayload: AuthPayload;
|
||||
Query: {};
|
||||
Mutation: {};
|
||||
Subscription: {};
|
||||
};
|
||||
|
||||
export interface DateTimeScalarConfig
|
||||
extends GraphQLScalarTypeConfig<ResolversTypes["DateTime"], any> {
|
||||
name: "DateTime";
|
||||
}
|
||||
|
||||
export type UserResolvers<
|
||||
ContextType = MercuriusContext,
|
||||
ParentType extends
|
||||
ResolversParentTypes["User"] = ResolversParentTypes["User"],
|
||||
> = {
|
||||
id?: Resolver<ResolversTypes["ID"], ParentType, ContextType>;
|
||||
email?: Resolver<ResolversTypes["String"], ParentType, ContextType>;
|
||||
username?: Resolver<ResolversTypes["String"], ParentType, ContextType>;
|
||||
createdAt?: Resolver<ResolversTypes["DateTime"], ParentType, ContextType>;
|
||||
updatedAt?: Resolver<
|
||||
Maybe<ResolversTypes["DateTime"]>,
|
||||
ParentType,
|
||||
ContextType
|
||||
>;
|
||||
messages?: Resolver<
|
||||
Maybe<Array<ResolversTypes["Message"]>>,
|
||||
ParentType,
|
||||
ContextType
|
||||
>;
|
||||
rooms?: Resolver<
|
||||
Maybe<Array<ResolversTypes["Room"]>>,
|
||||
ParentType,
|
||||
ContextType
|
||||
>;
|
||||
ownedRooms?: Resolver<
|
||||
Maybe<Array<ResolversTypes["Room"]>>,
|
||||
ParentType,
|
||||
ContextType
|
||||
>;
|
||||
isTypeOf?: IsTypeOfResolverFn<ParentType, ContextType>;
|
||||
};
|
||||
|
||||
export type RoomResolvers<
|
||||
ContextType = MercuriusContext,
|
||||
ParentType extends
|
||||
ResolversParentTypes["Room"] = ResolversParentTypes["Room"],
|
||||
> = {
|
||||
id?: Resolver<ResolversTypes["ID"], ParentType, ContextType>;
|
||||
name?: Resolver<ResolversTypes["String"], ParentType, ContextType>;
|
||||
description?: Resolver<
|
||||
Maybe<ResolversTypes["String"]>,
|
||||
ParentType,
|
||||
ContextType
|
||||
>;
|
||||
isPrivate?: Resolver<ResolversTypes["Boolean"], ParentType, ContextType>;
|
||||
createdAt?: Resolver<ResolversTypes["DateTime"], ParentType, ContextType>;
|
||||
updatedAt?: Resolver<
|
||||
Maybe<ResolversTypes["DateTime"]>,
|
||||
ParentType,
|
||||
ContextType
|
||||
>;
|
||||
messages?: Resolver<
|
||||
Maybe<Array<ResolversTypes["Message"]>>,
|
||||
ParentType,
|
||||
ContextType
|
||||
>;
|
||||
members?: Resolver<
|
||||
Maybe<Array<ResolversTypes["User"]>>,
|
||||
ParentType,
|
||||
ContextType
|
||||
>;
|
||||
owner?: Resolver<Maybe<ResolversTypes["User"]>, ParentType, ContextType>;
|
||||
isTypeOf?: IsTypeOfResolverFn<ParentType, ContextType>;
|
||||
};
|
||||
|
||||
export type MessageResolvers<
|
||||
ContextType = MercuriusContext,
|
||||
ParentType extends
|
||||
ResolversParentTypes["Message"] = ResolversParentTypes["Message"],
|
||||
> = {
|
||||
id?: Resolver<ResolversTypes["ID"], ParentType, ContextType>;
|
||||
content?: Resolver<ResolversTypes["String"], ParentType, ContextType>;
|
||||
createdAt?: Resolver<ResolversTypes["DateTime"], ParentType, ContextType>;
|
||||
updatedAt?: Resolver<
|
||||
Maybe<ResolversTypes["DateTime"]>,
|
||||
ParentType,
|
||||
ContextType
|
||||
>;
|
||||
userId?: Resolver<ResolversTypes["ID"], ParentType, ContextType>;
|
||||
user?: Resolver<Maybe<ResolversTypes["User"]>, ParentType, ContextType>;
|
||||
roomId?: Resolver<ResolversTypes["ID"], ParentType, ContextType>;
|
||||
room?: Resolver<Maybe<ResolversTypes["Room"]>, ParentType, ContextType>;
|
||||
isTypeOf?: IsTypeOfResolverFn<ParentType, ContextType>;
|
||||
};
|
||||
|
||||
export type AuthPayloadResolvers<
|
||||
ContextType = MercuriusContext,
|
||||
ParentType extends
|
||||
ResolversParentTypes["AuthPayload"] = ResolversParentTypes["AuthPayload"],
|
||||
> = {
|
||||
token?: Resolver<ResolversTypes["String"], ParentType, ContextType>;
|
||||
user?: Resolver<ResolversTypes["User"], ParentType, ContextType>;
|
||||
isTypeOf?: IsTypeOfResolverFn<ParentType, ContextType>;
|
||||
};
|
||||
|
||||
export type QueryResolvers<
|
||||
ContextType = MercuriusContext,
|
||||
ParentType extends
|
||||
ResolversParentTypes["Query"] = ResolversParentTypes["Query"],
|
||||
> = {
|
||||
me?: Resolver<Maybe<ResolversTypes["User"]>, ParentType, ContextType>;
|
||||
users?: Resolver<Array<ResolversTypes["User"]>, ParentType, ContextType>;
|
||||
user?: Resolver<
|
||||
Maybe<ResolversTypes["User"]>,
|
||||
ParentType,
|
||||
ContextType,
|
||||
RequireFields<QueryuserArgs, "id">
|
||||
>;
|
||||
rooms?: Resolver<Array<ResolversTypes["Room"]>, ParentType, ContextType>;
|
||||
room?: Resolver<
|
||||
Maybe<ResolversTypes["Room"]>,
|
||||
ParentType,
|
||||
ContextType,
|
||||
RequireFields<QueryroomArgs, "id">
|
||||
>;
|
||||
messages?: Resolver<
|
||||
Array<ResolversTypes["Message"]>,
|
||||
ParentType,
|
||||
ContextType,
|
||||
RequireFields<QuerymessagesArgs, "roomId">
|
||||
>;
|
||||
};
|
||||
|
||||
export type MutationResolvers<
|
||||
ContextType = MercuriusContext,
|
||||
ParentType extends
|
||||
ResolversParentTypes["Mutation"] = ResolversParentTypes["Mutation"],
|
||||
> = {
|
||||
register?: Resolver<
|
||||
ResolversTypes["AuthPayload"],
|
||||
ParentType,
|
||||
ContextType,
|
||||
RequireFields<MutationregisterArgs, "email" | "username" | "password">
|
||||
>;
|
||||
login?: Resolver<
|
||||
ResolversTypes["AuthPayload"],
|
||||
ParentType,
|
||||
ContextType,
|
||||
RequireFields<MutationloginArgs, "email" | "password">
|
||||
>;
|
||||
createRoom?: Resolver<
|
||||
ResolversTypes["Room"],
|
||||
ParentType,
|
||||
ContextType,
|
||||
RequireFields<MutationcreateRoomArgs, "name">
|
||||
>;
|
||||
joinRoom?: Resolver<
|
||||
ResolversTypes["Room"],
|
||||
ParentType,
|
||||
ContextType,
|
||||
RequireFields<MutationjoinRoomArgs, "roomId">
|
||||
>;
|
||||
leaveRoom?: Resolver<
|
||||
ResolversTypes["Boolean"],
|
||||
ParentType,
|
||||
ContextType,
|
||||
RequireFields<MutationleaveRoomArgs, "roomId">
|
||||
>;
|
||||
sendMessage?: Resolver<
|
||||
ResolversTypes["Message"],
|
||||
ParentType,
|
||||
ContextType,
|
||||
RequireFields<MutationsendMessageArgs, "content" | "roomId">
|
||||
>;
|
||||
};
|
||||
|
||||
export type SubscriptionResolvers<
|
||||
ContextType = MercuriusContext,
|
||||
ParentType extends
|
||||
ResolversParentTypes["Subscription"] = ResolversParentTypes["Subscription"],
|
||||
> = {
|
||||
messageAdded?: SubscriptionResolver<
|
||||
ResolversTypes["Message"],
|
||||
"messageAdded",
|
||||
ParentType,
|
||||
ContextType,
|
||||
RequireFields<SubscriptionmessageAddedArgs, "roomId">
|
||||
>;
|
||||
roomAdded?: SubscriptionResolver<
|
||||
ResolversTypes["Room"],
|
||||
"roomAdded",
|
||||
ParentType,
|
||||
ContextType
|
||||
>;
|
||||
roomUpdated?: SubscriptionResolver<
|
||||
ResolversTypes["Room"],
|
||||
"roomUpdated",
|
||||
ParentType,
|
||||
ContextType
|
||||
>;
|
||||
};
|
||||
|
||||
export type Resolvers<ContextType = MercuriusContext> = {
|
||||
DateTime?: GraphQLScalarType;
|
||||
User?: UserResolvers<ContextType>;
|
||||
Room?: RoomResolvers<ContextType>;
|
||||
Message?: MessageResolvers<ContextType>;
|
||||
AuthPayload?: AuthPayloadResolvers<ContextType>;
|
||||
Query?: QueryResolvers<ContextType>;
|
||||
Mutation?: MutationResolvers<ContextType>;
|
||||
Subscription?: SubscriptionResolvers<ContextType>;
|
||||
};
|
||||
|
||||
export type Loader<TReturn, TObj, TParams, TContext> = (
|
||||
queries: Array<{
|
||||
obj: TObj;
|
||||
params: TParams;
|
||||
}>,
|
||||
context: TContext & {
|
||||
reply: import("fastify").FastifyReply;
|
||||
},
|
||||
) => Promise<Array<import("mercurius-codegen").DeepPartial<TReturn>>>;
|
||||
export type LoaderResolver<TReturn, TObj, TParams, TContext> =
|
||||
| Loader<TReturn, TObj, TParams, TContext>
|
||||
| {
|
||||
loader: Loader<TReturn, TObj, TParams, TContext>;
|
||||
opts?: {
|
||||
cache?: boolean;
|
||||
};
|
||||
};
|
||||
export interface Loaders<
|
||||
TContext = import("mercurius").MercuriusContext & {
|
||||
reply: import("fastify").FastifyReply;
|
||||
},
|
||||
> {
|
||||
User?: {
|
||||
id?: LoaderResolver<Scalars["ID"], User, {}, TContext>;
|
||||
email?: LoaderResolver<Scalars["String"], User, {}, TContext>;
|
||||
username?: LoaderResolver<Scalars["String"], User, {}, TContext>;
|
||||
createdAt?: LoaderResolver<Scalars["DateTime"], User, {}, TContext>;
|
||||
updatedAt?: LoaderResolver<Maybe<Scalars["DateTime"]>, User, {}, TContext>;
|
||||
messages?: LoaderResolver<Maybe<Array<Message>>, User, {}, TContext>;
|
||||
rooms?: LoaderResolver<Maybe<Array<Room>>, User, {}, TContext>;
|
||||
ownedRooms?: LoaderResolver<Maybe<Array<Room>>, User, {}, TContext>;
|
||||
};
|
||||
|
||||
Room?: {
|
||||
id?: LoaderResolver<Scalars["ID"], Room, {}, TContext>;
|
||||
name?: LoaderResolver<Scalars["String"], Room, {}, TContext>;
|
||||
description?: LoaderResolver<Maybe<Scalars["String"]>, Room, {}, TContext>;
|
||||
isPrivate?: LoaderResolver<Scalars["Boolean"], Room, {}, TContext>;
|
||||
createdAt?: LoaderResolver<Scalars["DateTime"], Room, {}, TContext>;
|
||||
updatedAt?: LoaderResolver<Maybe<Scalars["DateTime"]>, Room, {}, TContext>;
|
||||
messages?: LoaderResolver<Maybe<Array<Message>>, Room, {}, TContext>;
|
||||
members?: LoaderResolver<Maybe<Array<User>>, Room, {}, TContext>;
|
||||
owner?: LoaderResolver<Maybe<User>, Room, {}, TContext>;
|
||||
};
|
||||
|
||||
Message?: {
|
||||
id?: LoaderResolver<Scalars["ID"], Message, {}, TContext>;
|
||||
content?: LoaderResolver<Scalars["String"], Message, {}, TContext>;
|
||||
createdAt?: LoaderResolver<Scalars["DateTime"], Message, {}, TContext>;
|
||||
updatedAt?: LoaderResolver<
|
||||
Maybe<Scalars["DateTime"]>,
|
||||
Message,
|
||||
{},
|
||||
TContext
|
||||
>;
|
||||
userId?: LoaderResolver<Scalars["ID"], Message, {}, TContext>;
|
||||
user?: LoaderResolver<Maybe<User>, Message, {}, TContext>;
|
||||
roomId?: LoaderResolver<Scalars["ID"], Message, {}, TContext>;
|
||||
room?: LoaderResolver<Maybe<Room>, Message, {}, TContext>;
|
||||
};
|
||||
|
||||
AuthPayload?: {
|
||||
token?: LoaderResolver<Scalars["String"], AuthPayload, {}, TContext>;
|
||||
user?: LoaderResolver<User, AuthPayload, {}, TContext>;
|
||||
};
|
||||
}
|
||||
declare module "mercurius" {
|
||||
interface IResolvers
|
||||
extends Resolvers<import("mercurius").MercuriusContext> {}
|
||||
interface MercuriusLoaders extends Loaders {}
|
||||
}
|
|
@ -1,93 +1,84 @@
|
|||
import { ApolloServer } from '@apollo/server';
|
||||
import { expressMiddleware } from '@apollo/server/express4';
|
||||
import { ApolloServerPluginDrainHttpServer } from '@apollo/server/plugin/drainHttpServer';
|
||||
import { makeExecutableSchema } from '@graphql-tools/schema';
|
||||
import { createServer } from 'http';
|
||||
import express from 'express';
|
||||
import { WebSocketServer } from 'ws';
|
||||
import { useServer } from 'graphql-ws/lib/use/ws';
|
||||
import cors from 'cors';
|
||||
import { PrismaClient } from '@prisma/client';
|
||||
import { typeDefs } from './schema/typeDefs';
|
||||
import { resolvers } from './resolvers';
|
||||
import dotenv from 'dotenv';
|
||||
import './types';
|
||||
import fastify, { FastifyRequest } from 'fastify';
|
||||
import mercurius from 'mercurius';
|
||||
import mercuriusCodegen from 'mercurius-codegen';
|
||||
import schema from './schema';
|
||||
import { resolvers } from './resolvers';
|
||||
import { PrismaClient } from '@prisma/client';
|
||||
import fastifyCors from '@fastify/cors';
|
||||
import { z } from 'zod';
|
||||
|
||||
// Load environment variables
|
||||
dotenv.config();
|
||||
dotenv.config({ path: '../../.env' });
|
||||
|
||||
const envs = z
|
||||
.object({
|
||||
ALLOWED_ORIGINS: z.string().default('http://localhost:5173'),
|
||||
})
|
||||
.transform((env) => {
|
||||
return {
|
||||
allowedOrigins: env.ALLOWED_ORIGINS.split(','),
|
||||
};
|
||||
})
|
||||
.parse(process.env);
|
||||
|
||||
console.log(envs);
|
||||
|
||||
const app = fastify({
|
||||
logger: true,
|
||||
exposeHeadRoutes: true,
|
||||
});
|
||||
|
||||
// Create Prisma client
|
||||
const prisma = new PrismaClient();
|
||||
|
||||
async function startServer() {
|
||||
// Create Express app and HTTP server
|
||||
const app = express();
|
||||
const httpServer = createServer(app);
|
||||
const context = async (req: FastifyRequest) => {
|
||||
const userId = (req.headers['user-id'] as string) || null;
|
||||
return {
|
||||
prisma,
|
||||
userId,
|
||||
};
|
||||
};
|
||||
|
||||
// Create WebSocket server
|
||||
const wsServer = new WebSocketServer({
|
||||
server: httpServer,
|
||||
path: '/graphql',
|
||||
});
|
||||
|
||||
// Create schema
|
||||
const schema = makeExecutableSchema({ typeDefs, resolvers });
|
||||
|
||||
// Set up WebSocket server
|
||||
const serverCleanup = useServer({ schema }, wsServer);
|
||||
|
||||
// Create Apollo Server
|
||||
const server = new ApolloServer({
|
||||
schema,
|
||||
plugins: [
|
||||
// Proper shutdown for the HTTP server
|
||||
ApolloServerPluginDrainHttpServer({ httpServer }),
|
||||
// Proper shutdown for the WebSocket server
|
||||
{
|
||||
async serverWillStart() {
|
||||
return {
|
||||
async drainServer() {
|
||||
await serverCleanup.dispose();
|
||||
},
|
||||
};
|
||||
},
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
// Start Apollo Server
|
||||
await server.start();
|
||||
|
||||
// Apply middleware
|
||||
app.use(
|
||||
'/graphql',
|
||||
cors<cors.CorsRequest>(),
|
||||
express.json(),
|
||||
expressMiddleware(server, {
|
||||
context: async ({ req }) => {
|
||||
// In a real application, you would extract the user ID from a JWT token
|
||||
// const token = req.headers.authorization || '';
|
||||
// const userId = getUserIdFromToken(token);
|
||||
|
||||
// For demo purposes, we'll use a dummy user ID
|
||||
const userId = (req.headers['user-id'] as string) || null;
|
||||
|
||||
return {
|
||||
prisma,
|
||||
userId,
|
||||
};
|
||||
},
|
||||
})
|
||||
);
|
||||
|
||||
// Start the server
|
||||
const PORT = process.env.PORT || 4000;
|
||||
httpServer.listen(PORT, () => {
|
||||
console.log(`🚀 Server ready at http://localhost:${PORT}/graphql`);
|
||||
console.log(`🚀 Subscriptions ready at ws://localhost:${PORT}/graphql`);
|
||||
});
|
||||
}
|
||||
|
||||
// Handle errors
|
||||
startServer().catch((err) => {
|
||||
console.error('Error starting server:', err);
|
||||
app.register(fastifyCors, {
|
||||
origin: (origin, callback) => {
|
||||
if (envs.allowedOrigins.includes(origin || '*'))
|
||||
return callback(null, true);
|
||||
return callback(new Error('Not allowed'), false);
|
||||
},
|
||||
methods: ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS'],
|
||||
allowedHeaders: ['Content-Type', 'Authorization', 'user-id'],
|
||||
});
|
||||
|
||||
app.register(mercurius, {
|
||||
schema,
|
||||
subscription: true,
|
||||
graphiql: true,
|
||||
context,
|
||||
});
|
||||
|
||||
app.register(async ({ graphql }) => {
|
||||
resolvers.forEach((resolver) => {
|
||||
graphql.defineResolvers(resolver);
|
||||
});
|
||||
});
|
||||
|
||||
app.get('/ping', async () => {
|
||||
return 'pong\n';
|
||||
});
|
||||
|
||||
mercuriusCodegen(app, {
|
||||
targetPath: './src/generated/graphql.ts',
|
||||
codegenConfig: {
|
||||
scalars: {
|
||||
DateTime: 'Date',
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
app.listen({ port: 8080 }, (err, address) => {
|
||||
if (err) {
|
||||
console.error(err);
|
||||
process.exit(1);
|
||||
}
|
||||
console.log(`Server listening at ${address}`);
|
||||
});
|
||||
|
|
|
@ -1,23 +1,10 @@
|
|||
import { userResolvers } from './user';
|
||||
import { roomResolvers } from './room';
|
||||
import { messageResolvers } from './message';
|
||||
import { IResolvers } from 'mercurius';
|
||||
|
||||
export const resolvers = {
|
||||
Query: {
|
||||
...userResolvers.Query,
|
||||
...roomResolvers.Query,
|
||||
...messageResolvers.Query,
|
||||
},
|
||||
Mutation: {
|
||||
...userResolvers.Mutation,
|
||||
...roomResolvers.Mutation,
|
||||
...messageResolvers.Mutation,
|
||||
},
|
||||
Subscription: {
|
||||
...messageResolvers.Subscription,
|
||||
...roomResolvers.Subscription,
|
||||
},
|
||||
User: userResolvers.User,
|
||||
Room: roomResolvers.Room,
|
||||
Message: messageResolvers.Message,
|
||||
};
|
||||
export const resolvers: IResolvers[] = [
|
||||
userResolvers,
|
||||
roomResolvers,
|
||||
messageResolvers,
|
||||
];
|
||||
|
|
|
@ -1,17 +1,17 @@
|
|||
import { PrismaClient } from '@prisma/client';
|
||||
import { AuthenticationError, ForbiddenError } from 'apollo-server-express';
|
||||
import { PubSub, withFilter } from 'graphql-subscriptions';
|
||||
|
||||
const prisma = new PrismaClient();
|
||||
const pubsub = new PubSub();
|
||||
import { GraphQLError } from 'graphql';
|
||||
import { IResolvers, withFilter } from 'mercurius';
|
||||
|
||||
export const MESSAGE_ADDED = 'MESSAGE_ADDED';
|
||||
|
||||
export const messageResolvers = {
|
||||
export const messageResolvers: IResolvers = {
|
||||
Query: {
|
||||
messages: async (_: any, { roomId }: { roomId: string }, context: any) => {
|
||||
if (!context.userId) {
|
||||
throw new AuthenticationError('You must be logged in to view messages');
|
||||
messages: async (_, { roomId }, { prisma, userId }) => {
|
||||
if (!userId) {
|
||||
throw new GraphQLError('You must be logged in to view messages', {
|
||||
extensions: {
|
||||
code: 'UNAUTHENTICATED',
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
// Check if user is a member of the room
|
||||
|
@ -21,14 +21,22 @@ export const messageResolvers = {
|
|||
});
|
||||
|
||||
if (!room) {
|
||||
throw new ForbiddenError('Room not found');
|
||||
throw new GraphQLError('Room not found', {
|
||||
extensions: {
|
||||
code: 'NOT_FOUND',
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
const isMember = room.members.some(
|
||||
(member: { id: string }) => member.id === context.userId
|
||||
(member: { id: string }) => member.id === userId
|
||||
);
|
||||
if (!isMember) {
|
||||
throw new ForbiddenError('You are not a member of this room');
|
||||
throw new GraphQLError('You are not a member of this room', {
|
||||
extensions: {
|
||||
code: 'FORBIDDEN',
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
return prisma.message.findMany({
|
||||
|
@ -41,12 +49,14 @@ export const messageResolvers = {
|
|||
sendMessage: async (
|
||||
_: any,
|
||||
{ content, roomId }: { content: string; roomId: string },
|
||||
context: any
|
||||
{ prisma, userId, pubsub }
|
||||
) => {
|
||||
if (!context.userId) {
|
||||
throw new AuthenticationError(
|
||||
'You must be logged in to send a message'
|
||||
);
|
||||
if (!userId) {
|
||||
throw new GraphQLError('You must be logged in to send a message', {
|
||||
extensions: {
|
||||
code: 'UNAUTHENTICATED',
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
// Check if user is a member of the room
|
||||
|
@ -56,21 +66,29 @@ export const messageResolvers = {
|
|||
});
|
||||
|
||||
if (!room) {
|
||||
throw new ForbiddenError('Room not found');
|
||||
throw new GraphQLError('Room not found', {
|
||||
extensions: {
|
||||
code: 'NOT_FOUND',
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
const isMember = room.members.some(
|
||||
(member: { id: string }) => member.id === context.userId
|
||||
(member: { id: string }) => member.id === userId
|
||||
);
|
||||
if (!isMember) {
|
||||
throw new ForbiddenError('You are not a member of this room');
|
||||
throw new GraphQLError('You are not a member of this room', {
|
||||
extensions: {
|
||||
code: 'FORBIDDEN',
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
const message = await prisma.message.create({
|
||||
data: {
|
||||
content,
|
||||
user: {
|
||||
connect: { id: context.userId },
|
||||
connect: { id: userId },
|
||||
},
|
||||
room: {
|
||||
connect: { id: roomId },
|
||||
|
@ -82,7 +100,10 @@ export const messageResolvers = {
|
|||
},
|
||||
});
|
||||
|
||||
pubsub.publish(MESSAGE_ADDED, { messageAdded: message, roomId });
|
||||
pubsub.publish({
|
||||
topic: MESSAGE_ADDED,
|
||||
payload: { messageAdded: message, roomId },
|
||||
});
|
||||
|
||||
return message;
|
||||
},
|
||||
|
@ -90,7 +111,7 @@ export const messageResolvers = {
|
|||
Subscription: {
|
||||
messageAdded: {
|
||||
subscribe: withFilter(
|
||||
() => pubsub.asyncIterator([MESSAGE_ADDED]),
|
||||
(_, __, { pubsub }) => pubsub.subscribe([MESSAGE_ADDED]),
|
||||
(payload, variables) => {
|
||||
return payload.roomId === variables.roomId;
|
||||
}
|
||||
|
@ -98,12 +119,18 @@ export const messageResolvers = {
|
|||
},
|
||||
},
|
||||
Message: {
|
||||
user: async (parent: any) => {
|
||||
user: async (parent, _, { prisma }) => {
|
||||
if (parent.user) {
|
||||
return parent.user;
|
||||
}
|
||||
return prisma.user.findUnique({
|
||||
where: { id: parent.userId },
|
||||
});
|
||||
},
|
||||
room: async (parent: any) => {
|
||||
room: async (parent, _, { prisma }) => {
|
||||
if (parent.room) {
|
||||
return parent.room;
|
||||
}
|
||||
return prisma.room.findUnique({
|
||||
where: { id: parent.roomId },
|
||||
});
|
||||
|
|
|
@ -1,21 +1,17 @@
|
|||
import { PrismaClient } from '@prisma/client';
|
||||
import { AuthenticationError, ForbiddenError } from 'apollo-server-express';
|
||||
import { PubSub } from 'graphql-subscriptions';
|
||||
|
||||
const prisma = new PrismaClient();
|
||||
const pubsub = new PubSub();
|
||||
import { GraphQLError } from 'graphql';
|
||||
import { IResolvers } from 'mercurius';
|
||||
|
||||
export const ROOM_ADDED = 'ROOM_ADDED';
|
||||
export const ROOM_UPDATED = 'ROOM_UPDATED';
|
||||
|
||||
export const roomResolvers = {
|
||||
export const roomResolvers: IResolvers = {
|
||||
Query: {
|
||||
rooms: async () => {
|
||||
rooms: async (_, __, { prisma }) => {
|
||||
return prisma.room.findMany({
|
||||
where: { isPrivate: false },
|
||||
});
|
||||
},
|
||||
room: async (_: any, { id }: { id: string }) => {
|
||||
room: async (_: any, { id }: { id: string }, { prisma }) => {
|
||||
return prisma.room.findUnique({
|
||||
where: { id },
|
||||
});
|
||||
|
@ -23,39 +19,50 @@ export const roomResolvers = {
|
|||
},
|
||||
Mutation: {
|
||||
createRoom: async (
|
||||
_: any,
|
||||
{
|
||||
name,
|
||||
description,
|
||||
isPrivate = false,
|
||||
}: { name: string; description?: string; isPrivate?: boolean },
|
||||
context: any
|
||||
_,
|
||||
{ name, description, isPrivate },
|
||||
{ prisma, userId, pubsub }
|
||||
) => {
|
||||
if (!context.userId) {
|
||||
throw new AuthenticationError('You must be logged in to create a room');
|
||||
if (!userId) {
|
||||
throw new GraphQLError('You must be logged in to create a room', {
|
||||
extensions: {
|
||||
code: 'UNAUTHENTICATED',
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
const room = await prisma.room.create({
|
||||
data: {
|
||||
name,
|
||||
description,
|
||||
isPrivate,
|
||||
isPrivate: isPrivate ?? false,
|
||||
owner: {
|
||||
connect: { id: context.userId },
|
||||
connect: { id: userId },
|
||||
},
|
||||
members: {
|
||||
connect: { id: context.userId },
|
||||
connect: { id: userId },
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
pubsub.publish(ROOM_ADDED, { roomAdded: room });
|
||||
pubsub.publish({
|
||||
topic: ROOM_ADDED,
|
||||
payload: { roomAdded: room },
|
||||
});
|
||||
|
||||
return room;
|
||||
},
|
||||
joinRoom: async (_: any, { roomId }: { roomId: string }, context: any) => {
|
||||
if (!context.userId) {
|
||||
throw new AuthenticationError('You must be logged in to join a room');
|
||||
joinRoom: async (
|
||||
_,
|
||||
{ roomId }: { roomId: string },
|
||||
{ prisma, userId, pubsub }
|
||||
) => {
|
||||
if (!userId) {
|
||||
throw new GraphQLError('You must be logged in to join a room', {
|
||||
extensions: {
|
||||
code: 'UNAUTHENTICATED',
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
const room = await prisma.room.findUnique({
|
||||
|
@ -64,19 +71,28 @@ export const roomResolvers = {
|
|||
});
|
||||
|
||||
if (!room) {
|
||||
throw new ForbiddenError('Room not found');
|
||||
throw new GraphQLError('Room not found', {
|
||||
extensions: {
|
||||
code: 'NOT_FOUND',
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
if (room.isPrivate) {
|
||||
// In a real application, you would check if the user has been invited
|
||||
throw new ForbiddenError(
|
||||
'You cannot join a private room without an invitation'
|
||||
throw new GraphQLError(
|
||||
'You cannot join a private room without an invitation',
|
||||
{
|
||||
extensions: {
|
||||
code: 'FORBIDDEN',
|
||||
},
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
// Check if user is already a member
|
||||
const isMember = room.members.some(
|
||||
(member: { id: string }) => member.id === context.userId
|
||||
(member: { id: string }) => member.id === userId
|
||||
);
|
||||
if (isMember) {
|
||||
return room;
|
||||
|
@ -86,20 +102,31 @@ export const roomResolvers = {
|
|||
where: { id: roomId },
|
||||
data: {
|
||||
members: {
|
||||
connect: { id: context.userId },
|
||||
connect: { id: userId },
|
||||
},
|
||||
},
|
||||
include: { members: true },
|
||||
});
|
||||
|
||||
// Publish room updated event
|
||||
pubsub.publish(ROOM_UPDATED, { roomUpdated: updatedRoom });
|
||||
pubsub.publish({
|
||||
topic: ROOM_UPDATED,
|
||||
payload: { roomUpdated: updatedRoom },
|
||||
});
|
||||
|
||||
return updatedRoom;
|
||||
},
|
||||
leaveRoom: async (_: any, { roomId }: { roomId: string }, context: any) => {
|
||||
if (!context.userId) {
|
||||
throw new AuthenticationError('You must be logged in to leave a room');
|
||||
leaveRoom: async (
|
||||
_,
|
||||
{ roomId }: { roomId: string },
|
||||
{ prisma, userId, pubsub }
|
||||
) => {
|
||||
if (!userId) {
|
||||
throw new GraphQLError('You must be logged in to leave a room', {
|
||||
extensions: {
|
||||
code: 'UNAUTHENTICATED',
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
const room = await prisma.room.findUnique({
|
||||
|
@ -108,68 +135,87 @@ export const roomResolvers = {
|
|||
});
|
||||
|
||||
if (!room) {
|
||||
throw new ForbiddenError('Room not found');
|
||||
throw new GraphQLError('Room not found', {
|
||||
extensions: {
|
||||
code: 'NOT_FOUND',
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
// Check if user is a member
|
||||
const isMember = room.members.some(
|
||||
(member: { id: string }) => member.id === context.userId
|
||||
(member: { id: string }) => member.id === userId
|
||||
);
|
||||
if (!isMember) {
|
||||
throw new ForbiddenError('You are not a member of this room');
|
||||
throw new GraphQLError('You are not a member of this room', {
|
||||
extensions: {
|
||||
code: 'FORBIDDEN',
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
// If user is the owner, they cannot leave
|
||||
if (room.ownerId === context.userId) {
|
||||
throw new ForbiddenError('You cannot leave a room you own');
|
||||
if (room.ownerId === userId) {
|
||||
throw new GraphQLError('You cannot leave a room you own', {
|
||||
extensions: {
|
||||
code: 'FORBIDDEN',
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
const updatedRoom = await prisma.room.update({
|
||||
where: { id: roomId },
|
||||
data: {
|
||||
members: {
|
||||
disconnect: { id: context.userId },
|
||||
disconnect: { id: userId },
|
||||
},
|
||||
},
|
||||
include: { members: true },
|
||||
});
|
||||
|
||||
// Publish room updated event
|
||||
pubsub.publish(ROOM_UPDATED, { roomUpdated: updatedRoom });
|
||||
pubsub.publish({
|
||||
topic: ROOM_UPDATED,
|
||||
payload: { roomUpdated: updatedRoom },
|
||||
});
|
||||
|
||||
return true;
|
||||
},
|
||||
},
|
||||
Subscription: {
|
||||
roomAdded: {
|
||||
subscribe: () => pubsub.asyncIterator([ROOM_ADDED]),
|
||||
subscribe: (_, __, { pubsub }) => pubsub.subscribe(ROOM_ADDED),
|
||||
},
|
||||
roomUpdated: {
|
||||
subscribe: () => pubsub.asyncIterator([ROOM_UPDATED]),
|
||||
subscribe: (_, __, { pubsub }) => pubsub.subscribe(ROOM_UPDATED),
|
||||
},
|
||||
},
|
||||
Room: {
|
||||
messages: async (parent: any) => {
|
||||
return prisma.message.findMany({
|
||||
where: { roomId: parent.id },
|
||||
orderBy: { createdAt: 'asc' },
|
||||
});
|
||||
},
|
||||
members: async (parent: any) => {
|
||||
return prisma.user.findMany({
|
||||
where: {
|
||||
rooms: {
|
||||
some: {
|
||||
id: parent.id,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
},
|
||||
owner: async (parent: any) => {
|
||||
return prisma.user.findUnique({
|
||||
where: { id: parent.ownerId },
|
||||
});
|
||||
},
|
||||
messages: async (parent: any, _, { prisma }) =>
|
||||
parent.messages
|
||||
? parent.messages
|
||||
: prisma.room
|
||||
.findUnique({
|
||||
where: { id: parent.id },
|
||||
})
|
||||
.messages({
|
||||
orderBy: { createdAt: 'asc' },
|
||||
}),
|
||||
members: async (parent, _, { prisma }) =>
|
||||
parent.members
|
||||
? parent.members
|
||||
: prisma.room
|
||||
.findUnique({
|
||||
where: { id: parent.id },
|
||||
})
|
||||
.members(),
|
||||
owner: async (parent, _, { prisma }) =>
|
||||
parent.owner
|
||||
? parent.owner
|
||||
: prisma.room
|
||||
.findUnique({
|
||||
where: { id: parent.id },
|
||||
})
|
||||
.owner(),
|
||||
},
|
||||
};
|
||||
|
|
|
@ -1,27 +1,26 @@
|
|||
import { PrismaClient } from '@prisma/client';
|
||||
import { AuthenticationError, UserInputError } from 'apollo-server-express';
|
||||
import { GraphQLError } from 'graphql';
|
||||
import { IResolvers } from 'mercurius';
|
||||
|
||||
// In a real application, you would use bcrypt for password hashing
|
||||
// import bcrypt from 'bcryptjs';
|
||||
// import jwt from 'jsonwebtoken';
|
||||
|
||||
const prisma = new PrismaClient();
|
||||
|
||||
export const userResolvers = {
|
||||
export const userResolvers: IResolvers = {
|
||||
Query: {
|
||||
me: async (_: any, __: any, context: any) => {
|
||||
me: async (_, __, { prisma, userId }) => {
|
||||
// In a real application, you would get the user from the context
|
||||
// which would be set by an authentication middleware
|
||||
if (!context.userId) {
|
||||
if (!userId) {
|
||||
return null;
|
||||
}
|
||||
return prisma.user.findUnique({
|
||||
where: { id: context.userId },
|
||||
where: { id: userId },
|
||||
});
|
||||
},
|
||||
users: async () => {
|
||||
users: async (_, __, { prisma }) => {
|
||||
return prisma.user.findMany();
|
||||
},
|
||||
user: async (_: any, { id }: { id: string }) => {
|
||||
user: async (_, { id }: { id: string }, { prisma }) => {
|
||||
return prisma.user.findUnique({
|
||||
where: { id },
|
||||
});
|
||||
|
@ -34,7 +33,8 @@ export const userResolvers = {
|
|||
email,
|
||||
username,
|
||||
password,
|
||||
}: { email: string; username: string; password: string }
|
||||
}: { email: string; username: string; password: string },
|
||||
{ prisma }
|
||||
) => {
|
||||
// Check if user already exists
|
||||
const existingUser = await prisma.user.findFirst({
|
||||
|
@ -44,7 +44,11 @@ export const userResolvers = {
|
|||
});
|
||||
|
||||
if (existingUser) {
|
||||
throw new UserInputError('User already exists');
|
||||
throw new GraphQLError('User already exists', {
|
||||
extensions: {
|
||||
code: 'USER_ALREADY_EXISTS',
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
// In a real application, you would hash the password
|
||||
|
@ -68,14 +72,19 @@ export const userResolvers = {
|
|||
},
|
||||
login: async (
|
||||
_: any,
|
||||
{ email, password }: { email: string; password: string }
|
||||
{ email, password }: { email: string; password: string },
|
||||
{ prisma }
|
||||
) => {
|
||||
const user = await prisma.user.findUnique({
|
||||
where: { email },
|
||||
});
|
||||
|
||||
if (!user) {
|
||||
throw new AuthenticationError('Invalid credentials');
|
||||
throw new GraphQLError('Invalid credentials', {
|
||||
extensions: {
|
||||
code: 'INVALID_CREDENTIALS',
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
// In a real application, you would verify the password
|
||||
|
@ -83,7 +92,11 @@ export const userResolvers = {
|
|||
const valid = password === user.password; // This is just for demo purposes
|
||||
|
||||
if (!valid) {
|
||||
throw new AuthenticationError('Invalid credentials');
|
||||
throw new GraphQLError('Invalid credentials', {
|
||||
extensions: {
|
||||
code: 'INVALID_CREDENTIALS',
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
// In a real application, you would generate a JWT token
|
||||
|
@ -96,26 +109,23 @@ export const userResolvers = {
|
|||
},
|
||||
},
|
||||
User: {
|
||||
messages: async (parent: any) => {
|
||||
return prisma.message.findMany({
|
||||
where: { userId: parent.id },
|
||||
});
|
||||
},
|
||||
rooms: async (parent: any) => {
|
||||
return prisma.room.findMany({
|
||||
messages: async (user, _, { prisma }) =>
|
||||
prisma.user
|
||||
.findUnique({
|
||||
where: { id: user.id },
|
||||
})
|
||||
.messages(),
|
||||
rooms: async (user, _, { prisma }) =>
|
||||
prisma.room.findMany({
|
||||
where: {
|
||||
members: {
|
||||
some: {
|
||||
id: parent.id,
|
||||
},
|
||||
},
|
||||
OR: [{ ownerId: user.id }, { members: { some: { id: user.id } } }],
|
||||
},
|
||||
});
|
||||
},
|
||||
ownedRooms: async (parent: any) => {
|
||||
return prisma.room.findMany({
|
||||
where: { ownerId: parent.id },
|
||||
});
|
||||
},
|
||||
}),
|
||||
ownedRooms: async (user, _, { prisma }) =>
|
||||
prisma.user
|
||||
.findUnique({
|
||||
where: { id: user.id },
|
||||
})
|
||||
.rooms(),
|
||||
},
|
||||
};
|
||||
|
|
|
@ -1,12 +1,14 @@
|
|||
import { gql } from 'apollo-server-express';
|
||||
import { gql } from 'mercurius-codegen';
|
||||
|
||||
export default gql`
|
||||
scalar DateTime
|
||||
|
||||
export const typeDefs = gql`
|
||||
type User {
|
||||
id: ID!
|
||||
email: String!
|
||||
username: String!
|
||||
createdAt: String!
|
||||
updatedAt: String!
|
||||
createdAt: DateTime!
|
||||
updatedAt: DateTime
|
||||
messages: [Message!]
|
||||
rooms: [Room!]
|
||||
ownedRooms: [Room!]
|
||||
|
@ -17,20 +19,22 @@ export const typeDefs = gql`
|
|||
name: String!
|
||||
description: String
|
||||
isPrivate: Boolean!
|
||||
createdAt: String!
|
||||
updatedAt: String!
|
||||
createdAt: DateTime!
|
||||
updatedAt: DateTime
|
||||
messages: [Message!]
|
||||
members: [User!]
|
||||
owner: User!
|
||||
owner: User
|
||||
}
|
||||
|
||||
type Message {
|
||||
id: ID!
|
||||
content: String!
|
||||
createdAt: String!
|
||||
updatedAt: String!
|
||||
user: User!
|
||||
room: Room!
|
||||
createdAt: DateTime!
|
||||
updatedAt: DateTime
|
||||
userId: ID!
|
||||
user: User
|
||||
roomId: ID!
|
||||
room: Room
|
||||
}
|
||||
|
||||
type AuthPayload {
|
8
apps/api/src/types.ts
Normal file
8
apps/api/src/types.ts
Normal file
|
@ -0,0 +1,8 @@
|
|||
import type { PrismaClient } from '@prisma/client';
|
||||
|
||||
declare module 'mercurius' {
|
||||
interface MercuriusContext {
|
||||
prisma: PrismaClient;
|
||||
userId: string | null;
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue