Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Session 6 - Dimitar #2

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 44 additions & 0 deletions backend/src/application/services/candidateService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,3 +63,47 @@ export const findCandidateById = async (id: number): Promise<Candidate | null> =
throw new Error('Error al recuperar el candidato');
}
};

interface CandidateProgress {
fullName: string;
currentInterviewStep: string;
averageScore: number | null;
}

export const getCandidatesByPosition = async (positionId: number): Promise<CandidateProgress[]> => {
try {
const candidates = await Candidate.findByPosition(positionId);
return candidates.map(candidate => ({
fullName: `${candidate.firstName} ${candidate.lastName}`,
currentInterviewStep: String(candidate.applications[0]?.currentInterviewStep || 'Not started'),
averageScore: calculateAverageScore(candidate.applications[0]?.interviews || [])
}));
} catch (error) {
console.error('Error fetching candidates by position:', error);
throw new Error('Error retrieving candidates for position');
}
};

function calculateAverageScore(interviews: any[]): number | null {
if (!interviews.length) return null;
const scores = interviews.filter(interview => interview.score !== null).map(interview => interview.score);
if (!scores.length) return null;
return scores.reduce((sum, score) => sum + score, 0) / scores.length;
}

export const updateCandidateInterviewStep = async (id: number) => {
const candidate = await Candidate.findOne(id);
if (!candidate) {
throw new Error('Candidate not found');
}

// Assuming currentInterviewStep is a number and we want to increment it
const currentStep = candidate.applications[0]?.currentInterviewStep || 0;
const newStep = Math.min(currentStep + 1, 3); // Increment and cap at 3

// Update the candidate's interview step
candidate.applications[0].currentInterviewStep = newStep;

// Save the updated candidate
return await candidate.save();
};
32 changes: 30 additions & 2 deletions backend/src/domain/models/Candidate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,8 @@ export class Candidate {
candidateData.resumes = {
create: this.resumes.map(resume => ({
filePath: resume.filePath,
fileType: resume.fileType
fileType: resume.fileType,
uploadDate: resume.uploadDate
}))
};
}
Expand All @@ -81,7 +82,6 @@ export class Candidate {
candidateData.applications = {
create: this.applications.map(app => ({
positionId: app.positionId,
candidateId: app.candidateId,
applicationDate: app.applicationDate,
currentInterviewStep: app.currentInterviewStep,
notes: app.notes,
Expand Down Expand Up @@ -160,4 +160,32 @@ export class Candidate {
if (!data) return null;
return new Candidate(data);
}

static async findByPosition(positionId: number): Promise<Candidate[]> {
const candidates = await prisma.candidate.findMany({
where: {
applications: {
some: {
positionId: positionId
}
}
},
include: {
applications: {
where: {
positionId: positionId
},
include: {
interviews: {
select: {
score: true
}
}
}
}
}
});

return candidates.map(data => new Candidate(data));
}
}
10 changes: 9 additions & 1 deletion backend/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,10 @@ import express from 'express';
import { PrismaClient } from '@prisma/client';
import dotenv from 'dotenv';
import candidateRoutes from './routes/candidateRoutes';
import candidatesRoutes from './routes/candidatesRoutes';
import { uploadFile } from './application/services/fileUploadService';
import cors from 'cors';
import positionRoutes from './routes/positionRoutes';

// Extender la interfaz Request para incluir prisma
declare global {
Expand Down Expand Up @@ -36,8 +38,14 @@ app.use(cors({
credentials: true
}));

// Import and use candidatesRoutes
app.use('/candidates', candidatesRoutes);

// Import and use candidateRoutes
app.use('/candidates', candidateRoutes);
app.use('/candidate', candidateRoutes);

// Import and use positionRoutes
app.use(positionRoutes);

// Route for file uploads
app.post('/upload', uploadFile);
Expand Down
30 changes: 29 additions & 1 deletion backend/src/presentation/controllers/candidateController.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { Request, Response } from 'express';
import { addCandidate, findCandidateById } from '../../application/services/candidateService';
import { addCandidate, findCandidateById, getCandidatesByPosition, updateCandidateInterviewStep as updateCandidateInterviewStepService } from '../../application/services/candidateService';

export const addCandidateController = async (req: Request, res: Response) => {
try {
Expand Down Expand Up @@ -31,4 +31,32 @@ export const getCandidateById = async (req: Request, res: Response) => {
}
};

export const getCandidatesByPositionController = async (req: Request, res: Response) => {
try {
const positionId = parseInt(req.params.id);
if (isNaN(positionId)) {
return res.status(400).json({ error: 'Invalid position ID format' });
}

const candidates = await getCandidatesByPosition(positionId);
res.json(candidates);
} catch (error) {
console.error('Controller error:', error);
res.status(500).json({ error: 'Internal Server Error' });
}
};

export const updateCandidateInterviewStep = async (req: Request, res: Response) => {
try {
const id = parseInt(req.params.id);
if (isNaN(id)) {
return res.status(400).json({ error: 'Invalid ID format' });
}
const updatedCandidate = await updateCandidateInterviewStepService(id);
res.json({ message: 'Candidate interview step updated successfully', data: updatedCandidate });
} catch (error) {
res.status(500).json({ error: 'Internal Server Error' });
}
};

export { addCandidate };
19 changes: 3 additions & 16 deletions backend/src/routes/candidateRoutes.ts
Original file line number Diff line number Diff line change
@@ -1,22 +1,9 @@
import { Router } from 'express';
import { addCandidate, getCandidateById } from '../presentation/controllers/candidateController';
import { updateCandidateInterviewStep } from '../presentation/controllers/candidateController';

const router = Router();

router.post('/', async (req, res) => {
try {
// console.log(req.body); //Just in case you want to inspect the request body
const result = await addCandidate(req.body);
res.status(201).send(result);
} catch (error) {
if (error instanceof Error) {
res.status(400).send({ message: error.message });
} else {
res.status(500).send({ message: "An unexpected error occurred" });
}
}
});

router.get('/:id', getCandidateById);
// New route for updating candidate interview step
router.put('/:id', updateCandidateInterviewStep);

export default router;
24 changes: 24 additions & 0 deletions backend/src/routes/candidatesRoutes.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { Router } from 'express';
import { addCandidate, getCandidateById, getCandidatesByPositionController } from '../presentation/controllers/candidateController';

const router = Router();

router.post('/', async (req, res) => {
try {
// console.log(req.body); //Just in case you want to inspect the request body
const result = await addCandidate(req.body);
res.status(201).send(result);
} catch (error) {
if (error instanceof Error) {
res.status(400).send({ message: error.message });
} else {
res.status(500).send({ message: "An unexpected error occurred" });
}
}
});

router.get('/:id', getCandidateById);
router.get('/position/:id/candidates', getCandidatesByPositionController);


export default router;
9 changes: 9 additions & 0 deletions backend/src/routes/positionRoutes.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { Router } from 'express';
import { getCandidatesByPositionController } from '../presentation/controllers/candidateController';

const router = Router();

// Route for getting candidates by position ID
router.get('/position/:id/candidates', getCandidatesByPositionController);

export default router;
79 changes: 79 additions & 0 deletions prompts/prompts-dky.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
IDE used: Cursor

#1 --------------------------------------------------------------------------------

You are an expert backend developer with knowledge in NodeJS. I need you to build me a new GET endpoint.
It will have this path: /position/:id/candidates

This endpoint will retrieve all the candidates in progress for a specific position, meaning all the applications for a particular positionID. It should provide the following basic information:

- Candidate’s full name (from the candidate table).
- current_interview_step: the phase of the process the candidate is in (from the application table).
- The candidate's average score.

Remember that each interview (interview) conducted with the candidate has a score.

Make the service implementation in @candidateService.ts , the controller implementation in @candidateController.ts , use the @Candidate.ts , @Interview.ts and any other files in that directory to get the idea of the database schema.

#2 --------------------------------------------------------------------------------

For getCandidatesByPositionController in @candidateController.ts , generate me the code for @candidateRoutes.ts

#3 --------------------------------------------------------------------------------

For the code present, we get this error:
```
Type '{ fullName: string; currentInterviewStep: string | number; averageScore: number | null; }[]' is not assignable to type 'CandidateProgress[]'.
Type '{ fullName: string; currentInterviewStep: string | number; averageScore: number | null; }' is not assignable to type 'CandidateProgress'.
Types of property 'currentInterviewStep' are incompatible.
Type 'string | number' is not assignable to type 'string'.
Type 'number' is not assignable to type 'string'.
```
How can I resolve this? If you propose a fix, please make it concise.

#4 --------------------------------------------------------------------------------

Now give me an example URL to call this new service.

#5 --------------------------------------------------------------------------------

This is not the path format I wanted, I want the following format: /position/:id/candidates
With the current implementation the current path starts with /candidates , change this, I want the complete full path to be /position/:id/candidates
Tell me the code modifications needed for this change. Maybe you need to create a new route file.

#5 --------------------------------------------------------------------------------

For the code present, we get this error:
```
Cannot find module '../controllers/candidateController' or its corresponding type declarations.
```
How can I resolve this? If you propose a fix, please make it concise. Use this file for context @candidateController.ts and the method getCandidatesByPositionController.

#7 --------------------------------------------------------------------------------

Now I want another new endpoint, this time it will be a PUT type.

It must have the following format: /candidate/:id

This endpoint will update the stage of the moved candidate. It allows modification of the current interview process phase for a specific candidate. :id is the identifier for @Candidate.ts , and the value to update is interviewStepId of . The update consists of adding +1 to the current value, with 3 being the current maximum.

Create a new dedicated router file.
Generate me the code for @positionRoutes.ts , @candidateController.ts and @candidateService.ts

#8 --------------------------------------------------------------------------------

For the code present, we get this error:
```
Import declaration conflicts with local declaration of 'updateCandidateInterviewStep'.
```
How can I resolve this? If you propose a fix, please make it concise.

#9 --------------------------------------------------------------------------------

I get the following error when running this new service: Argument `uploadDate` is missing.
at Tn (C:\Users\dimitar.kirilovyank\Desktop\curso\AI4Devs-backend-main\backend\node_modules\@prisma\client\runtime\library.js:115:6855)

#10 -------------------------------------------------------------------------------

Now I get 'Unknown argument `candidateId`. Available options are marked with ?.'