Skip to content

Commit b0ededc

Browse files
hatfelicienelijahladdie
authored andcommitted
#461 creating a community page
1 parent 3126013 commit b0ededc

16 files changed

+3131
-2095
lines changed

.eslintrc.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
"@typescript-eslint/no-explicit-any": 0,
2424
"@typescript-eslint/no-non-null-assertion": 0,
2525
"no-useless-catch": 0,
26-
"@typescript-eslint/explicit-module-boundary-types": "off"
26+
"@typescript-eslint/explicit-module-boundary-types": "off",
27+
"@typescript-eslint/no-unused-vars": "off"
2728
}
2829
}

package-lock.json

Lines changed: 2415 additions & 1794 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,6 @@
7070
"dotenv": "^16.0.1",
7171
"ejs": "^3.1.8",
7272
"express": "^4.18.1",
73-
"faker": "^6.6.6",
7473
"generate-password": "^1.7.0",
7574
"graphql": "^16.5.0",
7675
"graphql-subscriptions": "^2.0.0",
@@ -96,7 +95,6 @@
9695
"@types/chai": "^4.3.3",
9796
"@types/cors": "^2.8.17",
9897
"@types/express": "^4.17.6",
99-
"@types/faker": "^6.6.9",
10098
"@types/jsonwebtoken": "^8.5.8",
10199
"@types/mocha": "^8.0.3",
102100
"@types/node": "^13.13.52",

src/helpers/user.helpers.ts

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,10 +15,9 @@ export const generateTokenOrganization = (name: string) => {
1515
return jwt.sign({ name }, SECRET, { expiresIn: '336h' })
1616
}
1717

18-
export const genericToken=(playLoad:any)=>{
19-
return jwt.sign({...playLoad},SECRET)
20-
}
21-
18+
export const genericToken = (playLoad: any) => {
19+
return jwt.sign({ ...playLoad }, SECRET)
20+
}
2221

2322
export const emailExpression =
2423
/^(([^<>()[\]\\.,;:\s@"]+(\.[^<>()[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/

src/index.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,8 @@ import invitationSchema from './schema/invitation.schema'
5151
import TableViewInvitationResolver from './resolvers/TableViewInvitationResolver'
5252
import eventSchema from './schema/event.schema'
5353
import './utils/cron-jobs/team-jobs'
54+
import CommunitySchema from './schema/community.schema'
55+
import CommunityResolver from './resolvers/community.resolver'
5456

5557
const PORT: number = parseInt(process.env.PORT!) || 4000
5658

@@ -66,6 +68,7 @@ export const typeDefs = mergeTypeDefs([
6668
notificationSchema,
6769
statisticsSchema,
6870
eventSchema,
71+
CommunitySchema,
6972
])
7073

7174
export const resolvers = mergeResolvers([
@@ -87,7 +90,7 @@ export const resolvers = mergeResolvers([
8790
Sessionresolvers,
8891

8992
StatisticsResolvers,
90-
93+
CommunityResolver,
9194
invitationResolvers,
9295
TableViewInvitationResolver,
9396
])

src/models/question.model.ts

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
import mongoose, { Document, Schema } from 'mongoose'
2+
3+
interface IAnswer {
4+
content: string
5+
author: mongoose.Types.ObjectId // Reference to User
6+
createdAt?: Date // Optional if not needed
7+
}
8+
9+
interface IQuestion extends Document {
10+
title: string
11+
content: string
12+
author: mongoose.Types.ObjectId // Reference to User
13+
createdAt?: Date // Optional if not needed
14+
answers: IAnswer[]
15+
}
16+
17+
const answerSchema = new Schema<IAnswer>({
18+
content: { type: String, required: true },
19+
author: { type: mongoose.Schema.Types.ObjectId, ref: 'User', required: true },
20+
createdAt: { type: Date, default: Date.now },
21+
})
22+
23+
const questionSchema = new Schema<IQuestion>({
24+
title: { type: String, required: true },
25+
content: { type: String, required: true },
26+
author: { type: mongoose.Schema.Types.ObjectId, ref: 'User', required: true },
27+
createdAt: { type: Date, default: Date.now },
28+
answers: [answerSchema],
29+
})
30+
31+
const Question = mongoose.model<IQuestion>('Question', questionSchema)
32+
33+
export { Question, IQuestion, IAnswer }

src/resolvers/cohort.resolvers.ts

Lines changed: 50 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,8 @@ const resolvers = {
6262
},
6363
})
6464
).filter((item) => {
65-
const org = (item.program as InstanceType<typeof Program>)?.organization
65+
const org = (item.program as InstanceType<typeof Program>)
66+
?.organization
6667
return item.program !== null && org !== null
6768
})
6869
} catch (error) {
@@ -74,41 +75,55 @@ const resolvers = {
7475
})
7576
}
7677
},
77-
getUserCohorts: async(_:any, { orgToken }: {orgToken: string}, context: Context)=>{
78-
const { userId, role} = (await checkUserLoggedIn(context))([RoleOfUser.COORDINATOR, RoleOfUser.TTL, RoleOfUser.TRAINEE])
78+
getUserCohorts: async (
79+
_: any,
80+
{ orgToken }: { orgToken: string },
81+
context: Context
82+
) => {
83+
const { userId, role } = (await checkUserLoggedIn(context))([
84+
RoleOfUser.COORDINATOR,
85+
RoleOfUser.TTL,
86+
RoleOfUser.TRAINEE,
87+
])
7988
const user = await User.findById(userId)
80-
if(!user){
81-
throw new GraphQLError("No such user found",{
89+
if (!user) {
90+
throw new GraphQLError('No such user found', {
8291
extensions: {
83-
code: "USER_NOT_FOUND"
84-
}
92+
code: 'USER_NOT_FOUND',
93+
},
8594
})
8695
}
87-
const org= await checkLoggedInOrganization(orgToken)
88-
if(!org){
89-
throw new GraphQLError("No such organization found",{
96+
const org = await checkLoggedInOrganization(orgToken)
97+
if (!org) {
98+
throw new GraphQLError('No such organization found', {
9099
extensions: {
91-
code: "ORG_NOT_FOUND"
92-
}
100+
code: 'ORG_NOT_FOUND',
101+
},
93102
})
94103
}
95-
switch(role){
96-
case RoleOfUser.COORDINATOR:
104+
105+
switch (role) {
106+
case RoleOfUser.COORDINATOR: {
97107
const coordinatorCohorts = await Cohort.find({
98108
coordinator: user._id,
99-
organization: org._id
100-
}).populate(['coordinator','phase','program'])
109+
organization: org._id,
110+
}).populate(['coordinator', 'phase', 'program'])
101111

102112
return coordinatorCohorts
113+
}
103114
case RoleOfUser.TTL:
104-
case RoleOfUser.TRAINEE:
105-
const cohort = await Cohort.findOne(user?.cohort)
106-
.populate(['coordinator','phase','program'])
107-
return [ cohort ]
115+
case RoleOfUser.TRAINEE: {
116+
const cohort = await Cohort.findOne(user?.cohort).populate([
117+
'coordinator',
118+
'phase',
119+
'program',
120+
])
121+
return [cohort]
122+
}
108123
default:
109124
return []
110125
}
111-
}
126+
},
112127
},
113128

114129
Mutation: {
@@ -136,8 +151,8 @@ const resolvers = {
136151
orgToken,
137152
} = args
138153

139-
// some validations
140-
; (await checkUserLoggedIn(context))([
154+
// some validations
155+
;(await checkUserLoggedIn(context))([
141156
RoleOfUser.SUPER_ADMIN,
142157
RoleOfUser.ADMIN,
143158
RoleOfUser.MANAGER,
@@ -183,7 +198,7 @@ const resolvers = {
183198
endDate &&
184199
isAfter(new Date(startDate.toString()), new Date(endDate.toString()))
185200
) {
186-
throw new GraphQLError('End Date can\'t be before Start Date', {
201+
throw new GraphQLError("End Date can't be before Start Date", {
187202
extensions: {
188203
code: 'VALIDATION_ERROR',
189204
},
@@ -318,12 +333,16 @@ const resolvers = {
318333

319334
if (
320335
endDate &&
321-
(isAfter(new Date(startDate.toString()),
322-
new Date(endDate.toString())) ||
323-
isAfter(new Date(cohort?.startDate?.toString() || ''),
324-
new Date(endDate)))
336+
(isAfter(
337+
new Date(startDate.toString()),
338+
new Date(endDate.toString())
339+
) ||
340+
isAfter(
341+
new Date(cohort?.startDate?.toString() || ''),
342+
new Date(endDate)
343+
))
325344
) {
326-
throw new GraphQLError('End Date can\'t be before Start Date', {
345+
throw new GraphQLError("End Date can't be before Start Date", {
327346
extensions: {
328347
code: 'VALIDATION_ERROR',
329348
},
@@ -403,8 +422,8 @@ const resolvers = {
403422
notificationChanges.push('Name')
404423
}
405424
if (phaseName && cohort.phase.toString() !== phase.id.toString()) {
406-
cohort.phase = phase.id;
407-
addNewAttendanceWeek();
425+
cohort.phase = phase.id
426+
addNewAttendanceWeek()
408427
notificationChanges.push('Phase')
409428
}
410429

Lines changed: 135 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,135 @@
1+
import { ApolloError } from 'apollo-server'
2+
import { checkUserLoggedIn } from '../helpers/user.helpers'
3+
import { Question, IQuestion, IAnswer } from '../models/question.model'
4+
import { User } from '../models/user'
5+
import logger from '../utils/logger.utils'
6+
import { Types } from 'mongoose'
7+
import { GraphQLError } from 'graphql'
8+
import { Context } from '../context'
9+
10+
// type Context = {
11+
// user?: {
12+
// id: string;
13+
// role?:string;
14+
// username: string;
15+
// };
16+
// };
17+
18+
interface GetQuestionsResult {
19+
questions: IQuestion[]
20+
totalQuestions: number
21+
}
22+
23+
const CommunityResolver = {
24+
Query: {
25+
async getAllQuestions(
26+
parent: undefined,
27+
args: { limit?: number; offset?: number },
28+
context: Context
29+
): Promise<IQuestion[]> {
30+
try {
31+
const limit = args.limit ?? 10
32+
const offset = args.offset ?? 0
33+
34+
const questions = await Question.find()
35+
.skip(offset)
36+
.limit(limit)
37+
.sort({ createdAt: -1 })
38+
39+
const totalQuestions = await Question.countDocuments()
40+
41+
return questions
42+
} catch (error) {
43+
const message = (error as Error).message
44+
throw new ApolloError(
45+
'An error occurred while fetching questions.',
46+
'INTERNAL_SERVER_ERROR',
47+
{ detailedMessage: message }
48+
)
49+
}
50+
},
51+
52+
async getQuestionById(
53+
parent: undefined,
54+
{ id }: { id: string }
55+
): Promise<IQuestion | null> {
56+
try {
57+
const question = await Question.findById(id).populate('answers.author')
58+
if (!question) throw new GraphQLError('Question not found')
59+
return question
60+
} catch (error) {
61+
const message = (error as Error).message
62+
throw new ApolloError(
63+
'An error occurred while fetching the question.',
64+
'INTERNAL_SERVER_ERROR',
65+
{ detailedMessage: message }
66+
)
67+
}
68+
},
69+
},
70+
71+
Mutation: {
72+
async createQuestion(
73+
parent: undefined,
74+
{ title, content }: { title: string; content: string },
75+
context: Context
76+
): Promise<IQuestion> {
77+
try {
78+
const user = await checkUserLoggedIn(context)
79+
if (!user) throw new GraphQLError('User not logged in')
80+
81+
const newQuestion = new Question({
82+
title,
83+
content,
84+
author: new Types.ObjectId(context?.userId), // Storing as ObjectId
85+
createdAt: new Date(),
86+
answers: [],
87+
})
88+
89+
await newQuestion.save()
90+
return newQuestion
91+
} catch (error) {
92+
const message = (error as Error).message
93+
throw new ApolloError(
94+
'An error occurred while creating a question.',
95+
'INTERNAL_SERVER_ERROR',
96+
{ detailedMessage: message }
97+
)
98+
}
99+
},
100+
101+
async createAnswer(
102+
parent: undefined,
103+
{ questionId, content }: { questionId: string; content: string },
104+
context: Context
105+
): Promise<IAnswer> {
106+
try {
107+
const user = await checkUserLoggedIn(context)
108+
if (!user) throw new GraphQLError('User not logged in')
109+
110+
const question = await Question.findById(questionId)
111+
if (!question) throw new GraphQLError('Question not found')
112+
113+
const newAnswer: IAnswer = {
114+
content,
115+
author: new Types.ObjectId(context?.userId),
116+
createdAt: new Date(),
117+
}
118+
119+
question.answers.push(newAnswer)
120+
await question.save()
121+
122+
return newAnswer
123+
} catch (error) {
124+
const message = (error as Error).message
125+
throw new ApolloError(
126+
'An error occurred while creating an answer.',
127+
'INTERNAL_SERVER_ERROR',
128+
{ detailedMessage: message }
129+
)
130+
}
131+
},
132+
},
133+
}
134+
135+
export default CommunityResolver

0 commit comments

Comments
 (0)