Skip to content

Commit

Permalink
feat(job-application): Applicant should be able to apply to job post (#…
Browse files Browse the repository at this point in the history
…142)

- create mutation and schema to handle job applications

[Delivers #140]
  • Loading branch information
jkarenzi authored Nov 19, 2024
1 parent f42f00c commit fa6c505
Show file tree
Hide file tree
Showing 4 changed files with 282 additions and 0 deletions.
4 changes: 4 additions & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,8 @@ import { commentReplySchema } from "./schema/commentReplySchema";
import { commentLikeSchema } from "./schema/commentLikeSchema";
import { commentLikeResolvers } from "./resolvers/commentLikeResolvers";
import { commentReplyResolvers } from "./resolvers/commentReplyResolvers";
import { jobApplicationTypeDefs } from "./schema/jobApplicationSchema";
import { jobApplicationResolver } from "./resolvers/jobApplicationResolver";

const PORT = process.env.PORT || 3000;

Expand Down Expand Up @@ -125,6 +127,7 @@ const resolvers = mergeResolvers([
ticketResolver,
filterTicketResolver,
applicationStageResolvers,
jobApplicationResolver,
blogResolvers,
likeResolvers,
commentResolvers,
Expand Down Expand Up @@ -170,6 +173,7 @@ const typeDefs = mergeTypeDefs([
commentSchema,
commentReplySchema,
commentLikeSchema,
jobApplicationTypeDefs,
]);

const server = new ApolloServer({
Expand Down
31 changes: 31 additions & 0 deletions src/models/JobApplication.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import mongoose from 'mongoose'

const jobApplicationSchema = new mongoose.Schema({
userId: {
type: mongoose.Schema.Types.ObjectId,
required: true,
ref: 'LoggedUserModel'
},
jobId: {
type: mongoose.Schema.Types.ObjectId,
required: true,
ref: 'jobform'
},
essay:{
type: String,
required: true
},
resume:{
type:String,
required: true
},
status:{
type:String,
enum:['submitted','under-review','accepted','rejected'],
default:'submitted'
}
},{timestamps:true})

const JobApplication = mongoose.model('JobApplication', jobApplicationSchema)

export default JobApplication
184 changes: 184 additions & 0 deletions src/resolvers/jobApplicationResolver.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,184 @@
import { AuthenticationError } from 'apollo-server';
import JobApplication from '../models/JobApplication'
import applicationCycleResolver from './applicationCycleResolver';
import { CustomGraphQLError } from '../utils/customErrorHandler';
import { LoggedUserModel } from '../models/AuthUser';
import { jobModels } from '../models/jobModels';

interface formData {
traineeId: string,
jobId: string,
essay: string,
resume: string
}

interface getOneFormData {
applicationId: string
}

interface statusData {
applicationId: string,
status: string
}

const formatDate = (date:Date) => {
const year = date.getFullYear();
const month = (`0${date.getMonth() + 1}`).slice(-2);
const day = (`0${date.getDate()}`).slice(-2);
return `${year}-${month}-${day}`;
};

export const jobApplicationResolver = {
Query: {
getOwnJobApplications: async (_:any, { input }:{input: formData}, cxt:any) => {
if (!cxt.currentUser) {
throw new AuthenticationError('You must be logged in');
}

const userId = cxt.currentUser._id

const applications = await JobApplication.find({userId})
.populate('jobId')

return applications.map(application => {
return {
_id: application._id,
userId: application.userId,
essay: application.essay,
resume: application.resume,
status: application.status,
jobId: application.jobId,
createdAt: formatDate(application.createdAt)
}
})
},
getAllJobApplications: async (_:any, { input }:{input: formData}, cxt:any) => {
if (!cxt.currentUser) {
throw new AuthenticationError('You must be logged in');
}

const userWithRole = await LoggedUserModel.findById(
cxt.currentUser?._id
).populate("role");

if (
!userWithRole ||
((userWithRole.role as any)?.roleName !== "admin" &&
(userWithRole.role as any)?.roleName !== "superAdmin")
) {
throw new CustomGraphQLError(
"You do not have permission to perform this action"
);
}

const applications = await JobApplication.find().populate('jobId').populate('userId')
return applications.map(application => {
return {
_id: application._id,
userId: application.userId,
essay: application.essay,
resume: application.resume,
status: application.status,
jobId: application.jobId,
createdAt: formatDate(application.createdAt)
}
})
},
getOneJobApplication: async (_:any, { input }:{input: getOneFormData}, cxt:any) => {
if (!cxt.currentUser) {
throw new AuthenticationError('You must be logged in');
}

const userWithRole = await LoggedUserModel.findById(
cxt.currentUser?._id
).populate("role");

if (
!userWithRole ||
((userWithRole.role as any)?.roleName !== "admin" &&
(userWithRole.role as any)?.roleName !== "superAdmin")
) {
throw new CustomGraphQLError(
"You do not have permission to perform this action"
);
}

const application = await JobApplication.findOne({_id: input.applicationId}).populate('jobId').populate('userId')
if(!application){
throw new Error('Application not found');
}

return {
_id: application._id,
userId: application.userId,
essay: application.essay,
resume: application.resume,
status: application.status,
jobId: application.jobId,
createdAt: formatDate(application.createdAt)
}
},
checkIfUserApplied: async (_: any, args: any, cxt: any) => {
if (!cxt.currentUser) {
throw new AuthenticationError('You must be logged in');
}

const application = await JobApplication.findOne({userId: cxt.currentUser._id, jobId: args.input.jobId})
return {
status: application ? true : false
}
}
},
Mutation: {
createNewJobApplication: async (_:any, { input }:{input: formData}, cxt:any) => {
if (!cxt.currentUser) {
throw new AuthenticationError('You must be logged in');
}

const userId = cxt.currentUser._id

const { jobId, essay, resume } = input;

const existingApplication = await JobApplication.findOne({userId, jobId})
if(existingApplication){
throw new Error('Application already exists');
}

const jobApplication = new JobApplication({
userId,
jobId,
essay,
resume,
});


const savedApplication = await jobApplication.save();

return savedApplication;
},
changeApplicationStatus: async (_:any, { input }:{input: statusData}, cxt:any) => {
if (!cxt.currentUser) {
throw new AuthenticationError('You must be logged in');
}

const userWithRole = await LoggedUserModel.findById(
cxt.currentUser?._id
).populate("role");

if (
!userWithRole ||
((userWithRole.role as any)?.roleName !== "admin" &&
(userWithRole.role as any)?.roleName !== "superAdmin")
) {
throw new CustomGraphQLError(
"You do not have permission to perform this action"
);
}

const { status, applicationId } = input
await JobApplication.findByIdAndUpdate(applicationId, {status})
const application = await JobApplication.findOne({_id:applicationId}).populate('userId').populate('jobId')
return application
}
},
};
63 changes: 63 additions & 0 deletions src/schema/jobApplicationSchema.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import { gql } from "apollo-server";

export const jobApplicationTypeDefs = gql`
type Job {
_id: ID!
title: String!
}
type User {
_id: ID!
email: String!
gender: String!
firstname: String!
lastname: String!
country: String!
telephone: String!
}
type JobApplication {
_id: ID!
userId: User!
jobId: Job!
essay: String!
resume: String!
status: String!
createdAt: String!
}
input JobApplicationInput {
jobId: ID!
essay: String!
resume: String!
}
input SingleJobApplicationInput {
applicationId: ID!
}
input StatusInput {
applicationId: ID!
status: String!
}
type checkIfUserAppliedOutput {
status: Boolean!
}
input CheckIfUserAppliedInput {
jobId: ID!
}
type Query {
getOwnJobApplications: [JobApplication!]!
getAllJobApplications: [JobApplication!]!
getOneJobApplication(input: SingleJobApplicationInput): JobApplication!
checkIfUserApplied(input: CheckIfUserAppliedInput): checkIfUserAppliedOutput!
}
type Mutation {
createNewJobApplication(input: JobApplicationInput!): JobApplication!
changeApplicationStatus(input: StatusInput!): JobApplication!
}
`

0 comments on commit fa6c505

Please sign in to comment.