Repository: Efrei-M1-DEV1/GraphQL-Project-Social-Network
Pull request #59: feat: add Comment type and createComment mutation to GraphQL schema
Author: login: Farid-Efrei
Labels: name: enhancement
description: New feature or request
name: backend
description: Related to the backend
Created: 2025-04-08T10:45:33Z | Merged: 2025-04-09T08:44:03Z

This pull request includes several important changes to the GraphQL-Project-Social-Network repository, focusing on adding issue templates, a code of conduct, contributing guidelines, and security policies, as well as enhancing the project setup and configuration.

### Documentation and Community Guidelines:

* **Bug Report Template**: Added a template for reporting bugs to help improve the project. This includes sections for description, steps to reproduce, expected behavior, actual behavior, environment, additional context, and related issues. (.github/ISSUE_TEMPLATE/bug_report.md)
* **Feature Request Template**: Added a template for suggesting new features. This includes sections for description, problem or use case, proposed solution, alternatives considered, additional context, and related issues. (.github/ISSUE_TEMPLATE/feature_request.md)
* **Code of Conduct**: Implemented the Contributor Covenant Code of Conduct to ensure a welcoming and inclusive community. (CODE_OF_CONDUCT.md)
* **Contributing Guidelines**: Detailed guidelines for contributing to the project, including setup instructions, how to report bugs, suggest features, and submit code changes. (CONTRIBUTING.md)
* **Security Policy**: Added a security policy outlining supported versions, how to report vulnerabilities, and the disclosure process. (SECURITY.md)

### Project Setup and Configuration:

* **Pre-commit Hook**: Added a pre-commit hook to run lint-staged checks, ensuring code quality before commits. (.husky/pre-commit)
* **npm Configuration**: Enabled automatic installation of peer dependencies. (.npmrc)
* **Server Configuration**: Updated server configuration files including gitignore for Prisma database files, Biome configuration, and code generation setup. (apps/server/.gitignore, apps/server/biome.json, apps/server/codegen.ts) [[1]](diffhunk://#diff-73a249eca23b4a6165a70cd4d1534c5b9e65e734faad0b578b2288938ae623abR1-R3) [[2]](diffhunk://#diff-1f9cb96a9f943c51ef5f602ff4eb2e695ebc1d56d708ce45d1a856f7ca43c90cR1-R7) [[3]](diffhunk://#diff-6989d66dc4f37000bf38c8dd7a9dbd6edcfc1c2c4159683dc72547b7561a306aR1-R16)
* **Server Package.json**: Added necessary dependencies and scripts for the server application. (apps/server/package.json)

These changes collectively enhance the project's documentation, community engagement, and development workflow.


Diff:
diff --git a/apps/server/src/resolvers.ts b/apps/server/src/resolvers.ts
index ecd7990..71729e3 100644
--- a/apps/server/src/resolvers.ts
+++ b/apps/server/src/resolvers.ts
@@ -36,7 +36,14 @@ export const resolvers: Resolvers = {
 
       const article = await context.dataSources.db.article.findUnique({
         where: { id },
-        include: { author: true },
+        include: {
+          author: true,
+          comments: {
+            include: {
+              author: true,
+            },
+          },
+        },
       });
       if (!article) {
         return null;
@@ -50,6 +57,17 @@ export const resolvers: Resolvers = {
           createdAt: article.author.createdAt.toISOString(),
           updatedAt: article.author.updatedAt.toISOString(),
         },
+        comments: article.comments.map((comment) => ({
+          ...comment,
+          createdAt: comment.createdAt.toISOString(),
+          updatedAt: comment.updatedAt.toISOString(),
+
+          author: {
+            ...comment.author,
+            createdAt: comment.author.createdAt.toISOString(),
+            updatedAt: comment.author.updatedAt.toISOString(),
+          },
+        })),
       };
     },
     articles: async (_parent, { first, after }, context: DataSourceContext) => {
@@ -60,8 +78,14 @@ export const resolvers: Resolvers = {
         take: first || 10,
         skip: after || 0,
         orderBy: { createdAt: "desc" },
-        include: { author: true },
+        include: {
+          author: true,
+          comments: {
+            include: { author: true },
+          },
+        },
       });
+
       return articles.map((article) => ({
         ...article,
         createdAt: article.createdAt.toISOString(),
@@ -71,6 +95,16 @@ export const resolvers: Resolvers = {
           createdAt: article.author.createdAt.toISOString(),
           updatedAt: article.author.updatedAt.toISOString(),
         },
+        comments: article.comments.map((comment) => ({
+          ...comment,
+          createdAt: comment.createdAt.toISOString(),
+          updatedAt: comment.updatedAt.toISOString(),
+          author: {
+            ...comment.author,
+            createdAt: comment.author.createdAt.toISOString(),
+            updatedAt: comment.author.updatedAt.toISOString(),
+          },
+        })),
       }));
     },
     articlesByAuthor: async (_parent, { authorId, first, after }, context: DataSourceContext) => {
@@ -82,7 +116,7 @@ export const resolvers: Resolvers = {
         take: first || 10,
         skip: after || 0,
         orderBy: { createdAt: "desc" },
-        include: { author: true },
+        include: { author: true, comments: { include: { author: true } } },
       });
       return articles.map((article) => ({
         ...article,
@@ -93,6 +127,16 @@ export const resolvers: Resolvers = {
           createdAt: article.author.createdAt.toISOString(),
           updatedAt: article.author.updatedAt.toISOString(),
         },
+        comments: article.comments.map((comment) => ({
+          ...comment,
+          createdAt: comment.createdAt.toISOString(),
+          updatedAt: comment.updatedAt.toISOString(),
+          author: {
+            ...comment.author,
+            createdAt: comment.author.createdAt.toISOString(),
+            updatedAt: comment.author.updatedAt.toISOString(),
+          },
+        })),
       }));
     },
   },
@@ -121,6 +165,7 @@ export const resolvers: Resolvers = {
           createdAt: article.author.createdAt.toISOString(),
           updatedAt: article.author.updatedAt.toISOString(),
         },
+        comments: [],
       };
     },
     updateArticle: async (_parent, { id, title, content }, context: DataSourceContext) => {
@@ -130,18 +175,27 @@ export const resolvers: Resolvers = {
       if (!context.user) {
         throw new Error("Unauthorized: Please log in");
       }
-      const article = await context.dataSources.db.article.findUnique({ where: { id } });
+      const article = await context.dataSources.db.article.findUnique({
+        where: { id },
+      });
       if (!article) {
         throw new Error("Article not found");
       }
       if (article.authorId !== context.user.id) {
         throw new Error("Forbidden: You can only update your own articles");
       }
+
       const updatedArticle = await context.dataSources.db.article.update({
         where: { id },
         data: { title: title ?? undefined, content: content ?? undefined },
-        include: { author: true },
+        include: {
+          author: true,
+          comments: {
+            include: { author: true },
+          },
+        },
       });
+
       return {
         ...updatedArticle,
         createdAt: updatedArticle.createdAt.toISOString(),
@@ -151,6 +205,16 @@ export const resolvers: Resolvers = {
           createdAt: updatedArticle.author.createdAt.toISOString(),
           updatedAt: updatedArticle.author.updatedAt.toISOString(),
         },
+        comments: updatedArticle.comments.map((comment) => ({
+          ...comment,
+          createdAt: comment.createdAt.toISOString(),
+          updatedAt: comment.updatedAt.toISOString(),
+          author: {
+            ...comment.author,
+            createdAt: comment.author.createdAt.toISOString(),
+            updatedAt: comment.author.updatedAt.toISOString(),
+          },
+        })),
       };
     },
     deleteArticle: async (_parent, { id }, context: DataSourceContext) => {
@@ -160,7 +224,9 @@ export const resolvers: Resolvers = {
       if (!context.user) {
         throw new Error("Unauthorized: Please log in");
       }
-      const article = await context.dataSources.db.article.findUnique({ where: { id } });
+      const article = await context.dataSources.db.article.findUnique({
+        where: { id },
+      });
       if (!article) {
         throw new Error("Article not found");
       }
@@ -170,6 +236,44 @@ export const resolvers: Resolvers = {
       await context.dataSources.db.article.delete({ where: { id } });
       return true;
     },
+    createComment: async (_parent, { content, articleId }, context: DataSourceContext) => {
+      await simulateDelay();
+
+      if (!context.user) {
+        throw new Error("Unauthorized: Please log in");
+      }
+
+      const article = await context.dataSources.db.article.findUnique({
+        where: { id: articleId },
+      });
+
+      if (!article) {
+        throw new Error("Article not found");
+      }
+
+      const comment = await context.dataSources.db.comment.create({
+        data: {
+          content,
+          articleId,
+          authorId: context.user.id,
+        },
+        include: {
+          author: true,
+        },
+      });
+
+      return {
+        ...comment,
+        createdAt: comment.createdAt.toISOString(),
+        updatedAt: comment.updatedAt.toISOString(),
+        author: {
+          ...comment.author,
+          createdAt: comment.author.createdAt.toISOString(),
+          updatedAt: comment.author.updatedAt.toISOString(),
+        },
+      };
+    },
+
     register: async (_parent, args, context: DataSourceContext) => {
       // Simulate a delay for demonstration purposes
       await simulateDelay();
@@ -180,7 +284,9 @@ export const resolvers: Resolvers = {
       }
       const { email, password, name } = parsed.data;
 
-      const existingUser = await context.dataSources.db.user.findUnique({ where: { email } });
+      const existingUser = await context.dataSources.db.user.findUnique({
+        where: { email },
+      });
       if (existingUser) {
         throw new Error("User with this email already exists");
       }
@@ -209,7 +315,9 @@ export const resolvers: Resolvers = {
       }
       const { email, password } = parsed.data;
 
-      const user = await context.dataSources.db.user.findUnique({ where: { email } });
+      const user = await context.dataSources.db.user.findUnique({
+        where: { email },
+      });
       if (!user || !(await bcrypt.compare(password, user.password))) {
         throw new Error("Invalid email or password");
       }
diff --git a/apps/server/src/schema.ts b/apps/server/src/schema.ts
index becfdc5..c8bb226 100644
--- a/apps/server/src/schema.ts
+++ b/apps/server/src/schema.ts
@@ -12,21 +12,33 @@ export const typeDefs = `#graphql
     title: String!
     content: String!
     author: User!
+    comments: [Comment!]!
     createdAt: String!
     updatedAt: String!
   }
 
+  type Comment{
+  id:Int!
+  content: String!
+  author: User!    
+  createdAt: String!
+  updatedAt: String! 
+}
+  
+
   type AuthPayload {
     token: String!
     user: User!
   }
 
+
   type Query {
     hello: String
     users: [User!]!
     articles(first: Int, after: Int): [Article!]! # All articles with pagination
     article(id: Int!): Article # Single article by ID
     articlesByAuthor(authorId: Int!, first: Int, after: Int): [Article!]! # Author-specific articles with pagination
+    
   }
 
   type Mutation {
@@ -35,5 +47,6 @@ export const typeDefs = `#graphql
     deleteArticle(id: Int!): Boolean!
     register(email: String!, password: String!, name: String!): AuthPayload!
     login(email: String!, password: String!): AuthPayload!
+    createComment(content: String!, articleId: Int!): Comment!
   }
 `;
diff --git a/apps/server/src/types.ts b/apps/server/src/types.ts
index 7ab3557..850e815 100644
--- a/apps/server/src/types.ts
+++ b/apps/server/src/types.ts
@@ -1,143 +1,150 @@
-import { GraphQLResolveInfo } from 'graphql';
-import { DataSourceContext } from './context';
+import { GraphQLResolveInfo } from "graphql";
+import { DataSourceContext } from "./context";
 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 MakeEmpty<T extends { [key: string]: unknown }, K extends keyof T> = { [_ in K]?: never };
-export type Incremental<T> = T | { [P in keyof T]?: P extends ' $fragmentName' | '__typename' ? T[P] : never };
+export type Incremental<T> = T | { [P in keyof T]?: P extends " $fragmentName" | "__typename" ? T[P] : never };
 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: { input: string; output: string; }
-  String: { input: string; output: string; }
-  Boolean: { input: boolean; output: boolean; }
-  Int: { input: number; output: number; }
-  Float: { input: number; output: number; }
+  ID: { input: string; output: string };
+  String: { input: string; output: string };
+  Boolean: { input: boolean; output: boolean };
+  Int: { input: number; output: number };
+  Float: { input: number; output: number };
 };
 
 export type Article = {
-  __typename?: 'Article';
+  __typename?: "Article";
   author: User;
-  content: Scalars['String']['output'];
-  createdAt: Scalars['String']['output'];
-  id: Scalars['Int']['output'];
-  title: Scalars['String']['output'];
-  updatedAt: Scalars['String']['output'];
+  content: Scalars["String"]["output"];
+  createdAt: Scalars["String"]["output"];
+  id: Scalars["Int"]["output"];
+  title: Scalars["String"]["output"];
+  updatedAt: Scalars["String"]["output"];
 };
 
 export type AuthPayload = {
-  __typename?: 'AuthPayload';
-  token: Scalars['String']['output'];
+  __typename?: "AuthPayload";
+  token: Scalars["String"]["output"];
   user: User;
 };
 
+export type Comment = {
+  __typename?: "Comment";
+  article: Article;
+  author: User;
+  content: Scalars["String"]["output"];
+  createdAt: Scalars["String"]["output"];
+  id: Scalars["Int"]["output"];
+  updatedAt: Scalars["String"]["output"];
+};
+
 export type Mutation = {
-  __typename?: 'Mutation';
+  __typename?: "Mutation";
   createArticle: Article;
-  deleteArticle: Scalars['Boolean']['output'];
+  createComment: Comment;
+  deleteArticle: Scalars["Boolean"]["output"];
   login: AuthPayload;
   register: AuthPayload;
   updateArticle: Article;
 };
 
-
 export type MutationCreateArticleArgs = {
-  content: Scalars['String']['input'];
-  title: Scalars['String']['input'];
+  content: Scalars["String"]["input"];
+  title: Scalars["String"]["input"];
 };
 
+export type MutationCreateCommentArgs = {
+  articleId: Scalars["Int"]["input"];
+  content: Scalars["String"]["input"];
+};
 
 export type MutationDeleteArticleArgs = {
-  id: Scalars['Int']['input'];
+  id: Scalars["Int"]["input"];
 };
 
-
 export type MutationLoginArgs = {
-  email: Scalars['String']['input'];
-  password: Scalars['String']['input'];
+  email: Scalars["String"]["input"];
+  password: Scalars["String"]["input"];
 };
 
-
 export type MutationRegisterArgs = {
-  email: Scalars['String']['input'];
-  name: Scalars['String']['input'];
-  password: Scalars['String']['input'];
+  email: Scalars["String"]["input"];
+  name: Scalars["String"]["input"];
+  password: Scalars["String"]["input"];
 };
 
-
 export type MutationUpdateArticleArgs = {
-  content?: InputMaybe<Scalars['String']['input']>;
-  id: Scalars['Int']['input'];
-  title?: InputMaybe<Scalars['String']['input']>;
+  content?: InputMaybe<Scalars["String"]["input"]>;
+  id: Scalars["Int"]["input"];
+  title?: InputMaybe<Scalars["String"]["input"]>;
 };
 
 export type Query = {
-  __typename?: 'Query';
+  __typename?: "Query";
   article?: Maybe<Article>;
   articles: Array<Article>;
   articlesByAuthor: Array<Article>;
-  hello?: Maybe<Scalars['String']['output']>;
+  hello?: Maybe<Scalars["String"]["output"]>;
   users: Array<User>;
 };
 
-
 export type QueryArticleArgs = {
-  id: Scalars['Int']['input'];
+  id: Scalars["Int"]["input"];
 };
 
-
 export type QueryArticlesArgs = {
-  after?: InputMaybe<Scalars['Int']['input']>;
-  first?: InputMaybe<Scalars['Int']['input']>;
+  after?: InputMaybe<Scalars["Int"]["input"]>;
+  first?: InputMaybe<Scalars["Int"]["input"]>;
 };
 
-
 export type QueryArticlesByAuthorArgs = {
-  after?: InputMaybe<Scalars['Int']['input']>;
-  authorId: Scalars['Int']['input'];
-  first?: InputMaybe<Scalars['Int']['input']>;
+  after?: InputMaybe<Scalars["Int"]["input"]>;
+  authorId: Scalars["Int"]["input"];
+  first?: InputMaybe<Scalars["Int"]["input"]>;
 };
 
 export type User = {
-  __typename?: 'User';
-  createdAt: Scalars['String']['output'];
-  email: Scalars['String']['output'];
-  id: Scalars['Int']['output'];
-  name?: Maybe<Scalars['String']['output']>;
-  updatedAt: Scalars['String']['output'];
+  __typename?: "User";
+  createdAt: Scalars["String"]["output"];
+  email: Scalars["String"]["output"];
+  id: Scalars["Int"]["output"];
+  name?: Maybe<Scalars["String"]["output"]>;
+  updatedAt: Scalars["String"]["output"];
 };
 
-
-
 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 Resolver<TResult, TParent = {}, TContext = {}, TArgs = {}> =
+  | ResolverFn<TResult, TParent, TContext, TArgs>
+  | ResolverWithResolve<TResult, TParent, TContext, TArgs>;
 
 export type ResolverFn<TResult, TParent, TContext, TArgs> = (
   parent: TParent,
   args: TArgs,
   context: TContext,
-  info: GraphQLResolveInfo
+  info: GraphQLResolveInfo,
 ) => Promise<TResult> | TResult;
 
 export type SubscriptionSubscribeFn<TResult, TParent, TContext, TArgs> = (
   parent: TParent,
   args: TArgs,
   context: TContext,
-  info: GraphQLResolveInfo
+  info: GraphQLResolveInfo,
 ) => AsyncIterable<TResult> | Promise<AsyncIterable<TResult>>;
 
 export type SubscriptionResolveFn<TResult, TParent, TContext, TArgs> = (
   parent: TParent,
   args: TArgs,
   context: TContext,
-  info: GraphQLResolveInfo
+  info: GraphQLResolveInfo,
 ) => TResult | Promise<TResult>;
 
 export interface SubscriptionSubscriberObject<TResult, TKey extends string, TParent, TContext, TArgs> {
@@ -161,10 +168,14 @@ export type SubscriptionResolver<TResult, TKey extends string, TParent = {}, TCo
 export type TypeResolveFn<TTypes, TParent = {}, TContext = {}> = (
   parent: TParent,
   context: TContext,
-  info: GraphQLResolveInfo
+  info: GraphQLResolveInfo,
 ) => Maybe<TTypes> | Promise<Maybe<TTypes>>;
 
-export type IsTypeOfResolverFn<T = {}, TContext = {}> = (obj: T, context: TContext, info: GraphQLResolveInfo) => boolean | Promise<boolean>;
+export type IsTypeOfResolverFn<T = {}, TContext = {}> = (
+  obj: T,
+  context: TContext,
+  info: GraphQLResolveInfo,
+) => boolean | Promise<boolean>;
 
 export type NextResolverFn<T> = () => Promise<T>;
 
@@ -173,20 +184,19 @@ export type DirectiveResolverFn<TResult = {}, TParent = {}, TContext = {}, TArgs
   parent: TParent,
   args: TArgs,
   context: TContext,
-  info: GraphQLResolveInfo
+  info: GraphQLResolveInfo,
 ) => TResult | Promise<TResult>;
 
-
-
 /** Mapping between all available schema types and the resolvers types */
 export type ResolversTypes = {
   Article: ResolverTypeWrapper<Article>;
   AuthPayload: ResolverTypeWrapper<AuthPayload>;
-  Boolean: ResolverTypeWrapper<Scalars['Boolean']['output']>;
-  Int: ResolverTypeWrapper<Scalars['Int']['output']>;
+  Boolean: ResolverTypeWrapper<Scalars["Boolean"]["output"]>;
+  Comment: ResolverTypeWrapper<Comment>;
+  Int: ResolverTypeWrapper<Scalars["Int"]["output"]>;
   Mutation: ResolverTypeWrapper<{}>;
   Query: ResolverTypeWrapper<{}>;
-  String: ResolverTypeWrapper<Scalars['String']['output']>;
+  String: ResolverTypeWrapper<Scalars["String"]["output"]>;
   User: ResolverTypeWrapper<User>;
 };
 
@@ -194,58 +204,114 @@ export type ResolversTypes = {
 export type ResolversParentTypes = {
   Article: Article;
   AuthPayload: AuthPayload;
-  Boolean: Scalars['Boolean']['output'];
-  Int: Scalars['Int']['output'];
+  Boolean: Scalars["Boolean"]["output"];
+  Comment: Comment;
+  Int: Scalars["Int"]["output"];
   Mutation: {};
   Query: {};
-  String: Scalars['String']['output'];
+  String: Scalars["String"]["output"];
   User: User;
 };
 
-export type ArticleResolvers<ContextType = DataSourceContext, ParentType extends ResolversParentTypes['Article'] = ResolversParentTypes['Article']> = {
-  author?: Resolver<ResolversTypes['User'], ParentType, ContextType>;
-  content?: Resolver<ResolversTypes['String'], ParentType, ContextType>;
-  createdAt?: Resolver<ResolversTypes['String'], ParentType, ContextType>;
-  id?: Resolver<ResolversTypes['Int'], ParentType, ContextType>;
-  title?: Resolver<ResolversTypes['String'], ParentType, ContextType>;
-  updatedAt?: Resolver<ResolversTypes['String'], ParentType, ContextType>;
+export type ArticleResolvers<
+  ContextType = DataSourceContext,
+  ParentType extends ResolversParentTypes["Article"] = ResolversParentTypes["Article"],
+> = {
+  author?: Resolver<ResolversTypes["User"], ParentType, ContextType>;
+  content?: Resolver<ResolversTypes["String"], ParentType, ContextType>;
+  createdAt?: Resolver<ResolversTypes["String"], ParentType, ContextType>;
+  id?: Resolver<ResolversTypes["Int"], ParentType, ContextType>;
+  title?: Resolver<ResolversTypes["String"], ParentType, ContextType>;
+  updatedAt?: Resolver<ResolversTypes["String"], ParentType, ContextType>;
+  __isTypeOf?: IsTypeOfResolverFn<ParentType, ContextType>;
+};
+
+export type AuthPayloadResolvers<
+  ContextType = DataSourceContext,
+  ParentType extends ResolversParentTypes["AuthPayload"] = ResolversParentTypes["AuthPayload"],
+> = {
+  token?: Resolver<ResolversTypes["String"], ParentType, ContextType>;
+  user?: Resolver<ResolversTypes["User"], ParentType, ContextType>;
   __isTypeOf?: IsTypeOfResolverFn<ParentType, ContextType>;
 };
 
-export type AuthPayloadResolvers<ContextType = DataSourceContext, ParentType extends ResolversParentTypes['AuthPayload'] = ResolversParentTypes['AuthPayload']> = {
-  token?: Resolver<ResolversTypes['String'], ParentType, ContextType>;
-  user?: Resolver<ResolversTypes['User'], ParentType, ContextType>;
+export type CommentResolvers<
+  ContextType = DataSourceContext,
+  ParentType extends ResolversParentTypes["Comment"] = ResolversParentTypes["Comment"],
+> = {
+  article?: Resolver<ResolversTypes["Article"], ParentType, ContextType>;
+  author?: Resolver<ResolversTypes["User"], ParentType, ContextType>;
+  content?: Resolver<ResolversTypes["String"], ParentType, ContextType>;
+  createdAt?: Resolver<ResolversTypes["String"], ParentType, ContextType>;
+  id?: Resolver<ResolversTypes["Int"], ParentType, ContextType>;
+  updatedAt?: Resolver<ResolversTypes["String"], ParentType, ContextType>;
   __isTypeOf?: IsTypeOfResolverFn<ParentType, ContextType>;
 };
 
-export type MutationResolvers<ContextType = DataSourceContext, ParentType extends ResolversParentTypes['Mutation'] = ResolversParentTypes['Mutation']> = {
-  createArticle?: Resolver<ResolversTypes['Article'], ParentType, ContextType, RequireFields<MutationCreateArticleArgs, 'content' | 'title'>>;
-  deleteArticle?: Resolver<ResolversTypes['Boolean'], ParentType, ContextType, RequireFields<MutationDeleteArticleArgs, 'id'>>;
-  login?: Resolver<ResolversTypes['AuthPayload'], ParentType, ContextType, RequireFields<MutationLoginArgs, 'email' | 'password'>>;
-  register?: Resolver<ResolversTypes['AuthPayload'], ParentType, ContextType, RequireFields<MutationRegisterArgs, 'email' | 'name' | 'password'>>;
-  updateArticle?: Resolver<ResolversTypes['Article'], ParentType, ContextType, RequireFields<MutationUpdateArticleArgs, 'id'>>;
+export type MutationResolvers<
+  ContextType = DataSourceContext,
+  ParentType extends ResolversParentTypes["Mutation"] = ResolversParentTypes["Mutation"],
+> = {
+  createArticle?: Resolver<
+    ResolversTypes["Article"],
+    ParentType,
+    ContextType,
+    RequireFields<MutationCreateArticleArgs, "content" | "title">
+  >;
+  createComment?: Resolver<
+    ResolversTypes["Comment"],
+    ParentType,
+    ContextType,
+    RequireFields<MutationCreateCommentArgs, "articleId" | "content">
+  >;
+  deleteArticle?: Resolver<ResolversTypes["Boolean"], ParentType, ContextType, RequireFields<MutationDeleteArticleArgs, "id">>;
+  login?: Resolver<
+    ResolversTypes["AuthPayload"],
+    ParentType,
+    ContextType,
+    RequireFields<MutationLoginArgs, "email" | "password">
+  >;
+  register?: Resolver<
+    ResolversTypes["AuthPayload"],
+    ParentType,
+    ContextType,
+    RequireFields<MutationRegisterArgs, "email" | "name" | "password">
+  >;
+  updateArticle?: Resolver<ResolversTypes["Article"], ParentType, ContextType, RequireFields<MutationUpdateArticleArgs, "id">>;
 };
 
-export type QueryResolvers<ContextType = DataSourceContext, ParentType extends ResolversParentTypes['Query'] = ResolversParentTypes['Query']> = {
-  article?: Resolver<Maybe<ResolversTypes['Article']>, ParentType, ContextType, RequireFields<QueryArticleArgs, 'id'>>;
-  articles?: Resolver<Array<ResolversTypes['Article']>, ParentType, ContextType, Partial<QueryArticlesArgs>>;
-  articlesByAuthor?: Resolver<Array<ResolversTypes['Article']>, ParentType, ContextType, RequireFields<QueryArticlesByAuthorArgs, 'authorId'>>;
-  hello?: Resolver<Maybe<ResolversTypes['String']>, ParentType, ContextType>;
-  users?: Resolver<Array<ResolversTypes['User']>, ParentType, ContextType>;
+export type QueryResolvers<
+  ContextType = DataSourceContext,
+  ParentType extends ResolversParentTypes["Query"] = ResolversParentTypes["Query"],
+> = {
+  article?: Resolver<Maybe<ResolversTypes["Article"]>, ParentType, ContextType, RequireFields<QueryArticleArgs, "id">>;
+  articles?: Resolver<Array<ResolversTypes["Article"]>, ParentType, ContextType, Partial<QueryArticlesArgs>>;
+  articlesByAuthor?: Resolver<
+    Array<ResolversTypes["Article"]>,
+    ParentType,
+    ContextType,
+    RequireFields<QueryArticlesByAuthorArgs, "authorId">
+  >;
+  hello?: Resolver<Maybe<ResolversTypes["String"]>, ParentType, ContextType>;
+  users?: Resolver<Array<ResolversTypes["User"]>, ParentType, ContextType>;
 };
 
-export type UserResolvers<ContextType = DataSourceContext, ParentType extends ResolversParentTypes['User'] = ResolversParentTypes['User']> = {
-  createdAt?: Resolver<ResolversTypes['String'], ParentType, ContextType>;
-  email?: Resolver<ResolversTypes['String'], ParentType, ContextType>;
-  id?: Resolver<ResolversTypes['Int'], ParentType, ContextType>;
-  name?: Resolver<Maybe<ResolversTypes['String']>, ParentType, ContextType>;
-  updatedAt?: Resolver<ResolversTypes['String'], ParentType, ContextType>;
+export type UserResolvers<
+  ContextType = DataSourceContext,
+  ParentType extends ResolversParentTypes["User"] = ResolversParentTypes["User"],
+> = {
+  createdAt?: Resolver<ResolversTypes["String"], ParentType, ContextType>;
+  email?: Resolver<ResolversTypes["String"], ParentType, ContextType>;
+  id?: Resolver<ResolversTypes["Int"], ParentType, ContextType>;
+  name?: Resolver<Maybe<ResolversTypes["String"]>, ParentType, ContextType>;
+  updatedAt?: Resolver<ResolversTypes["String"], ParentType, ContextType>;
   __isTypeOf?: IsTypeOfResolverFn<ParentType, ContextType>;
 };
 
 export type Resolvers<ContextType = DataSourceContext> = {
   Article?: ArticleResolvers<ContextType>;
   AuthPayload?: AuthPayloadResolvers<ContextType>;
+  Comment?: CommentResolvers<ContextType>;
   Mutation?: MutationResolvers<ContextType>;
   Query?: QueryResolvers<ContextType>;
   User?: UserResolvers<ContextType>;


Reviews:
user: login: copilot-pull-request-reviewer[bot]
body: 

Copilot reviewed 3 out of 3 changed files in this pull request and generated 1 comment.




state: COMMENTED
author_association: NONE
submitted_at: 2025-04-08T10:46:14Z
user: login: georgyguei
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-09T07:29:38Z
user: login: georgyguei
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-09T07:33:30Z
user: login: georgyguei
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-09T07:34:12Z
user: login: georgyguei
state: DISMISSED
author_association: MEMBER
submitted_at: 2025-04-09T07:35:35Z
user: login: georgyguei
state: APPROVED
author_association: MEMBER
submitted_at: 2025-04-09T08:43:54Z

Review comments:
diff_hunk: @@ -170,6 +174,43 @@ export const resolvers: Resolvers = {
       await context.dataSources.db.article.delete({ where: { id } });
       return true;
     },
+    createComment: async (_parent, { content, articleId }, context: DataSourceContext) => {
path: apps/server/src/resolvers.ts
user: login: Copilot
body: The createComment resolver does not verify that the associated article exists before attempting to create the comment, which might lead to foreign key errors. Consider adding a check similar to that in deleteArticle to confirm the article's existence.
created_at: 2025-04-08T10:46:14Z
author_association: NONE
diff_hunk: @@ -50,6 +57,21 @@ export const resolvers: Resolvers = {
           createdAt: article.author.createdAt.toISOString(),
           updatedAt: article.author.updatedAt.toISOString(),
         },
+        comments: article.comments.map((comment) => ({
+          ...comment,
+          createdAt: comment.createdAt.toISOString(),
+          updatedAt: comment.updatedAt.toISOString(),
+          // article: {
+          //   ...comment.article,
+          //   createdAt: comment.article.createdAt.toISOString(),
+          //   updatedAt: comment.article.updatedAt.toISOString(),
+          // },
path: apps/server/src/resolvers.ts
user: login: georgyguei
body: Remove useless comments
created_at: 2025-04-09T07:29:38Z
author_association: MEMBER
diff_hunk: @@ -170,6 +240,55 @@ export const resolvers: Resolvers = {
       await context.dataSources.db.article.delete({ where: { id } });
       return true;
     },
+    createComment: async (_parent, { content, articleId }, context: DataSourceContext) => {
+      await simulateDelay();
+
+      if (!context.user) {
+        throw new Error("Unauthorized: Please log in");
+      }
+
+      const article = await context.dataSources.db.article.findUnique({
+        where: { id: articleId },
+      });
+
+      if (!article) {
+        throw new Error("Article not found");
+      }
+
+      const comment = await context.dataSources.db.comment.create({
+        data: {
+          content,
+          articleId,
+          authorId: context.user.id,
+        },
+        include: {
+          //  article: true,
path: apps/server/src/resolvers.ts
user: login: georgyguei
body: Remove useless comment
created_at: 2025-04-09T07:33:30Z
author_association: MEMBER
diff_hunk: @@ -170,6 +240,55 @@ export const resolvers: Resolvers = {
       await context.dataSources.db.article.delete({ where: { id } });
       return true;
     },
+    createComment: async (_parent, { content, articleId }, context: DataSourceContext) => {
+      await simulateDelay();
+
+      if (!context.user) {
+        throw new Error("Unauthorized: Please log in");
+      }
+
+      const article = await context.dataSources.db.article.findUnique({
+        where: { id: articleId },
+      });
+
+      if (!article) {
+        throw new Error("Article not found");
+      }
+
+      const comment = await context.dataSources.db.comment.create({
+        data: {
+          content,
+          articleId,
+          authorId: context.user.id,
+        },
+        include: {
+          //  article: true,
+          author: true,
+        },
+      });
+
+      return {
+        ...comment,
+        createdAt: comment.createdAt.toISOString(),
+        updatedAt: comment.updatedAt.toISOString(),
+        // article: {
+        //   ...comment.article,
+        //   author: {
+        //     ...comment.author,
+        //     createdAt: comment.author.createdAt.toISOString(),
+        //     updatedAt: comment.author.updatedAt.toISOString(),
+        //   },
+        //   createdAt: comment.article.createdAt.toISOString(),
+        //   updatedAt: comment.article.updatedAt.toISOString(),
+        // },
+
path: apps/server/src/resolvers.ts
user: login: georgyguei
body: Remove useless comments
created_at: 2025-04-09T07:34:12Z
author_association: MEMBER

PR comments:
user: login: Farid-Efrei
created_at: 2025-04-09T07:53:54Z
body: This pull request introduces significant changes to the GraphQL schema and resolvers in the server application to support comments on articles. It includes modifications to the resolvers, schema definitions, and type definitions to handle comments.

### GraphQL Resolvers Enhancements:
* Added support for including comments and their authors when fetching articles and articles by author. (`apps/server/src/resolvers.ts`, [[1]](diffhunk://#diff-2b1f559e35df3c6aee138dc7111aa00f903383b67bdf8a8f74d7b9e2d7278d06L39-R46) [[2]](diffhunk://#diff-2b1f559e35df3c6aee138dc7111aa00f903383b67bdf8a8f74d7b9e2d7278d06R60-R70) [[3]](diffhunk://#diff-2b1f559e35df3c6aee138dc7111aa00f903383b67bdf8a8f74d7b9e2d7278d06R81-R88) [[4]](diffhunk://#diff-2b1f559e35df3c6aee138dc7111aa00f903383b67bdf8a8f74d7b9e2d7278d06R98-R107) [[5]](diffhunk://#diff-2b1f559e35df3c6aee138dc7111aa00f903383b67bdf8a8f74d7b9e2d7278d06L85-R119) [[6]](diffhunk://#diff-2b1f559e35df3c6aee138dc7111aa00f903383b67bdf8a8f74d7b9e2d7278d06R130-R139) [[7]](diffhunk://#diff-2b1f559e35df3c6aee138dc7111aa00f903383b67bdf8a8f74d7b9e2d7278d06R168) [[8]](diffhunk://#diff-2b1f559e35df3c6aee138dc7111aa00f903383b67bdf8a8f74d7b9e2d7278d06L133-R198) [[9]](diffhunk://#diff-2b1f559e35df3c6aee138dc7111aa00f903383b67bdf8a8f74d7b9e2d7278d06R208-R217) [[10]](diffhunk://#diff-2b1f559e35df3c6aee138dc7111aa00f903383b67bdf8a8f74d7b9e2d7278d06L163-R229)
* Introduced a new mutation `createComment` to allow users to add comments to articles. (`apps/server/src/resolvers.ts`, [apps/server/src/resolvers.tsR239-R276](diffhunk://#diff-2b1f559e35df3c6aee138dc7111aa00f903383b67bdf8a8f74d7b9e2d7278d06R239-R276))

### GraphQL Schema Updates:
* Added `comments` field to the `Article` type and defined a new `Comment` type. (`apps/server/src/schema.ts`, [apps/server/src/schema.tsR15-R41](diffhunk://#diff-ca76cab8bd94b74775374ab503f17e1b9e8d9c276f273ff77cf49ddca5ce3b54R15-R41))
* Added `createComment` mutation to the schema. (`apps/server/src/schema.ts`, [apps/server/src/schema.tsR50](diffhunk://#diff-ca76cab8bd94b74775374ab503f17e1b9e8d9c276f273ff77cf49ddca5ce3b54R50))

### Type Definitions Adjustments:
* Updated type definitions to include `Comment` type and its related fields. (`apps/server/src/types.ts`, [apps/server/src/types.tsL1-R147](diffhunk://#diff-df69a8eee2e8530ab6b2d9eebe27806a821850ecd9eaa9dee7aa1b541f0aa04cL1-R147))
* Refactored existing type definitions for consistency and clarity. (`apps/server/src/types.ts`, [apps/server/src/types.tsL164-R178](diffhunk://#diff-df69a8eee2e8530ab6b2d9eebe27806a821850ecd9eaa9dee7aa1b541f0aa04cL164-R178))

These changes enhance the functionality of the server by allowing users to interact with comments on articles, providing a more comprehensive and interactive experience.
author_association: MEMBER
user: login: Farid-Efrei
created_at: 2025-04-09T08:43:12Z
body: Reopen
author_association: MEMBER