diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..9e5d7ce --- /dev/null +++ b/.gitmodules @@ -0,0 +1,6 @@ +[submodule "Candidate-Tracker-Server"] + path = Candidate-Tracker-Server + url = https://github.com/harshpatel23/Candidate-Tracker-Server.git +[submodule "Candidate-Tracker-App"] + path = Candidate-Tracker-App + url = https://github.com/shivaneej/Candidate-Tracker-App.git diff --git a/Candidate-Tracker-App b/Candidate-Tracker-App new file mode 160000 index 0000000..b68edad --- /dev/null +++ b/Candidate-Tracker-App @@ -0,0 +1 @@ +Subproject commit b68edad8b36cbac3946fe01d548a02308b358faa diff --git a/Candidate-Tracker-Server b/Candidate-Tracker-Server new file mode 160000 index 0000000..cb482c9 --- /dev/null +++ b/Candidate-Tracker-Server @@ -0,0 +1 @@ +Subproject commit cb482c9167a30247d96c76d002198e3482501b48 diff --git a/README.md b/README.md new file mode 100644 index 0000000..945235a --- /dev/null +++ b/README.md @@ -0,0 +1,60 @@ +
+

Candidate Tracker

+ +[![GitHub contributors](https://img.shields.io/badge/Contributors-3-blueviolet?style=flat-square)](#contributors)  +[![PRs Welcome](https://img.shields.io/badge/PRs-welcome-orange.svg?style=flat-square)](http://makeapullrequest.com)  +[![npm version](https://img.shields.io/badge/npm-v6.9.0-blue?style=flat-square)](https://www.npmjs.com/)  +[![Spring Boot](https://img.shields.io/badge/Made%20with-Spring%20Boot-brightgreen?style=flat-square)](https://start.spring.io/)  +[![Angular](https://img.shields.io/badge/Made%20with-Angular-red?style=flat-square)](https://angular.io/)  +[![Build](https://img.shields.io/badge/Build-Passing-green?style=flat-square)]() +
+ +* Candidate Tracker is web-based recruitment platform. It can be used to track profile of candidate and supports end to end recruitment process from picking up a candidate's profile to onboarding of the candidate. + + +## Features +* Dedicated dashboard for every user role. User role is automatically identified upon successful login. +* JWT based authentication and authorization. +* Support for virtual interview between candidate and interviewer using Google Meet. +* Email notifications for account creation, candidate hired or rejected. +* Calendar invite for interviews scheduled. +* Option to upload candidate CV. +* Search, sort and filter candidates. +* Statistics for custom duration. + +## Screenshots +    +

+    +
+ +## Technology Stack +* Java +* Angular 9 +* MySQL Database +* Spring Boot +* Gradle + +## Repositories +* [Candidate-Tracker-Server](https://github.com/harshpatel23/Candidate-Tracker-Server) +* [Candidate-Tracker-App](https://github.com/shivaneej/Candidate-Tracker-App) + +## How To Run +1. Clone the repository with all the submodule
+ `git clone --recurse-submodules ` +2. Import `database/candidate_tracker_schema` in MySQL Workbench. +3. Start the Spring Server by running
+ `./Candidate-Tracker-Server/gradlew/bootRun` +4. Navigate to `Candidate-Tracker-App`
+ `cd Candidate-Tracker-App` +4. Install npm dependencies by running
+ `npm install` +5. Start the Application Server by running
+ `ng serve` +6. Open `http://localhost:4200/` in your browser to view the application. + + +## Contributors +* [Shivanee Jaiswal](https://www.github.com/shivaneej) +* [Harsh Patel](https://github.com/harshpatel23) +* [Hitanshu Shah](https://github.com/hitanshu310) diff --git a/assets/calendar.png b/assets/calendar.png new file mode 100644 index 0000000..fa65005 Binary files /dev/null and b/assets/calendar.png differ diff --git a/assets/candidates.png b/assets/candidates.png new file mode 100644 index 0000000..4f2e9c8 Binary files /dev/null and b/assets/candidates.png differ diff --git a/assets/dashboard.png b/assets/dashboard.png new file mode 100644 index 0000000..4d9e14a Binary files /dev/null and b/assets/dashboard.png differ diff --git a/assets/interview.png b/assets/interview.png new file mode 100644 index 0000000..2b08376 Binary files /dev/null and b/assets/interview.png differ diff --git a/database/candidate_tracker_EER.pdf b/database/candidate_tracker_EER.pdf new file mode 100644 index 0000000..e440754 Binary files /dev/null and b/database/candidate_tracker_EER.pdf differ diff --git a/database/candidate_tracker_schema.sql b/database/candidate_tracker_schema.sql new file mode 100644 index 0000000..cab4d25 --- /dev/null +++ b/database/candidate_tracker_schema.sql @@ -0,0 +1,209 @@ +-- MySQL Workbench Forward Engineering + +SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0; +SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0; +SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='ONLY_FULL_GROUP_BY,STRICT_TRANS_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO,NO_ENGINE_SUBSTITUTION'; + +-- ----------------------------------------------------- +-- Schema candidate_tracker +-- ----------------------------------------------------- + +-- ----------------------------------------------------- +-- Schema candidate_tracker +-- ----------------------------------------------------- +CREATE SCHEMA IF NOT EXISTS `candidate_tracker` DEFAULT CHARACTER SET utf8 ; +USE `candidate_tracker` ; + +-- ----------------------------------------------------- +-- Table `candidate_tracker`.`roles` +-- ----------------------------------------------------- +CREATE TABLE IF NOT EXISTS `candidate_tracker`.`roles` ( + `role` VARCHAR(20) NOT NULL, + `role_string` VARCHAR(30) NOT NULL, + `h_level` FLOAT NOT NULL, + PRIMARY KEY (`role`), + UNIQUE INDEX `role_UNIQUE` (`role` ASC) VISIBLE, + UNIQUE INDEX `role_string_UNIQUE` (`role_string` ASC) VISIBLE) +ENGINE = InnoDB +DEFAULT CHARACTER SET = utf8; + + +-- ----------------------------------------------------- +-- Table `candidate_tracker`.`user` +-- ----------------------------------------------------- +CREATE TABLE IF NOT EXISTS `candidate_tracker`.`user` ( + `id` INT NOT NULL AUTO_INCREMENT, + `email` VARCHAR(45) NOT NULL, + `password` VARCHAR(255) NOT NULL, + `r_id` VARCHAR(20) NOT NULL, + `first_name` VARCHAR(45) NULL DEFAULT NULL, + `last_name` VARCHAR(45) NULL DEFAULT NULL, + `contact` VARCHAR(10) NULL DEFAULT NULL, + `is_active` TINYINT NULL DEFAULT '1', + `manager_id` INT NULL DEFAULT NULL, + PRIMARY KEY (`id`), + UNIQUE INDEX `id_UNIQUE` (`id` ASC) VISIBLE, + UNIQUE INDEX `email_UNIQUE` (`email` ASC) VISIBLE, + INDEX `fk_role_idx` (`r_id` ASC) VISIBLE, + CONSTRAINT `fk_role` + FOREIGN KEY (`r_id`) + REFERENCES `candidate_tracker`.`roles` (`role`), + CONSTRAINT `fk_manager_id` + FOREIGN KEY (`manager_id`) + REFERENCES `candidate_tracker`.`user` (`id`)) +ENGINE = InnoDB +AUTO_INCREMENT = 1 +DEFAULT CHARACTER SET = utf8; + + +-- ----------------------------------------------------- +-- Table `candidate_tracker`.`candidate` +-- ----------------------------------------------------- +CREATE TABLE IF NOT EXISTS `candidate_tracker`.`candidate` ( + `id` INT NOT NULL AUTO_INCREMENT, + `email` VARCHAR(45) NOT NULL, + `recruiter_id` INT NOT NULL, + `first_name` VARCHAR(45) NULL DEFAULT NULL, + `last_name` VARCHAR(45) NULL DEFAULT NULL, + `contact` VARCHAR(10) NULL DEFAULT NULL, + `address` TEXT NULL DEFAULT NULL, + `preferred_loc` VARCHAR(45) NULL DEFAULT NULL, + `ectc` INT NULL DEFAULT NULL, + `ctct` INT NULL DEFAULT NULL, + `source` VARCHAR(45) NULL DEFAULT NULL, + `cv` MEDIUMBLOB NULL DEFAULT NULL, + `current_round` INT NULL DEFAULT 0, + `status` ENUM('ready', 'hold', 'hired', 'rejected') NULL DEFAULT 'ready', + `last_updated` DATETIME NULL DEFAULT NULL, + PRIMARY KEY (`id`), + UNIQUE INDEX `email_UNIQUE` (`email` ASC) VISIBLE, + INDEX `fk_recruiter_id_idx` (`recruiter_id` ASC) VISIBLE, + CONSTRAINT `fk_recruiter_id` + FOREIGN KEY (`recruiter_id`) + REFERENCES `candidate_tracker`.`user` (`id`)) +ENGINE = InnoDB +DEFAULT CHARACTER SET = utf8; + + +-- ----------------------------------------------------- +-- Table `candidate_tracker`.`skills` +-- ----------------------------------------------------- +CREATE TABLE IF NOT EXISTS `candidate_tracker`.`skills` ( + `skill_id` INT NOT NULL AUTO_INCREMENT, + `skill_name` VARCHAR(45) NOT NULL, + PRIMARY KEY (`skill_id`)) +ENGINE = InnoDB +DEFAULT CHARACTER SET = utf8; + + +-- ----------------------------------------------------- +-- Table `candidate_tracker`.`candidate_skills` +-- ----------------------------------------------------- +CREATE TABLE IF NOT EXISTS `candidate_tracker`.`candidate_skills` ( + `candidate_id` INT NOT NULL, + `skill_id` INT NOT NULL, + PRIMARY KEY (`candidate_id`, `skill_id`), + INDEX `fk_skill_id_idx` (`skill_id` ASC) VISIBLE, + CONSTRAINT `fk_candidate_id` + FOREIGN KEY (`candidate_id`) + REFERENCES `candidate_tracker`.`candidate` (`id`), + CONSTRAINT `fk_skill_id` + FOREIGN KEY (`skill_id`) + REFERENCES `candidate_tracker`.`skills` (`skill_id`)) +ENGINE = InnoDB +DEFAULT CHARACTER SET = utf8; + + +-- ----------------------------------------------------- +-- Table `candidate_tracker`.`interview` +-- ----------------------------------------------------- +CREATE TABLE IF NOT EXISTS `candidate_tracker`.`interview` ( + `interview_id` INT NOT NULL AUTO_INCREMENT, + `candidate_id` INT NOT NULL, + `interviewer_id` INT NOT NULL, + `start_time` DATETIME NULL DEFAULT NULL, + `end_time` DATETIME NULL DEFAULT NULL, + `feedback` TEXT NULL DEFAULT NULL, + `updated_by` INT NULL DEFAULT NULL, + `round_no` INT NULL DEFAULT NULL, + `approval_status` ENUM('recruiter_approved', 'interviewer_approved', 'both_approved') NULL DEFAULT 'recruiter_approved', + `is_complete` TINYINT NULL DEFAULT '0', + PRIMARY KEY (`interview_id`), + INDEX `fk_interviewer_id_idx` (`interviewer_id` ASC) VISIBLE, + INDEX `fk_candidate_id_idx` (`candidate_id` ASC) VISIBLE, + INDEX `fk_updated_by_idx` (`updated_by` ASC) VISIBLE, + CONSTRAINT `fk_interview_candidate_id` + FOREIGN KEY (`candidate_id`) + REFERENCES `candidate_tracker`.`candidate` (`id`), + CONSTRAINT `fk_interview_interviewer_id` + FOREIGN KEY (`interviewer_id`) + REFERENCES `candidate_tracker`.`user` (`id`), + CONSTRAINT `fk_updated_by` + FOREIGN KEY (`updated_by`) + REFERENCES `candidate_tracker`.`user` (`id`)) +ENGINE = InnoDB +DEFAULT CHARACTER SET = utf8; + + +-- ----------------------------------------------------- +-- Table `candidate_tracker`.`interviewer_skills` +-- ----------------------------------------------------- +CREATE TABLE IF NOT EXISTS `candidate_tracker`.`interviewer_skills` ( + `interviewer_id` INT NOT NULL, + `skill_id` INT NOT NULL, + PRIMARY KEY (`interviewer_id`, `skill_id`), + INDEX `fk_skill_id_idx` (`skill_id` ASC) VISIBLE, + CONSTRAINT `fk_interviewer_id` + FOREIGN KEY (`interviewer_id`) + REFERENCES `candidate_tracker`.`user` (`id`), + CONSTRAINT `fk_skill_interviewer_id` + FOREIGN KEY (`skill_id`) + REFERENCES `candidate_tracker`.`skills` (`skill_id`)) +ENGINE = InnoDB +DEFAULT CHARACTER SET = utf8; + + +-- ----------------------------------------------------- +-- Table `candidate_tracker`.`user_closure` +-- ----------------------------------------------------- +CREATE TABLE IF NOT EXISTS `candidate_tracker`.`user_closure` ( + `mapping_id` INT NOT NULL AUTO_INCREMENT, + `parent_id` INT NULL DEFAULT NULL, + `child_id` INT NULL DEFAULT NULL, + `depth` INT NULL DEFAULT NULL, + PRIMARY KEY (`mapping_id`), + CONSTRAINT `fk_parent_id` + FOREIGN KEY (`parent_id`) + REFERENCES `candidate_tracker`.`user` (`id`), + CONSTRAINT `fk_child_id` + FOREIGN KEY (`child_id`) + REFERENCES `candidate_tracker`.`user` (`id`)) +ENGINE = InnoDB +DEFAULT CHARACTER SET = utf8; + +USE `candidate_tracker`; + +DELIMITER $$ +USE `candidate_tracker`$$ +CREATE +DEFINER=`root`@`localhost` +TRIGGER `candidate_tracker`.`user_AFTER_INSERT` +AFTER INSERT ON `candidate_tracker`.`user` +FOR EACH ROW +BEGIN + +insert into user_closure(parent_id,child_id,depth) values (new.id, new.id, 0); + +INSERT INTO user_closure(parent_id, child_id, depth) +SELECT p.parent_id, c.child_id, p.depth+c.depth+1 +FROM user_closure p, user_closure c +WHERE p.child_id = new.manager_id AND c.parent_id = new.id; + +END$$ + + +DELIMITER ; + +SET SQL_MODE=@OLD_SQL_MODE; +SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS; +SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS; diff --git a/database/populate_data.sql b/database/populate_data.sql new file mode 100644 index 0000000..f5d7f52 --- /dev/null +++ b/database/populate_data.sql @@ -0,0 +1,41 @@ +INSERT INTO `candidate_tracker`.`roles` (`role`,`role_string`,`h_level`) VALUES ('root','Root',1),('admin','Admin',2),('ops','OPS',3),('recruiter','Recruiter',4),('interviewer','Interviewer',4); + +INSERT into `candidate_tracker`.`skills` (`skill_name`) VALUES ('Java'),('Cyber Security'),('Artificial Intelligence'),('SQL'),('DS'),('Algorithms'); + +INSERT INTO `candidate_tracker`.`user` (`email`,`password`,`r_id`,`first_name`,`last_name`,`contact`) VALUES ('wissen@wissen.com','$2a$10$WB99cJN1ir.MgOOXgew6.eVrmvcxJnTDQMKRXteMJwoD22c6HeYR6','root','Wissen','Technology','123456789'); +INSERT INTO `candidate_tracker`.`user` (`email`,`password`,`r_id`,`first_name`,`last_name`,`contact`,`manager_id`) VALUES ('harsh.patel4@somaiya.edu','$2a$10$X7A6XWsoJx5cKNR.mTd/vuwbX3PWw8tKl8FaYqbBRt0l5FUbZvENq','admin','Harsh','Patel','1864513287',1); +INSERT INTO `candidate_tracker`.`user` (`email`,`password`,`r_id`,`first_name`,`last_name`,`contact`,`manager_id`) VALUES ('shivani@gmail.com','$2a$10$rh5Tp067kFIf3DTDdNRo/uZqjRbQ.bAao/iJSZlk5mC6k3bf4g7Cy','admin','Shivani','Jaiswal','8745852147',1); +INSERT INTO `candidate_tracker`.`user` (`email`,`password`,`r_id`,`first_name`,`last_name`,`contact`,`manager_id`) VALUES ('hitanshu@somaiya.edu','$2a$10$es0OyjA7a/dd3IJu5UgtsOIo0ydRpsffNY5udjQJcWEb.rYT95Pwq','ops','Hitanshu','Shah','8451284511',2); +INSERT INTO `candidate_tracker`.`user` (`email`,`password`,`r_id`,`first_name`,`last_name`,`contact`,`manager_id`) VALUES ('murtaza@gmail.com','$2a$10$10rXQLRvVPp0a3xbzkXZgOBmKW2HBzzok/JYv78Mmna0jT6d0XyMi','ops','Murtaza','Patrawala','8541484511',2); +INSERT INTO `candidate_tracker`.`user` (`email`,`password`,`r_id`,`first_name`,`last_name`,`contact`,`manager_id`) VALUES ('ojas@somaiya.edu','$2a$10$YuuxJUGrMMVn46tsKRH8YuCubD98ETKnuXE5tIFJeYphulfNn6rhi','recruiter','Ojas','Kapre','8451247511',4); +INSERT INTO `candidate_tracker`.`user` (`email`,`password`,`r_id`,`first_name`,`last_name`,`contact`,`manager_id`) VALUES ('tanay@somaiya.edu','$2a$10$1jHD2OsY3CSC7/TgCtjRyORsUnlPJOQ7x5p9WWq.2FihdnpQR8l/.','interviewer','Tanay','Raul','7141284511',5); + +INSERT INTO `candidate_tracker`.`candidate` (`email`,`recruiter_id`,`first_name`,`last_name`,`contact`,`address`,`preferred_loc`,`ectc`,`ctct`,`source`, `current_round`, `status`) +VALUES ('candidate1@gamil.com',6,'Manish','Potey','9224659941','Alibaug','Mumbai',4, 3, 'College Placement', 1, 'hold'); +INSERT INTO `candidate_tracker`.`candidate` (`email`,`recruiter_id`,`first_name`,`last_name`,`contact`,`address`,`preferred_loc`,`ectc`,`ctct`,`source`, `current_round`, `status`) +VALUES ('candidate2@gamil.com',6,'Hitansh','Shah','7506847230','Sion','Pune',5, 6, 'College Placement', 2, 'hold'); +INSERT INTO `candidate_tracker`.`candidate` (`email`,`recruiter_id`,`first_name`,`last_name`,`contact`,`address`,`preferred_loc`,`ectc`,`ctct`,`source`, `current_round`, `status`) +VALUES ('candidate3@gamil.com',6,'Jash','Gopnai','8659321547','Borivali','Bangalore',8, 8, 'College Placement', 0, 'ready'); +INSERT INTO `candidate_tracker`.`candidate` (`email`,`recruiter_id`,`first_name`,`last_name`,`contact`,`address`,`preferred_loc`,`ectc`,`ctct`,`source`, `current_round`, `status`) +VALUES ('candidate4@gamil.com',6,'Mikin','Padalia','9869710860','Rajkot','Surat',7, 6, 'College Placement', 1, 'hold'); +INSERT INTO `candidate_tracker`.`candidate` (`email`,`recruiter_id`,`first_name`,`last_name`,`contact`,`address`,`preferred_loc`,`ectc`,`ctct`,`source`, `current_round`, `status`) +VALUES ('candidate5@gamil.com',4,'Jugal','Bharmani','7786381542','Andheri','Mumbai',6, 6, 'College Placement', 2, 'hold'); + +INSERT INTO `candidate_tracker`.`candidate_skills` (`candidate_id`, `skill_id`) VALUES (1, 1), (1, 2), (1, 3), (1, 4); + +INSERT INTO `candidate_tracker`.`interview` (`candidate_id`, `interviewer_id`, `start_time`, `end_time`, `feedback`, `updated_by`, `round_no`) +VALUES (1, 7, '2020-07-15 10:30:00', '2020-07-15 12:30:00', 'Good', 7, 1); + +INSERT INTO `candidate_tracker`.`interview` (`candidate_id`, `interviewer_id`, `start_time`, `end_time`, `feedback`, `updated_by`, `round_no`) +VALUES (1, 7, '2020-08-15 14:30:00', '2020-08-15 16:30:00', 'Amazing', 7, 1); + +INSERT INTO `candidate_tracker`.`interview` (`candidate_id`, `interviewer_id`, `start_time`, `end_time`, `feedback`, `updated_by`, `round_no`) +VALUES (1, 7, '2020-10-15 16:00:00', '2020-07-15 18:00:00', 'Great', 7, 1); + +select * from candidate_tracker.roles; +select * from candidate_tracker.user; +select * from candidate_tracker.interviewuser_closure; + +# retreiving all children of an admin (Harsh -> id = 1) +use candidate_Tracker; +select first_name, last_name, email from user inner join user_closure on user.id = user_closure.child_id where user_closure.parent_id = 5;