diff --git a/backend/database/data/applicationStatus.json b/backend/database/data/applicationStatus.json index 39262dd..780e9eb 100644 --- a/backend/database/data/applicationStatus.json +++ b/backend/database/data/applicationStatus.json @@ -1 +1 @@ -[{ "label": "Accepté" }, { "label": "Refusé" }, { "label": "En cours" }] +[{ "label": "Accepté" }, { "label": "En cours" }, { "label": "Refusé" }] diff --git a/backend/database/schema.sql b/backend/database/schema.sql index fee9d73..3878364 100644 --- a/backend/database/schema.sql +++ b/backend/database/schema.sql @@ -63,7 +63,7 @@ CREATE TABLE application ( user_id INTEGER NOT NULL, job_id INTEGER NOT NULL, created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, - status_id INTEGER NOT NULL, + status_id INTEGER NOT NULL DEFAULT 2, FOREIGN KEY (user_id) REFERENCES user(id) ON DELETE CASCADE, FOREIGN KEY (job_id) REFERENCES job(id) ON DELETE CASCADE, FOREIGN KEY (status_id) REFERENCES application_status(id) ON DELETE CASCADE diff --git a/backend/seed.js b/backend/seed.js index 8cb87dc..d0888e2 100644 --- a/backend/seed.js +++ b/backend/seed.js @@ -6,10 +6,10 @@ require("dotenv").config(); // Import database client const database = require("./database/client"); const role = require("./database/data/role.json"); +const user = require("./database/data/user.json"); const company = require("./database/data/company.json"); const applicationStatus = require("./database/data/applicationStatus.json"); const job = require("./database/data/job.json"); -const user = require("./database/data/user.json"); const seed = async () => { try { diff --git a/backend/src/controllers/applicationControllers.js b/backend/src/controllers/applicationControllers.js new file mode 100644 index 0000000..b64cc2a --- /dev/null +++ b/backend/src/controllers/applicationControllers.js @@ -0,0 +1,118 @@ +// Import access to database tables +const tables = require("../tables"); + +// The B of BREAD - Browse (Read All) operation +const browse = async (req, res, next) => { + try { + // Fetch all applications from the database + const applications = await tables.application.readAll(); + + // Respond with the applications in JSON format + res.status(200).json(applications); + } catch (err) { + // Pass any errors to the error-handling middleware + next(err); + } +}; + +// The R of BREAD - Read operation +const read = async (req, res, next) => { + try { + // Fetch a specific application from the database based on the provided ID + const application = await tables.application.read(req.params.id); + + // If the application is not found, respond with HTTP 404 (Not Found) + // Otherwise, respond with the application in JSON format + if (application == null) { + res.sendStatus(404); + } else { + res.status(200).json(application); + } + } catch (err) { + // Pass any errors to the error-handling middleware + next(err); + } +}; + +const readProfileApplications = async (req, res, next) => { + try { + const applications = await tables.application.getProfileApplications( + req.user.id + ); + + if (applications == null) { + res.sendStatus(404); + } else { + res.status(200).json(applications); + } + } catch (err) { + next(err); + } +}; + +const readConsultantApplications = async (req, res, next) => { + try { + const applications = await tables.application.getConsultantApplications( + req.user.id + ); + + if (applications == null) { + res.sendStatus(404); + } else { + res.status(200).json(applications); + } + } catch (err) { + next(err); + } +}; + +// The E of BREAD - Edit (Update) operation +const edit = async (req, res, next) => { + // Extract the application data from the request body + const application = req.body; + try { + // Fetch a specific city from the database based on the provided ID + const result = await tables.application.update(req.params.id, application); + + // If the application is not found, respond with HTTP 404 (Not Found) + if (result.affectedRows === 1) { + res.sendStatus(204); + } else { + res.sendStatus(404); + } + } catch (err) { + // Pass any errors to the error-handling middleware + next(err); + } +}; + +// The A of BREAD - Add (Create) operation +const add = async (req, res, next) => { + // Extract the application data from the request body + const application = req.body; + + try { + // Insert the application into the database + const insertId = await tables.application.create(application); + + // Respond with HTTP 201 (Created) and the ID of the newly inserted application + res.status(201).json({ insertId }); + } catch (err) { + // Pass any errors to the error-handling middleware + next(err); + } +}; + +// The D of BREAD - Destroy (Delete) operation +// This operation is not yet implemented + +// Ready to export the controller functions +module.exports = { + browse, + read, + readProfileApplications, + readConsultantApplications, + edit, + add, + // destroy, +}; diff --git a/backend/src/controllers/applicationStatusControllers.js b/backend/src/controllers/applicationStatusControllers.js new file mode 100644 index 0000000..36243ab --- /dev/null +++ b/backend/src/controllers/applicationStatusControllers.js @@ -0,0 +1,21 @@ +// Import access to database tables +const tables = require("../tables"); + +// The B of BREAD - Browse (Read All) operation +const browse = async (req, res, next) => { + try { + // Fetch all status from the database + const status = await tables.application_status.readAll(); + + // Respond with the status in JSON format + res.status(200).json(status); + } catch (err) { + // Pass any errors to the error-handling middleware + next(err); + } +}; + +// Ready to export the controller functions +module.exports = { + browse, +}; diff --git a/backend/src/controllers/userControllers.js b/backend/src/controllers/userControllers.js index 482f4d4..2087085 100644 --- a/backend/src/controllers/userControllers.js +++ b/backend/src/controllers/userControllers.js @@ -188,6 +188,10 @@ const destroy = async (req, res, next) => { } }; +const logout = (req, res) => { + res.cookie("auth", "", { expires: new Date(0) }).sendStatus(200); +}; + // Ready to export the controller functions module.exports = { getConsultant, @@ -202,4 +206,5 @@ module.exports = { destroy, updateProfile, updateProfileCV, + logout, }; diff --git a/backend/src/models/ApplicationManager.js b/backend/src/models/ApplicationManager.js new file mode 100644 index 0000000..16778c2 --- /dev/null +++ b/backend/src/models/ApplicationManager.js @@ -0,0 +1,111 @@ +const AbstractManager = require("./AbstractManager"); + +class ApplicationManager extends AbstractManager { + constructor() { + // Call the constructor of the parent class (AbstractManager) + // and pass the table name "application" as configuration + super({ table: "application" }); + } + + // The C of CRUD - Create operation + + async create(application) { + // Execute the SQL INSERT query to add a new application to the "application" table + const [result] = await this.database.query( + `insert into ${this.table} (user_id, job_id) values (?, ?)`, + [application.user_id, application.job_id] + ); + + // Return the ID of the newly inserted application + return result.insertId; + } + + // The Rs of CRUD - Read operations + + async read(id) { + // Execute the SQL SELECT query to retrieve a specific application by its ID + const [rows] = await this.database.query( + `select * from ${this.table} where id = ?`, + [id] + ); + + // Return the first row of the result, which represents the application + return rows[0]; + } + + async readAll() { + // Execute the SQL SELECT query to retrieve all applications from the "application" table + const [rows] = await this.database.query(`select * from ${this.table}`); + + // Return the array of applications + return rows; + } + + async getProfileApplications(userId) { + const [rows] = await this.database.query( + `SELECT + application.id, + application.job_id, + application.status_id, + job.title AS job_title, + job.consultant_id, + consultant.email AS consultant_email, + application_status.label as status_label + FROM ${this.table} AS application + INNER JOIN job ON application.job_id = job.id + INNER JOIN user AS consultant ON job.consultant_id = consultant.id + INNER JOIN user ON application.user_id = user.id + INNER JOIN application_status ON application.status_id = application_status.id + WHERE user.id = ?`, + [userId] + ); + return rows; + } + + async getConsultantApplications(consultantId) { + const [rows] = await this.database.query( + `SELECT + application.id AS application_id, + application.job_id, + application.status_id, + job.title AS job_title, + consultant.id AS consultant_id, + consultant.email AS consultant_email, + user.id AS candidate_id, + user.email AS candidate_email, + application_status.label AS status_label, + company.name AS company_name + FROM ${this.table} AS application + INNER JOIN job ON application.job_id = job.id + INNER JOIN user AS consultant ON job.consultant_id = consultant.id + INNER JOIN user ON application.user_id = user.id + INNER JOIN application_status ON application.status_id = application_status.id + INNER JOIN company ON job.company_id = company.id + WHERE consultant.id = ? + ORDER BY company.name`, + [consultantId] + ); + return rows; + } + + // The U of CRUD - Update operation + async update(id, application) { + // Execute the SQL SELECT query to retrieve a specific application by its ID + const [result] = await this.database.query( + `UPDATE ${this.table} set ? WHERE id = ?`, + [application, id] + ); + + // Return the first row of the result, which represents the item + return result; + } + + // The D of CRUD - Delete operation + // TODO: Implement the delete operation to remove an application by its ID + + // async delete(id) { + // ... + // } +} + +module.exports = ApplicationManager; diff --git a/backend/src/models/ApplicationStatusManager.js b/backend/src/models/ApplicationStatusManager.js new file mode 100644 index 0000000..448d1c4 --- /dev/null +++ b/backend/src/models/ApplicationStatusManager.js @@ -0,0 +1,19 @@ +const AbstractManager = require("./AbstractManager"); + +class ApplicationStatusManager extends AbstractManager { + constructor() { + // Call the constructor of the parent class (AbstractManager) + // and pass the table name "application_status" as configuration + super({ table: "application_status" }); + } + + async readAll() { + // Execute the SQL SELECT query to retrieve all status from the "application_status" table + const [rows] = await this.database.query(`select * from ${this.table}`); + + // Return the array of status + return rows; + } +} + +module.exports = ApplicationStatusManager; diff --git a/backend/src/router.js b/backend/src/router.js index fafa0ee..f479526 100644 --- a/backend/src/router.js +++ b/backend/src/router.js @@ -12,6 +12,8 @@ const userControllers = require("./controllers/userControllers"); const jobControllers = require("./controllers/jobControllers"); const companyControllers = require("./controllers/companyControllers"); const roleControllers = require("./controllers/roleControllers"); +const applicationControllers = require("./controllers/applicationControllers"); +const applicationStatusControllers = require("./controllers/applicationStatusControllers"); const checkCredentials = require("./middleware/checkCredentials"); const checkAdmin = require("./middleware/checkAdmin"); @@ -23,11 +25,17 @@ const validateCompany = require("./validators/validateCompany"); const validateCV = require("./validators/validateCV"); const validateJob = require("./validators/validateJob"); +// ROUTES GET router.get("/jobs", jobControllers.browse); router.get("/locations", jobControllers.getLocations); router.get("/languages", jobControllers.getLanguages); router.get("/companies", companyControllers.browse); -router.get("/consultants", userControllers.getConsultant); +router.get( + "/consultants", + checkCredentials, + checkConsultant, + userControllers.getConsultant +); router.get("/roles", checkCredentials, checkAdmin, roleControllers.browse); router.get( "/candidates", @@ -36,8 +44,26 @@ router.get( userControllers.getCandidates ); router.get("/profile", checkCredentials, userControllers.getProfile); +router.get( + "/profile/applications", + checkCredentials, + applicationControllers.readProfileApplications +); +router.get( + "/applications/consultant", + checkCredentials, + checkConsultant, + applicationControllers.readConsultantApplications +); router.get("/jobs/all/latest", jobControllers.browseLatest); +router.get( + "/applicationStatus", + checkCredentials, + checkConsultant, + applicationStatusControllers.browse +); +// ROUTES GET BY ID router.get("/jobs/:id", jobControllers.read); router.get( "/companies/:id", @@ -76,7 +102,9 @@ router.post( validateCompany, companyControllers.add ); +router.post("/application", checkCredentials, applicationControllers.add); +// ROUTES DELETE router.delete( "/jobs/:id", checkCredentials, @@ -96,6 +124,7 @@ router.delete( userControllers.destroy ); +// ROUTES PUT router.put( "/companies/:id", checkCredentials, diff --git a/backend/src/tables.js b/backend/src/tables.js index c283a42..24ef145 100644 --- a/backend/src/tables.js +++ b/backend/src/tables.js @@ -7,12 +7,16 @@ const JobManager = require("./models/JobManager"); const CompanyManager = require("./models/CompanyManager"); const UserManager = require("./models/UserManager"); const RoleManager = require("./models/RoleManager"); +const ApplicationManager = require("./models/ApplicationManager"); +const ApplicationStatusManager = require("./models/ApplicationStatusManager"); const managers = [ JobManager, CompanyManager, UserManager, RoleManager, + ApplicationManager, + ApplicationStatusManager, // Add other managers here ]; diff --git a/frontend/src/components/CarouselJobs.jsx b/frontend/src/components/CarouselJobs.jsx index 82f8f95..b8d2155 100644 --- a/frontend/src/components/CarouselJobs.jsx +++ b/frontend/src/components/CarouselJobs.jsx @@ -41,7 +41,7 @@ function Carousel({ jobs }) { > {jobs.map((job) => (
- +
))} diff --git a/frontend/src/components/JobCard.jsx b/frontend/src/components/JobCard.jsx index 4f09d97..35ef55b 100644 --- a/frontend/src/components/JobCard.jsx +++ b/frontend/src/components/JobCard.jsx @@ -43,22 +43,23 @@ function JobCard({ job, cardStyle, refresh, isUserPage }) { >

{job.title}

- {access ? ( -
- - - - - -
+ + ) : ( + "" + )} + {!isUserPage ? ( + ) : ( )} diff --git a/frontend/src/components/SelectConsultant.jsx b/frontend/src/components/SelectConsultant.jsx index 9666ead..fd29880 100644 --- a/frontend/src/components/SelectConsultant.jsx +++ b/frontend/src/components/SelectConsultant.jsx @@ -5,37 +5,52 @@ import connexion from "../services/connexion"; import colorStyles from "../assets/selectStyle"; -function SelectConsultant({ label, url, criteria, handleSelect, name }) { - const [list, setList] = useState([]); - - const getList = async () => { - try { - const result = await connexion.get(`/${url}`).then((res) => res.data); - setList(result); - } catch (error) { - console.error(error); - } - }; +function SelectConsultant({ + label, + url, + criteria, + handleSelect, + name, + company, +}) { + const [options, setOptions] = useState([]); useEffect(() => { + const getList = async () => { + try { + const result = await connexion.get(`/${url}`); + const formattedOptions = result.data.map((item) => ({ + value: item.id, + label: item[criteria], + })); + setOptions(formattedOptions); + } catch (error) { + console.error(error); + } + }; + getList(); - }, []); + }, [url, criteria]); - const standardizeEvent = (option) => { - handleSelect({ target: { name, value: option.value } }); + const standardizeEvent = (selectedOption) => { + handleSelect({ + target: { name, value: selectedOption ? selectedOption.value : "" }, + }); }; + const value = company + ? options.find((option) => String(option.value) === String(company)) + : null; + return (