diff --git a/docs/ER-DiagramFile.mwb b/docs/ER-DiagramFile.mwb deleted file mode 100644 index c05526f..0000000 Binary files a/docs/ER-DiagramFile.mwb and /dev/null differ diff --git a/docs/ER-DiagramFile.mwb.bak b/docs/ER-DiagramFile.mwb.bak deleted file mode 100644 index 24c783b..0000000 Binary files a/docs/ER-DiagramFile.mwb.bak and /dev/null differ diff --git a/docs/ER-Diagram v1.png b/docs/SqlDatabase/ER-Diagram v1.png similarity index 100% rename from docs/ER-Diagram v1.png rename to docs/SqlDatabase/ER-Diagram v1.png diff --git a/docs/ER-Diagram v2.png b/docs/SqlDatabase/ER-Diagram v2.png similarity index 100% rename from docs/ER-Diagram v2.png rename to docs/SqlDatabase/ER-Diagram v2.png diff --git a/docs/SqlDatabase/ER-Diagram v3.png b/docs/SqlDatabase/ER-Diagram v3.png new file mode 100644 index 0000000..af63917 Binary files /dev/null and b/docs/SqlDatabase/ER-Diagram v3.png differ diff --git a/docs/SqlDatabase/ER-Diagram v4.png b/docs/SqlDatabase/ER-Diagram v4.png new file mode 100644 index 0000000..bffbaf8 Binary files /dev/null and b/docs/SqlDatabase/ER-Diagram v4.png differ diff --git a/docs/SqlDatabase/ER-DiagramFile.mwb b/docs/SqlDatabase/ER-DiagramFile.mwb new file mode 100644 index 0000000..ee90f5e Binary files /dev/null and b/docs/SqlDatabase/ER-DiagramFile.mwb differ diff --git a/docs/SqlDatabase/ER-DiagramFile.mwb.bak b/docs/SqlDatabase/ER-DiagramFile.mwb.bak new file mode 100644 index 0000000..64d9533 Binary files /dev/null and b/docs/SqlDatabase/ER-DiagramFile.mwb.bak differ diff --git a/docs/SqlDatabase/Sql_script_v1.1.sql b/docs/SqlDatabase/Sql_script_v1.1.sql new file mode 100644 index 0000000..6310455 --- /dev/null +++ b/docs/SqlDatabase/Sql_script_v1.1.sql @@ -0,0 +1,203 @@ +-- 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 apbaluna +-- ----------------------------------------------------- + +-- ----------------------------------------------------- +-- Schema apbaluna +-- ----------------------------------------------------- +CREATE SCHEMA IF NOT EXISTS `apbaluna` DEFAULT CHARACTER SET utf8 ; +USE `apbaluna` ; + +-- ----------------------------------------------------- +-- Table `apbaluna`.`Charities` +-- ----------------------------------------------------- +CREATE TABLE IF NOT EXISTS `apbaluna`.`Charities` ( + `UUID_charities` CHAR(36) NOT NULL, + `org_number` VARCHAR(255) NOT NULL, + `charity_name` VARCHAR(255) NOT NULL, + `charity_description` VARCHAR(255) NOT NULL, + `charity_link` VARCHAR(255) NOT NULL, + `pre_approved` TINYINT NOT NULL, + `status` VARCHAR(255) NOT NULL, + PRIMARY KEY (`UUID_charities`)) +ENGINE = InnoDB; + + +-- ----------------------------------------------------- +-- Table `apbaluna`.`User` +-- ----------------------------------------------------- +CREATE TABLE IF NOT EXISTS `apbaluna`.`User` ( + `UUID_User` CHAR(36) NOT NULL, + `user_name` VARCHAR(255) NOT NULL, + `user_email` VARCHAR(255) NOT NULL, + `role` VARCHAR(45) NOT NULL, + PRIMARY KEY (`UUID_User`)) +ENGINE = InnoDB; + + +-- ----------------------------------------------------- +-- Table `apbaluna`.`Donations` +-- ----------------------------------------------------- +CREATE TABLE IF NOT EXISTS `apbaluna`.`Donations` ( + `UUID_Donations` CHAR(36) NOT NULL, + `amount` DECIMAL NOT NULL, + `date` DATE NOT NULL, + `charity_id` CHAR(36) NOT NULL, + `user_id` CHAR(36) NOT NULL, + PRIMARY KEY (`UUID_Donations`), + INDEX `fk_Donations_Charities_idx` (`charity_id` ASC) VISIBLE, + INDEX `fk_Donations_User1_idx` (`user_id` ASC) VISIBLE, + CONSTRAINT `fk_Donations_Charities` + FOREIGN KEY (`charity_id`) + REFERENCES `apbaluna`.`Charities` (`UUID_charities`) + ON DELETE NO ACTION + ON UPDATE NO ACTION, + CONSTRAINT `fk_Donations_User1` + FOREIGN KEY (`user_id`) + REFERENCES `apbaluna`.`User` (`UUID_User`) + ON DELETE NO ACTION + ON UPDATE NO ACTION) +ENGINE = InnoDB; + + +-- ----------------------------------------------------- +-- Table `apbaluna`.`Settings` +-- ----------------------------------------------------- +CREATE TABLE IF NOT EXISTS `apbaluna`.`Settings` ( + `User_UUID_User` CHAR(36) NOT NULL, + `isAnonymous` TINYINT NOT NULL, + `language` VARCHAR(45) NOT NULL, + `lightmode` TINYINT NOT NULL, + PRIMARY KEY (`User_UUID_User`), + INDEX `fk_Settings_User1_idx` (`User_UUID_User` ASC) VISIBLE, + CONSTRAINT `fk_Settings_User1` + FOREIGN KEY (`User_UUID_User`) + REFERENCES `apbaluna`.`User` (`UUID_User`) + ON DELETE NO ACTION + ON UPDATE NO ACTION) +ENGINE = InnoDB; + + +-- ----------------------------------------------------- +-- Table `apbaluna`.`Messages` +-- ----------------------------------------------------- +CREATE TABLE IF NOT EXISTS `apbaluna`.`Messages` ( + `UUID_message` CHAR(36) NOT NULL, + `message_title` VARCHAR(255) NOT NULL, + `message_content` VARCHAR(255) NOT NULL, + `message_date` DATE NOT NULL, + `sender_user_id` CHAR(36) NULL, + `sender_charity_id` CHAR(36) NULL, + `user_id` CHAR(36) NOT NULL, + PRIMARY KEY (`UUID_message`), + INDEX `fk_Messages_User1_idx` (`sender_user_id` ASC) VISIBLE, + INDEX `fk_Messages_Charities1_idx` (`sender_charity_id` ASC) VISIBLE, + INDEX `fk_Messages_User2_idx` (`user_id` ASC) VISIBLE, + CONSTRAINT `fk_Messages_User1` + FOREIGN KEY (`sender_user_id`) + REFERENCES `apbaluna`.`User` (`UUID_User`) + ON DELETE NO ACTION + ON UPDATE NO ACTION, + CONSTRAINT `fk_Messages_Charities1` + FOREIGN KEY (`sender_charity_id`) + REFERENCES `apbaluna`.`Charities` (`UUID_charities`) + ON DELETE NO ACTION + ON UPDATE NO ACTION, + CONSTRAINT `fk_Messages_User2` + FOREIGN KEY (`user_id`) + REFERENCES `apbaluna`.`User` (`UUID_User`) + ON DELETE NO ACTION + ON UPDATE NO ACTION) +ENGINE = InnoDB; + + +-- ----------------------------------------------------- +-- Table `apbaluna`.`Feedback` +-- ----------------------------------------------------- +CREATE TABLE IF NOT EXISTS `apbaluna`.`Feedback` ( + `UUID_feedback` CHAR(36) NOT NULL, + `feedback_comment` VARCHAR(255) NOT NULL, + `feedback_date` DATE NOT NULL, + `isAnonymous` TINYINT NOT NULL, + `charity_id` CHAR(36) NOT NULL, + `user_id` CHAR(36) NOT NULL, + PRIMARY KEY (`UUID_feedback`), + INDEX `fk_Feedback_Charities1_idx` (`charity_id` ASC) VISIBLE, + INDEX `fk_Feedback_User1_idx` (`user_id` ASC) VISIBLE, + CONSTRAINT `fk_Feedback_Charities1` + FOREIGN KEY (`charity_id`) + REFERENCES `apbaluna`.`Charities` (`UUID_charities`) + ON DELETE NO ACTION + ON UPDATE NO ACTION, + CONSTRAINT `fk_Feedback_User1` + FOREIGN KEY (`user_id`) + REFERENCES `apbaluna`.`User` (`UUID_User`) + ON DELETE NO ACTION + ON UPDATE NO ACTION) +ENGINE = InnoDB; + + +-- ----------------------------------------------------- +-- Table `apbaluna`.`Categories` +-- ----------------------------------------------------- +CREATE TABLE IF NOT EXISTS `apbaluna`.`Categories` ( + `category_id` INT NOT NULL AUTO_INCREMENT, + `category` VARCHAR(255) NOT NULL, + PRIMARY KEY (`category_id`)) +ENGINE = InnoDB; + + +-- ----------------------------------------------------- +-- Table `apbaluna`.`Charity_Categories` +-- ----------------------------------------------------- +CREATE TABLE IF NOT EXISTS `apbaluna`.`Charity_Categories` ( + `Categories_category_id` INT NOT NULL, + `Charities_UUID_charities` CHAR(36) NOT NULL, + PRIMARY KEY (`Categories_category_id`, `Charities_UUID_charities`), + INDEX `fk_Categories_has_Charities_Charities1_idx` (`Charities_UUID_charities` ASC) VISIBLE, + INDEX `fk_Categories_has_Charities_Categories1_idx` (`Categories_category_id` ASC) VISIBLE, + CONSTRAINT `fk_Categories_has_Charities_Categories1` + FOREIGN KEY (`Categories_category_id`) + REFERENCES `apbaluna`.`Categories` (`category_id`) + ON DELETE NO ACTION + ON UPDATE NO ACTION, + CONSTRAINT `fk_Categories_has_Charities_Charities1` + FOREIGN KEY (`Charities_UUID_charities`) + REFERENCES `apbaluna`.`Charities` (`UUID_charities`) + ON DELETE NO ACTION + ON UPDATE NO ACTION) +ENGINE = InnoDB; + + +-- ----------------------------------------------------- +-- Table `apbaluna`.`CharityUsers` +-- ----------------------------------------------------- +CREATE TABLE IF NOT EXISTS `apbaluna`.`CharityUsers` ( + `Charities_UUID_charities` CHAR(36) NOT NULL, + `User_UUID_User` CHAR(36) NOT NULL, + PRIMARY KEY (`Charities_UUID_charities`, `User_UUID_User`), + INDEX `fk_Charities_has_User_User1_idx` (`User_UUID_User` ASC) VISIBLE, + INDEX `fk_Charities_has_User_Charities1_idx` (`Charities_UUID_charities` ASC) VISIBLE, + CONSTRAINT `fk_Charities_has_User_Charities1` + FOREIGN KEY (`Charities_UUID_charities`) + REFERENCES `apbaluna`.`Charities` (`UUID_charities`) + ON DELETE NO ACTION + ON UPDATE NO ACTION, + CONSTRAINT `fk_Charities_has_User_User1` + FOREIGN KEY (`User_UUID_User`) + REFERENCES `apbaluna`.`User` (`UUID_User`) + ON DELETE NO ACTION + ON UPDATE NO ACTION) +ENGINE = InnoDB; + +USE `apbaluna`; + +SET SQL_MODE=@OLD_SQL_MODE; +SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS; +SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS; diff --git a/docs/SqlDatabase/Sql_script_v1.2.sql b/docs/SqlDatabase/Sql_script_v1.2.sql new file mode 100644 index 0000000..4acb758 --- /dev/null +++ b/docs/SqlDatabase/Sql_script_v1.2.sql @@ -0,0 +1,203 @@ +-- 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 apbaluna +-- ----------------------------------------------------- + +-- ----------------------------------------------------- +-- Schema apbaluna +-- ----------------------------------------------------- +CREATE SCHEMA IF NOT EXISTS `apbaluna` DEFAULT CHARACTER SET utf8 ; +USE `apbaluna` ; + +-- ----------------------------------------------------- +-- Table `apbaluna`.`Charities` +-- ----------------------------------------------------- +CREATE TABLE IF NOT EXISTS `apbaluna`.`Charities` ( + `UUID_charities` CHAR(36) NOT NULL, + `org_number` VARCHAR(255) NOT NULL, + `charity_name` VARCHAR(255) NOT NULL, + `charity_description` VARCHAR(255) NOT NULL, + `charity_link` VARCHAR(255) NOT NULL, + `pre_approved` TINYINT NOT NULL, + `status` VARCHAR(255) NOT NULL, + PRIMARY KEY (`UUID_charities`), + UNIQUE INDEX `org_number_UNIQUE` (`org_number` ASC) VISIBLE) +ENGINE = InnoDB; + + +-- ----------------------------------------------------- +-- Table `apbaluna`.`User` +-- ----------------------------------------------------- +CREATE TABLE IF NOT EXISTS `apbaluna`.`User` ( + `UUID_User` CHAR(36) NOT NULL, + `user_name` VARCHAR(255) NOT NULL, + `user_email` VARCHAR(255) NOT NULL, + `role` VARCHAR(45) NOT NULL, + PRIMARY KEY (`UUID_User`)) +ENGINE = InnoDB; + + +-- ----------------------------------------------------- +-- Table `apbaluna`.`Donations` +-- ----------------------------------------------------- +CREATE TABLE IF NOT EXISTS `apbaluna`.`Donations` ( + `UUID_Donations` CHAR(36) NOT NULL, + `amount` DECIMAL NOT NULL, + `date` DATE NOT NULL, + `charity_id` CHAR(36) NOT NULL, + `user_id` CHAR(36) NOT NULL, + PRIMARY KEY (`UUID_Donations`), + INDEX `fk_Donations_Charities_idx` (`charity_id` ASC) VISIBLE, + INDEX `fk_Donations_User1_idx` (`user_id` ASC) VISIBLE, + CONSTRAINT `fk_Donations_Charities` + FOREIGN KEY (`charity_id`) + REFERENCES `apbaluna`.`Charities` (`UUID_charities`) + ON DELETE NO ACTION + ON UPDATE NO ACTION, + CONSTRAINT `fk_Donations_User1` + FOREIGN KEY (`user_id`) + REFERENCES `apbaluna`.`User` (`UUID_User`) + ON DELETE NO ACTION + ON UPDATE NO ACTION) +ENGINE = InnoDB; + + +-- ----------------------------------------------------- +-- Table `apbaluna`.`Settings` +-- ----------------------------------------------------- +CREATE TABLE IF NOT EXISTS `apbaluna`.`Settings` ( + `User_UUID_User` CHAR(36) NOT NULL, + `isAnonymous` TINYINT NOT NULL, + `language` VARCHAR(45) NOT NULL, + `lightmode` TINYINT NOT NULL, + PRIMARY KEY (`User_UUID_User`), + INDEX `fk_Settings_User1_idx` (`User_UUID_User` ASC) VISIBLE, + CONSTRAINT `fk_Settings_User1` + FOREIGN KEY (`User_UUID_User`) + REFERENCES `apbaluna`.`User` (`UUID_User`) + ON DELETE NO ACTION + ON UPDATE NO ACTION) +ENGINE = InnoDB; + + +-- ----------------------------------------------------- +-- Table `apbaluna`.`Messages` +-- ----------------------------------------------------- +CREATE TABLE IF NOT EXISTS `apbaluna`.`Messages` ( + `UUID_message` CHAR(36) NOT NULL, + `message_title` VARCHAR(255) NOT NULL, + `message_content` VARCHAR(255) NOT NULL, + `message_date` DATE NOT NULL, + `sender_user_id` CHAR(36) NULL, + `sender_charity_id` CHAR(36) NULL, + `user_id` CHAR(36) NOT NULL, + PRIMARY KEY (`UUID_message`), + INDEX `fk_Messages_User1_idx` (`sender_user_id` ASC) VISIBLE, + INDEX `fk_Messages_Charities1_idx` (`sender_charity_id` ASC) VISIBLE, + INDEX `fk_Messages_User2_idx` (`user_id` ASC) VISIBLE, + CONSTRAINT `fk_Messages_User1` + FOREIGN KEY (`sender_user_id`) + REFERENCES `apbaluna`.`User` (`UUID_User`) + ON DELETE NO ACTION + ON UPDATE NO ACTION, + CONSTRAINT `fk_Messages_Charities1` + FOREIGN KEY (`sender_charity_id`) + REFERENCES `apbaluna`.`Charities` (`UUID_charities`) + ON DELETE NO ACTION + ON UPDATE NO ACTION, + CONSTRAINT `fk_Messages_User2` + FOREIGN KEY (`user_id`) + REFERENCES `apbaluna`.`User` (`UUID_User`) + ON DELETE NO ACTION + ON UPDATE NO ACTION) +ENGINE = InnoDB; + + +-- ----------------------------------------------------- +-- Table `apbaluna`.`Feedback` +-- ----------------------------------------------------- +CREATE TABLE IF NOT EXISTS `apbaluna`.`Feedback` ( + `UUID_feedback` CHAR(36) NOT NULL, + `feedback_comment` VARCHAR(255) NOT NULL, + `feedback_date` DATE NOT NULL, + `isAnonymous` TINYINT NOT NULL, + `charity_id` CHAR(36) NOT NULL, + `user_id` CHAR(36) NOT NULL, + PRIMARY KEY (`UUID_feedback`), + INDEX `fk_Feedback_Charities1_idx` (`charity_id` ASC) VISIBLE, + INDEX `fk_Feedback_User1_idx` (`user_id` ASC) VISIBLE, + CONSTRAINT `fk_Feedback_Charities1` + FOREIGN KEY (`charity_id`) + REFERENCES `apbaluna`.`Charities` (`UUID_charities`) + ON DELETE NO ACTION + ON UPDATE NO ACTION, + CONSTRAINT `fk_Feedback_User1` + FOREIGN KEY (`user_id`) + REFERENCES `apbaluna`.`User` (`UUID_User`) + ON DELETE NO ACTION + ON UPDATE NO ACTION) +ENGINE = InnoDB; + + +-- ----------------------------------------------------- +-- Table `apbaluna`.`Categories` +-- ----------------------------------------------------- +CREATE TABLE IF NOT EXISTS `apbaluna`.`Categories` ( + `category_id` INT NOT NULL AUTO_INCREMENT, + `category` VARCHAR(255) NOT NULL, + PRIMARY KEY (`category_id`)) +ENGINE = InnoDB; + + +-- ----------------------------------------------------- +-- Table `apbaluna`.`Charity_Categories` +-- ----------------------------------------------------- +CREATE TABLE IF NOT EXISTS `apbaluna`.`Charity_Categories` ( + `Categories_category_id` INT NOT NULL, + `Charities_UUID_charities` CHAR(36) NOT NULL, + PRIMARY KEY (`Categories_category_id`, `Charities_UUID_charities`), + INDEX `fk_Categories_has_Charities_Charities1_idx` (`Charities_UUID_charities` ASC) VISIBLE, + INDEX `fk_Categories_has_Charities_Categories1_idx` (`Categories_category_id` ASC) VISIBLE, + CONSTRAINT `fk_Categories_has_Charities_Categories1` + FOREIGN KEY (`Categories_category_id`) + REFERENCES `apbaluna`.`Categories` (`category_id`) + ON DELETE NO ACTION + ON UPDATE NO ACTION, + CONSTRAINT `fk_Categories_has_Charities_Charities1` + FOREIGN KEY (`Charities_UUID_charities`) + REFERENCES `apbaluna`.`Charities` (`UUID_charities`) + ON DELETE NO ACTION + ON UPDATE NO ACTION) +ENGINE = InnoDB; + + +-- ----------------------------------------------------- +-- Table `apbaluna`.`CharityUsers` +-- ----------------------------------------------------- +CREATE TABLE IF NOT EXISTS `apbaluna`.`CharityUsers` ( + `Charities_UUID_charities` CHAR(36) NOT NULL, + `User_UUID_User` CHAR(36) NOT NULL, + PRIMARY KEY (`Charities_UUID_charities`, `User_UUID_User`), + INDEX `fk_Charities_has_User_User1_idx` (`User_UUID_User` ASC) VISIBLE, + INDEX `fk_Charities_has_User_Charities1_idx` (`Charities_UUID_charities` ASC) VISIBLE, + CONSTRAINT `fk_Charities_has_User_Charities1` + FOREIGN KEY (`Charities_UUID_charities`) + REFERENCES `apbaluna`.`Charities` (`UUID_charities`) + ON DELETE NO ACTION + ON UPDATE NO ACTION, + CONSTRAINT `fk_Charities_has_User_User1` + FOREIGN KEY (`User_UUID_User`) + REFERENCES `apbaluna`.`User` (`UUID_User`) + ON DELETE NO ACTION + ON UPDATE NO ACTION) +ENGINE = InnoDB; + + +SET SQL_MODE=@OLD_SQL_MODE; +SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS; +SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS; diff --git a/docs/SqlDatabase/Sql_scriptv1.0.sql b/docs/SqlDatabase/Sql_scriptv1.0.sql new file mode 100644 index 0000000..529aac8 --- /dev/null +++ b/docs/SqlDatabase/Sql_scriptv1.0.sql @@ -0,0 +1,213 @@ +-- 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 apbaluna +-- ----------------------------------------------------- + +-- ----------------------------------------------------- +-- Schema apbaluna +-- ----------------------------------------------------- +CREATE SCHEMA IF NOT EXISTS `apbaluna` DEFAULT CHARACTER SET utf8 ; +USE `apbaluna` ; + +-- ----------------------------------------------------- +-- Table `apbaluna`.`Charities` +-- ----------------------------------------------------- +CREATE TABLE IF NOT EXISTS `apbaluna`.`Charities` ( + `UUID_charities` CHAR(36) NOT NULL, + `org_number` VARCHAR(255) NOT NULL, + `charity_name` VARCHAR(255) NOT NULL, + `charity_description` VARCHAR(255) NOT NULL, + `charity_link` VARCHAR(255) NOT NULL, + `pre_approved` TINYINT NOT NULL, + `status` VARCHAR(255) NOT NULL, + PRIMARY KEY (`UUID_charities`)) +ENGINE = InnoDB; + + +-- ----------------------------------------------------- +-- Table `apbaluna`.`User` +-- ----------------------------------------------------- +CREATE TABLE IF NOT EXISTS `apbaluna`.`User` ( + `UUID_User` CHAR(36) NOT NULL, + `user_name` VARCHAR(255) NOT NULL, + `user_email` VARCHAR(255) NOT NULL, + `role` VARCHAR(45) NOT NULL, + PRIMARY KEY (`UUID_User`)) +ENGINE = InnoDB; + + +-- ----------------------------------------------------- +-- Table `apbaluna`.`Donations` +-- ----------------------------------------------------- +CREATE TABLE IF NOT EXISTS `apbaluna`.`Donations` ( + `UUID_Donations` CHAR(36) NOT NULL, + `amount` DECIMAL NOT NULL, + `date` DATE NOT NULL, + `charity_id` CHAR(36) NOT NULL, + `user_id` CHAR(36) NOT NULL, + PRIMARY KEY (`UUID_Donations`), + INDEX `fk_Donations_Charities_idx` (`charity_id` ASC) VISIBLE, + INDEX `fk_Donations_User1_idx` (`user_id` ASC) VISIBLE, + CONSTRAINT `fk_Donations_Charities` + FOREIGN KEY (`charity_id`) + REFERENCES `apbaluna`.`Charities` (`UUID_charities`) + ON DELETE NO ACTION + ON UPDATE NO ACTION, + CONSTRAINT `fk_Donations_User1` + FOREIGN KEY (`user_id`) + REFERENCES `apbaluna`.`User` (`UUID_User`) + ON DELETE NO ACTION + ON UPDATE NO ACTION) +ENGINE = InnoDB; + + +-- ----------------------------------------------------- +-- Table `apbaluna`.`Settings` +-- ----------------------------------------------------- +CREATE TABLE IF NOT EXISTS `apbaluna`.`Settings` ( + `User_UUID_User` CHAR(36) NOT NULL, + `isAnonymous` TINYINT NOT NULL, + `language` VARCHAR(45) NOT NULL, + `lightmode` TINYINT NOT NULL, + PRIMARY KEY (`User_UUID_User`), + INDEX `fk_Settings_User1_idx` (`User_UUID_User` ASC) VISIBLE, + CONSTRAINT `fk_Settings_User1` + FOREIGN KEY (`User_UUID_User`) + REFERENCES `apbaluna`.`User` (`UUID_User`) + ON DELETE NO ACTION + ON UPDATE NO ACTION) +ENGINE = InnoDB; + + +-- ----------------------------------------------------- +-- Table `apbaluna`.`Messages` +-- ----------------------------------------------------- +CREATE TABLE IF NOT EXISTS `apbaluna`.`Messages` ( + `UUID_message` CHAR(36) NOT NULL, + `message_title` VARCHAR(255) NOT NULL, + `message_content` VARCHAR(255) NOT NULL, + `message_date` DATE NOT NULL, + `sender_user_id` CHAR(36) NULL, + `sender_charity_id` CHAR(36) NULL, + `user_id` CHAR(36) NOT NULL, + PRIMARY KEY (`UUID_message`), + INDEX `fk_Messages_User1_idx` (`sender_user_id` ASC) VISIBLE, + INDEX `fk_Messages_Charities1_idx` (`sender_charity_id` ASC) VISIBLE, + INDEX `fk_Messages_User2_idx` (`user_id` ASC) VISIBLE, + CONSTRAINT `fk_Messages_User1` + FOREIGN KEY (`sender_user_id`) + REFERENCES `apbaluna`.`User` (`UUID_User`) + ON DELETE NO ACTION + ON UPDATE NO ACTION, + CONSTRAINT `fk_Messages_Charities1` + FOREIGN KEY (`sender_charity_id`) + REFERENCES `apbaluna`.`Charities` (`UUID_charities`) + ON DELETE NO ACTION + ON UPDATE NO ACTION, + CONSTRAINT `fk_Messages_User2` + FOREIGN KEY (`user_id`) + REFERENCES `apbaluna`.`User` (`UUID_User`) + ON DELETE NO ACTION + ON UPDATE NO ACTION) +ENGINE = InnoDB; + + +-- ----------------------------------------------------- +-- Table `apbaluna`.`Feedback` +-- ----------------------------------------------------- +CREATE TABLE IF NOT EXISTS `apbaluna`.`Feedback` ( + `UUID_feedback` CHAR(36) NOT NULL, + `feedback_comment` VARCHAR(255) NOT NULL, + `feedback_date` DATE NOT NULL, + `isAnonymous` TINYINT NOT NULL, + `charity_id` CHAR(36) NOT NULL, + `user_id` CHAR(36) NOT NULL, + PRIMARY KEY (`UUID_feedback`), + INDEX `fk_Feedback_Charities1_idx` (`charity_id` ASC) VISIBLE, + INDEX `fk_Feedback_User1_idx` (`user_id` ASC) VISIBLE, + CONSTRAINT `fk_Feedback_Charities1` + FOREIGN KEY (`charity_id`) + REFERENCES `apbaluna`.`Charities` (`UUID_charities`) + ON DELETE NO ACTION + ON UPDATE NO ACTION, + CONSTRAINT `fk_Feedback_User1` + FOREIGN KEY (`user_id`) + REFERENCES `apbaluna`.`User` (`UUID_User`) + ON DELETE NO ACTION + ON UPDATE NO ACTION) +ENGINE = InnoDB; + + +-- ----------------------------------------------------- +-- Table `apbaluna`.`Categories` +-- ----------------------------------------------------- +CREATE TABLE IF NOT EXISTS `apbaluna`.`Categories` ( + `category_id` INT NOT NULL AUTO_INCREMENT, + `category` VARCHAR(255) NOT NULL, + PRIMARY KEY (`category_id`)) +ENGINE = InnoDB; + + +-- ----------------------------------------------------- +-- Table `apbaluna`.`Charity_Categories` +-- ----------------------------------------------------- +CREATE TABLE IF NOT EXISTS `apbaluna`.`Charity_Categories` ( + `Categories_category_id` INT NOT NULL, + `Charities_UUID_charities` CHAR(36) NOT NULL, + PRIMARY KEY (`Categories_category_id`, `Charities_UUID_charities`), + INDEX `fk_Categories_has_Charities_Charities1_idx` (`Charities_UUID_charities` ASC) VISIBLE, + INDEX `fk_Categories_has_Charities_Categories1_idx` (`Categories_category_id` ASC) VISIBLE, + CONSTRAINT `fk_Categories_has_Charities_Categories1` + FOREIGN KEY (`Categories_category_id`) + REFERENCES `apbaluna`.`Categories` (`category_id`) + ON DELETE NO ACTION + ON UPDATE NO ACTION, + CONSTRAINT `fk_Categories_has_Charities_Charities1` + FOREIGN KEY (`Charities_UUID_charities`) + REFERENCES `apbaluna`.`Charities` (`UUID_charities`) + ON DELETE NO ACTION + ON UPDATE NO ACTION) +ENGINE = InnoDB; + + +-- ----------------------------------------------------- +-- Table `apbaluna`.`CharityUsers` +-- ----------------------------------------------------- +CREATE TABLE IF NOT EXISTS `apbaluna`.`CharityUsers` ( + `Charities_UUID_charities` CHAR(36) NOT NULL, + `User_UUID_User` CHAR(36) NOT NULL, + PRIMARY KEY (`Charities_UUID_charities`, `User_UUID_User`), + INDEX `fk_Charities_has_User_User1_idx` (`User_UUID_User` ASC) VISIBLE, + INDEX `fk_Charities_has_User_Charities1_idx` (`Charities_UUID_charities` ASC) VISIBLE, + CONSTRAINT `fk_Charities_has_User_Charities1` + FOREIGN KEY (`Charities_UUID_charities`) + REFERENCES `apbaluna`.`Charities` (`UUID_charities`) + ON DELETE NO ACTION + ON UPDATE NO ACTION, + CONSTRAINT `fk_Charities_has_User_User1` + FOREIGN KEY (`User_UUID_User`) + REFERENCES `apbaluna`.`User` (`UUID_User`) + ON DELETE NO ACTION + ON UPDATE NO ACTION) +ENGINE = InnoDB; + +USE `apbaluna`; + +DELIMITER $$ +USE `apbaluna`$$ +CREATE DEFINER = CURRENT_USER TRIGGER `apbaluna`.`User_AFTER_INSERT` AFTER INSERT ON `User` FOR EACH ROW +BEGIN + INSERT INTO apbaluna.Settings (User_UUID_User, isAnonymous, language, lightmode) VALUES (NEW.UUID_User, 0, 'English', 0); +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/helpmehelpapplication/.gitignore b/helpmehelpapplication/.gitignore index e673575..3fbc17a 100644 --- a/helpmehelpapplication/.gitignore +++ b/helpmehelpapplication/.gitignore @@ -1,2 +1,4 @@ .idea/ -target/ \ No newline at end of file +target/ +helpmehelpapplication/src/main/java/ntnu/systemutvikling/team6/database/DatabaseConnection.java +helpmehelpapplication/src/main/resources/fxml/test/ diff --git a/helpmehelpapplication/pom.xml b/helpmehelpapplication/pom.xml index 347518e..ec74b68 100644 --- a/helpmehelpapplication/pom.xml +++ b/helpmehelpapplication/pom.xml @@ -62,6 +62,13 @@ 2.4.240 test + + + org.mockito + mockito-junit-jupiter + 5.23.0 + test + @@ -111,6 +118,4 @@ - - \ No newline at end of file diff --git a/helpmehelpapplication/src/main/java/ntnu/systemutvikling/team6/HmHApplication.java b/helpmehelpapplication/src/main/java/ntnu/systemutvikling/team6/HmHApplication.java index 293aa2d..b910eb3 100644 --- a/helpmehelpapplication/src/main/java/ntnu/systemutvikling/team6/HmHApplication.java +++ b/helpmehelpapplication/src/main/java/ntnu/systemutvikling/team6/HmHApplication.java @@ -3,20 +3,19 @@ import static javafx.application.Application.launch; import java.net.http.HttpClient; -import java.util.Objects; import javafx.application.Application; -import javafx.fxml.FXMLLoader; -import javafx.scene.Scene; -import javafx.scene.image.Image; import javafx.stage.Stage; -import ntnu.systemutvikling.team6.database.DatabaseManager; +import ntnu.systemutvikling.team6.database.DatabaseConnection; +import ntnu.systemutvikling.team6.database.DatabaseSetup; import ntnu.systemutvikling.team6.models.Charity; import ntnu.systemutvikling.team6.models.CharityRegistry; import ntnu.systemutvikling.team6.scraper.APICharityScraper; +import ntnu.systemutvikling.team6.service.APIToDatabaseService; public class HmHApplication extends Application { @Override public void start(Stage stage) throws Exception { + /* FXMLLoader fxmlLoader = new FXMLLoader(HmHApplication.class.getResource("/fxml/frontPage.fxml")); Scene scene = new Scene(fxmlLoader.load()); @@ -31,13 +30,16 @@ public void start(Stage stage) throws Exception { stage.setFullScreen(true); stage.show(); + */ + } @Override public void init() { /* Test and create tables to MySQL if ain't any */ try { - DatabaseManager db = new DatabaseManager(); + DatabaseConnection conn = new DatabaseConnection(); + DatabaseSetup db = new DatabaseSetup(conn); db.testConnection(); db.createTables(); } catch (Exception e) { @@ -47,7 +49,8 @@ public void init() { try { HttpClient https = HttpClient.newHttpClient(); APICharityScraper scraper = new APICharityScraper(https); - DatabaseManager db = new DatabaseManager(); + DatabaseConnection conn = new DatabaseConnection(); + APIToDatabaseService db = new APIToDatabaseService(conn); if (scraper.checkConnection()) { CharityRegistry charityRegistry = scraper.parseJSON(scraper.getJSONData()); @@ -59,6 +62,7 @@ public void init() { } catch (Exception e) { e.printStackTrace(); } + System.out.println("-- \n Init complete \n --"); } public static void main(String[] args) { diff --git a/helpmehelpapplication/src/main/java/ntnu/systemutvikling/team6/controller/AvailableOrganizationController.java b/helpmehelpapplication/src/main/java/ntnu/systemutvikling/team6/controller/AvailableOrganizationController.java index e2c7abd..f29ee61 100644 --- a/helpmehelpapplication/src/main/java/ntnu/systemutvikling/team6/controller/AvailableOrganizationController.java +++ b/helpmehelpapplication/src/main/java/ntnu/systemutvikling/team6/controller/AvailableOrganizationController.java @@ -9,7 +9,8 @@ import javafx.scene.Parent; import javafx.scene.control.TextField; import javafx.scene.layout.FlowPane; -import ntnu.systemutvikling.team6.database.DatabaseManager; +import ntnu.systemutvikling.team6.database.DatabaseConnection; +import ntnu.systemutvikling.team6.database.Readers.CharitySelect; import ntnu.systemutvikling.team6.models.Charity; import ntnu.systemutvikling.team6.models.CharityRegistry; @@ -38,7 +39,8 @@ public class AvailableOrganizationController { */ @FXML public void initialize() { - DatabaseManager db = new DatabaseManager(); + DatabaseConnection conn = new DatabaseConnection(); + CharitySelect db = new CharitySelect(conn); CharityRegistry charities = db.getCharitiesFromDB(); allCharities = charities.getAllCharities(); diff --git a/helpmehelpapplication/src/main/java/ntnu/systemutvikling/team6/controller/FrontpageController.java b/helpmehelpapplication/src/main/java/ntnu/systemutvikling/team6/controller/FrontpageController.java index f91877c..7fa2b6c 100644 --- a/helpmehelpapplication/src/main/java/ntnu/systemutvikling/team6/controller/FrontpageController.java +++ b/helpmehelpapplication/src/main/java/ntnu/systemutvikling/team6/controller/FrontpageController.java @@ -12,7 +12,9 @@ import javafx.scene.control.Label; import javafx.scene.control.TextField; import javafx.scene.layout.FlowPane; -import ntnu.systemutvikling.team6.database.DatabaseManager; +import ntnu.systemutvikling.team6.database.DatabaseConnection; +import ntnu.systemutvikling.team6.database.Readers.CharitySelect; +import ntnu.systemutvikling.team6.database.Readers.DonationSelect; import ntnu.systemutvikling.team6.models.Charity; import ntnu.systemutvikling.team6.models.CharityRegistry; import ntnu.systemutvikling.team6.models.Donation; @@ -52,9 +54,11 @@ public class FrontpageController { @FXML public void initialize() { try { - DatabaseManager db = new DatabaseManager(); - CharityRegistry charities = db.getCharitiesFromDB(); - DonationRegistry donations = db.getDonationFromDB(); + DatabaseConnection conn = new DatabaseConnection(); + CharitySelect cdb = new CharitySelect(conn); + DonationSelect ddb = new DonationSelect(conn); + CharityRegistry charities = cdb.getCharitiesFromDB(); + DonationRegistry donations = ddb.getDonationFromDB(); allCharities = new ArrayList<>(charities.getAllCharities()); displayCharities(allCharities); diff --git a/helpmehelpapplication/src/main/java/ntnu/systemutvikling/team6/database/DatabaseSetup.java b/helpmehelpapplication/src/main/java/ntnu/systemutvikling/team6/database/DatabaseSetup.java new file mode 100644 index 0000000..62de2e8 --- /dev/null +++ b/helpmehelpapplication/src/main/java/ntnu/systemutvikling/team6/database/DatabaseSetup.java @@ -0,0 +1,274 @@ +package ntnu.systemutvikling.team6.database; + +import java.sql.*; +import ntnu.systemutvikling.team6.models.*; +import ntnu.systemutvikling.team6.models.user.*; + +/** + * Manages the Database with MySQL tables and test connection. + * + *

This class object is able to create MySQL to ntnu localized database and able to + * testConnection to it. + */ +public class DatabaseSetup { + private final DatabaseConnection connection; + + /** + * Contractor for DatabaseManager. It uses a DatabaseConnection object that contains a connection + * credentials. + */ + public DatabaseSetup(DatabaseConnection connection) { + this.connection = connection; + } + + /** + * Connection test for the Database. Does a simple SELECT SQL query and returns either true og and + * Exception if failed + * + * @return true if Sucsedd or SQLExepction if failed + */ + public boolean testConnection() { + try (Connection conn = connection.getMySqlConnection(); + Statement stmt = conn.createStatement()) { + + ResultSet rs = stmt.executeQuery("SELECT 1"); + + if (rs.next()) { + System.out.println("Database connection verified."); + return true; + } + + } catch (SQLException e) { + System.out.println("Database connection failed."); + e.printStackTrace(); + } + + return false; + } + + /** + * Creates the {@code Charities} and {@code Donations} tables if they do not already exist. + * + * @throws RuntimeException if a {@link SQLException} occurs while creating the table + */ + public void createTables() { + String charitiesTable = + """ + -- ----------------------------------------------------- + -- Table `HelpMeHelp`.`Charities` + -- ----------------------------------------------------- + CREATE TABLE IF NOT EXISTS `apbaluna`.`Charities` ( + `UUID_charities` CHAR(36) NOT NULL, + `org_number` VARCHAR(255) NOT NULL, + `charity_name` VARCHAR(255) NOT NULL, + `charity_description` VARCHAR(255) NOT NULL, + `charity_link` VARCHAR(255) NOT NULL, + `pre_approved` TINYINT NOT NULL, + `status` VARCHAR(255) NOT NULL, + PRIMARY KEY (`UUID_charities`), + UNIQUE INDEX `org_number_UNIQUE` (`org_number` ASC) VISIBLE) + ENGINE = InnoDB; + + """; + String donationsTable = + """ + -- ----------------------------------------------------- + -- Table `HelpMeHelp`.`Donations` + -- ----------------------------------------------------- + CREATE TABLE IF NOT EXISTS `apbaluna`.`Donations` ( + `UUID_Donations` CHAR(36) NOT NULL, + `amount` DECIMAL NOT NULL, + `date` DATE NOT NULL, + `charity_id` CHAR(36) NOT NULL, + `user_id` CHAR(36) NOT NULL, + PRIMARY KEY (`UUID_Donations`), + INDEX `fk_Donations_Charities_idx` (`charity_id` ASC) VISIBLE, + INDEX `fk_Donations_User1_idx` (`user_id` ASC) VISIBLE, + CONSTRAINT `fk_Donations_Charities` + FOREIGN KEY (`charity_id`) + REFERENCES `apbaluna`.`Charities` (`UUID_charities`) + ON DELETE NO ACTION + ON UPDATE NO ACTION, + CONSTRAINT `fk_Donations_User1` + FOREIGN KEY (`user_id`) + REFERENCES `apbaluna`.`User` (`UUID_User`) + ON DELETE NO ACTION + ON UPDATE NO ACTION) + ENGINE = InnoDB; + """; + String userTable = + """ + -- ----------------------------------------------------- + -- Table `apbaluna`.`User` + -- ----------------------------------------------------- + CREATE TABLE IF NOT EXISTS `apbaluna`.`User` ( + `UUID_User` CHAR(36) NOT NULL, + `user_displayname` VARCHAR(255) NOT NULL, + `user_name` VARCHAR(255) NOT NULL, + `user_email` VARCHAR(255) NOT NULL, + `user_password` VARCHAR(255) NOT NULL, + `role` VARCHAR(45) NOT NULL, + PRIMARY KEY (`UUID_User`)) + ENGINE = InnoDB; + """; + + String settingsTable = + """ + -- ----------------------------------------------------- + -- Table `apbaluna`.`Settings` + -- ----------------------------------------------------- + CREATE TABLE IF NOT EXISTS `apbaluna`.`Settings` ( + `User_UUID_User` CHAR(36) NOT NULL, + `isAnonymous` TINYINT NOT NULL, + `language` VARCHAR(45) NOT NULL, + `lightmode` TINYINT NOT NULL, + PRIMARY KEY (`User_UUID_User`), + INDEX `fk_Settings_User1_idx` (`User_UUID_User` ASC) VISIBLE, + CONSTRAINT `fk_Settings_User1` + FOREIGN KEY (`User_UUID_User`) + REFERENCES `apbaluna`.`User` (`UUID_User`) + ON DELETE NO ACTION + ON UPDATE NO ACTION) + ENGINE = InnoDB; + """; + + String messagesTable = + """ + -- ----------------------------------------------------- + -- Table `apbaluna`.`Messages` + -- ----------------------------------------------------- + CREATE TABLE IF NOT EXISTS `apbaluna`.`Messages` ( + `UUID_message` CHAR(36) NOT NULL, + `message_title` VARCHAR(255) NOT NULL, + `message_content` VARCHAR(255) NOT NULL, + `message_date` DATE NOT NULL, + `sender_user_id` CHAR(36) NULL, + `sender_charity_id` CHAR(36) NULL, + `user_id` CHAR(36) NOT NULL, + PRIMARY KEY (`UUID_message`), + INDEX `fk_Messages_User1_idx` (`sender_user_id` ASC) VISIBLE, + INDEX `fk_Messages_Charities1_idx` (`sender_charity_id` ASC) VISIBLE, + INDEX `fk_Messages_User2_idx` (`user_id` ASC) VISIBLE, + CONSTRAINT `fk_Messages_User1` + FOREIGN KEY (`sender_user_id`) + REFERENCES `apbaluna`.`User` (`UUID_User`) + ON DELETE NO ACTION + ON UPDATE NO ACTION, + CONSTRAINT `fk_Messages_Charities1` + FOREIGN KEY (`sender_charity_id`) + REFERENCES `apbaluna`.`Charities` (`UUID_charities`) + ON DELETE NO ACTION + ON UPDATE NO ACTION, + CONSTRAINT `fk_Messages_User2` + FOREIGN KEY (`user_id`) + REFERENCES `apbaluna`.`User` (`UUID_User`) + ON DELETE NO ACTION + ON UPDATE NO ACTION) + ENGINE = InnoDB; + """; + + String feedbackTable = + """ + -- ----------------------------------------------------- + -- Table `apbaluna`.`Feedback` + -- ----------------------------------------------------- + CREATE TABLE IF NOT EXISTS `apbaluna`.`Feedback` ( + `UUID_feedback` CHAR(36) NOT NULL, + `feedback_comment` VARCHAR(255) NOT NULL, + `feedback_date` DATE NOT NULL, + `isAnonymous` TINYINT NOT NULL, + `charity_id` CHAR(36) NOT NULL, + `user_id` CHAR(36) NOT NULL, + PRIMARY KEY (`UUID_feedback`), + INDEX `fk_Feedback_Charities1_idx` (`charity_id` ASC) VISIBLE, + INDEX `fk_Feedback_User1_idx` (`user_id` ASC) VISIBLE, + CONSTRAINT `fk_Feedback_Charities1` + FOREIGN KEY (`charity_id`) + REFERENCES `apbaluna`.`Charities` (`UUID_charities`) + ON DELETE NO ACTION + ON UPDATE NO ACTION, + CONSTRAINT `fk_Feedback_User1` + FOREIGN KEY (`user_id`) + REFERENCES `apbaluna`.`User` (`UUID_User`) + ON DELETE NO ACTION + ON UPDATE NO ACTION) + ENGINE = InnoDB; + """; + String categoriesTable = + """ + -- ----------------------------------------------------- + -- Table `apbaluna`.`Categories` + -- ----------------------------------------------------- + CREATE TABLE IF NOT EXISTS `apbaluna`.`Categories` ( + `category_id` INT NOT NULL AUTO_INCREMENT, + `category` VARCHAR(255) NOT NULL, + PRIMARY KEY (`category_id`)) + ENGINE = InnoDB; + """; + + String charityCategoriesTable = + """ + -- ----------------------------------------------------- + -- Table `apbaluna`.`Charity_Categories` + -- ----------------------------------------------------- + CREATE TABLE IF NOT EXISTS `apbaluna`.`Charity_Categories` ( + `Categories_category_id` INT NOT NULL, + `Charities_UUID_charities` CHAR(36) NOT NULL, + PRIMARY KEY (`Categories_category_id`, `Charities_UUID_charities`), + INDEX `fk_Categories_has_Charities_Charities1_idx` (`Charities_UUID_charities` ASC) VISIBLE, + INDEX `fk_Categories_has_Charities_Categories1_idx` (`Categories_category_id` ASC) VISIBLE, + CONSTRAINT `fk_Categories_has_Charities_Categories1` + FOREIGN KEY (`Categories_category_id`) + REFERENCES `apbaluna`.`Categories` (`category_id`) + ON DELETE NO ACTION + ON UPDATE NO ACTION, + CONSTRAINT `fk_Categories_has_Charities_Charities1` + FOREIGN KEY (`Charities_UUID_charities`) + REFERENCES `apbaluna`.`Charities` (`UUID_charities`) + ON DELETE NO ACTION + ON UPDATE NO ACTION) + ENGINE = InnoDB; + """; + + String charityUserTable = + """ + -- ----------------------------------------------------- + -- Table `apbaluna`.`CharityUsers` + -- ----------------------------------------------------- + CREATE TABLE IF NOT EXISTS `apbaluna`.`CharityUsers` ( + `Charities_UUID_charities` CHAR(36) NOT NULL, + `User_UUID_User` CHAR(36) NOT NULL, + PRIMARY KEY (`Charities_UUID_charities`, `User_UUID_User`), + INDEX `fk_Charities_has_User_User1_idx` (`User_UUID_User` ASC) VISIBLE, + INDEX `fk_Charities_has_User_Charities1_idx` (`Charities_UUID_charities` ASC) VISIBLE, + CONSTRAINT `fk_Charities_has_User_Charities1` + FOREIGN KEY (`Charities_UUID_charities`) + REFERENCES `apbaluna`.`Charities` (`UUID_charities`) + ON DELETE NO ACTION + ON UPDATE NO ACTION, + CONSTRAINT `fk_Charities_has_User_User1` + FOREIGN KEY (`User_UUID_User`) + REFERENCES `apbaluna`.`User` (`UUID_User`) + ON DELETE NO ACTION + ON UPDATE NO ACTION) + ENGINE = InnoDB; + """; + + try (Connection conn = connection.getMySqlConnection(); + Statement s = conn.createStatement()) { + + s.execute(charitiesTable); + s.execute(userTable); + s.execute(donationsTable); + s.execute(settingsTable); + s.execute(messagesTable); + s.execute(feedbackTable); + s.execute(categoriesTable); + s.execute(charityCategoriesTable); + s.execute(charityUserTable); + } catch (SQLException e) { + e.printStackTrace(); + throw new RuntimeException("Error creating table."); + } + } +} diff --git a/helpmehelpapplication/src/main/java/ntnu/systemutvikling/team6/database/Readers/CharitySelect.java b/helpmehelpapplication/src/main/java/ntnu/systemutvikling/team6/database/Readers/CharitySelect.java new file mode 100644 index 0000000..4c9ffb1 --- /dev/null +++ b/helpmehelpapplication/src/main/java/ntnu/systemutvikling/team6/database/Readers/CharitySelect.java @@ -0,0 +1,168 @@ +package ntnu.systemutvikling.team6.database.Readers; + +import java.sql.*; +import java.time.LocalDate; +import java.util.ArrayList; +import ntnu.systemutvikling.team6.database.DatabaseConnection; +import ntnu.systemutvikling.team6.models.Charity; +import ntnu.systemutvikling.team6.models.CharityRegistry; +import ntnu.systemutvikling.team6.models.Feedback; +import ntnu.systemutvikling.team6.models.user.User; + +/** + * Data access class responsible for reading charity-related data from the database. + * + *

Provides methods to retrieve all charities (with their associated feedback and users) as well + * as feedback entries for a specific charity by UUID. + * + *

All queries are executed against a MySQL database via a {@link DatabaseConnection}. + */ +public class CharitySelect { + private final DatabaseConnection connection; + + /** + * Constructs a new {@code CharitySelect} with the given database connection. + * + * @param connection the {@link DatabaseConnection} to use for executing queries; must not be + * {@code null} + */ + public CharitySelect(DatabaseConnection connection) { + this.connection = connection; + } + + /** + * Retrieves all charities from the database, including their associated feedback and the users + * who submitted each piece of feedback. + * + *

The query performs a LEFT JOIN between the {@code Charities}, {@code Feedback}, and {@code + * User} tables. Each unique charity is added once to the registry; any feedback rows found for + * that charity are appended to its feedback list. + * + *

Note: charities with no feedback are still included in the result due to the LEFT JOIN. + * + * @return a {@link CharityRegistry} containing all charities found in the database, each + * populated with its associated {@link Feedback} objects (if any) + * @throws RuntimeException if a {@link SQLException} occurs while executing the query + */ + public CharityRegistry getCharitiesFromDB() { + CharityRegistry registry = null; + Connection conn = null; + try { + conn = connection.getMySqlConnection(); + String sql_query = + """ + SELECT + c.UUID_charities, c.org_number, c.charity_name, c.charity_link, c.pre_approved, c.status, + f.UUID_feedback, f.feedback_comment, f.feedback_date, f.isAnonymous, f.charity_id, f.user_id, + u.UUID_user, u.user_name, u.user_email, u.user_password, u.role + FROM Charities c + LEFT JOIN Feedback f ON f.charity_id = c.UUID_charities + LEFT JOIN User u ON f.user_id = u.UUID_user + """; + Statement stmt = conn.createStatement(); + ResultSet rs = stmt.executeQuery(sql_query); + + Charity currentCharity = null; + String lastCharity = null; + + registry = new CharityRegistry(); + while (rs.next()) { + String currentId = rs.getString("UUID_charities"); + + if (lastCharity == null || !currentId.equals(lastCharity)) { + currentCharity = + new Charity( + rs.getString("UUID_charities"), + rs.getString("org_number"), + rs.getString("charity_link"), + rs.getString("charity_name"), + rs.getBoolean("pre_approved"), + rs.getString("status")); + registry.addCharity(currentCharity); + lastCharity = currentId; + } + String feedbackId = rs.getString("UUID_feedback"); + if (feedbackId != null) { + User userWithNoSettingsAndInbox = + new User( + rs.getString("UUID_User"), + rs.getString("user_name"), + rs.getString("user_email"), + rs.getString("user_password"), + rs.getString("role")); + + Feedback feedback = + new Feedback( + rs.getString("UUID_feedback"), + userWithNoSettingsAndInbox, + rs.getString("feedback_comment"), + LocalDate.parse(rs.getString("feedback_date"))); + + currentCharity.getFeedbacks().add(feedback); + } + } + } catch (SQLException e) { + e.printStackTrace(); + throw new RuntimeException("ERROR: Something went wrong during updating charities table."); + } + return registry; + } + + /** + * A helper function that retrieves all feedback entries associated with a specific charity, + * identified by its UUID. Currently, has no use. + * + *

Each {@link Feedback} object is populated with the associated {@link User} (without settings + * or inbox data). The query uses a LEFT JOIN between the {@code Feedback} and {@code User} + * tables, filtered by {@code charity_id}. + * + * @param charity_uuid the UUID of the charity whose feedback should be retrieved; must not be + * {@code null} + * @return an {@link ArrayList} of {@link Feedback} objects for the given charity; returns an + * empty list if no feedback exists for that charity + * @throws RuntimeException if any exception occurs while executing the query, wrapping the + * original cause + */ + public ArrayList getFeedbackforCharityUUID(String charity_uuid) { + ArrayList Feedbacks = new ArrayList<>(); + Connection conn = null; + try { + conn = connection.getMySqlConnection(); + String sql_query = + """ + SELECT + f.UUID_feedback, f.feedback_comment, f.feedback_date, f.isAnonymous, f.charity_id, f.user_id, + u.UUID_user, u.user_name, u.user_email, u.user_password, u.role + FROM Feedback f + LEFT JOIN User u ON f.user_id = u.UUID_user + WHERE f.charity_id = ?; + """; + PreparedStatement stmt = conn.prepareStatement(sql_query); + stmt.setString(1, charity_uuid); + ResultSet rs = stmt.executeQuery(); + + while (rs.next()) { + User userWithNoSettingsAndInbox = + new User( + rs.getString("UUID_User"), + rs.getString("user_name"), + rs.getString("user_email"), + rs.getString("user_password"), + rs.getString("role")); + Feedback feedback = + new Feedback( + rs.getString("UUID_feedback"), + userWithNoSettingsAndInbox, + rs.getString("feedback_comment"), + LocalDate.parse(rs.getString("feedback_date"))); + Feedbacks.add(feedback); + } + } catch (Exception e) { + e.printStackTrace(); + throw new RuntimeException(e); + } finally { + conn = null; + } + return Feedbacks; + } +} diff --git a/helpmehelpapplication/src/main/java/ntnu/systemutvikling/team6/database/Readers/DonationSelect.java b/helpmehelpapplication/src/main/java/ntnu/systemutvikling/team6/database/Readers/DonationSelect.java new file mode 100644 index 0000000..26690ab --- /dev/null +++ b/helpmehelpapplication/src/main/java/ntnu/systemutvikling/team6/database/Readers/DonationSelect.java @@ -0,0 +1,94 @@ +package ntnu.systemutvikling.team6.database.Readers; + +import java.sql.Connection; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.Statement; +import ntnu.systemutvikling.team6.database.DatabaseConnection; +import ntnu.systemutvikling.team6.models.Charity; +import ntnu.systemutvikling.team6.models.Donation; +import ntnu.systemutvikling.team6.models.DonationRegistry; + +/** + * Data access class responsible for reading donation data from the database. + * + *

Retrieves donations along with their associated charity information by performing an INNER + * JOIN between the {@code Donations} and {@code Charities} tables. Only donations that have a + * matching charity record are returned. + */ +public class DonationSelect { + + /** The database connection used for all queries in this class. */ + private final DatabaseConnection connection; + + /** + * Constructs a new {@code DonationSelect} with the given database connection. + * + * @param connection the {@link DatabaseConnection} to use for executing queries; must not be + * {@code null} + */ + public DonationSelect(DatabaseConnection connection) { + this.connection = connection; + } + + /** + * Retrieves all donations from the database, each populated with its associated {@link Charity}. + * + *

The query performs an INNER JOIN between the {@code Donations} and {@code Charities} tables + * on the charity UUID foreign key. Donations without a matching charity are excluded from the + * result. + * + * @return a {@link DonationRegistry} containing all matched donations; never {@code null}, but + * may be empty if no rows are returned + * @throws RuntimeException if a {@link SQLException} occurs while executing the query + */ + public DonationRegistry getDonationFromDB() { + DonationRegistry registry = null; + Connection conn = null; + try { + conn = connection.getMySqlConnection(); + String sql_query = + """ + SELECT + d.UUID_Donations, + d.amount, + d.date, + c.UUID_charities, + c.org_number, + c.charity_name, + c.charity_link, + c.pre_approved, + c.status + FROM Donations d + JOIN Charities c + ON d.Charities_UUID_charities = c.UUID_charities + """; + Statement stmt = conn.createStatement(); + ResultSet rs = stmt.executeQuery(sql_query); + + registry = new DonationRegistry(); + while (rs.next()) { + Charity charity = + new Charity( + rs.getString("UUID_charities"), + rs.getString("org_number"), + rs.getString("charity_name"), + rs.getString("charity_link"), + rs.getBoolean("pre_approved"), + rs.getString("status")); + + Donation donation = + new Donation( + rs.getString("UUID_Donations"), + rs.getDouble("amount"), + rs.getDate("date").toLocalDate(), + charity); + registry.addDonation(donation); + } + } catch (SQLException e) { + e.printStackTrace(); + throw new RuntimeException("ERROR: Something went wrong during updating charities table."); + } + return registry; + } +} diff --git a/helpmehelpapplication/src/main/java/ntnu/systemutvikling/team6/database/Readers/UserSelect.java b/helpmehelpapplication/src/main/java/ntnu/systemutvikling/team6/database/Readers/UserSelect.java new file mode 100644 index 0000000..ce41742 --- /dev/null +++ b/helpmehelpapplication/src/main/java/ntnu/systemutvikling/team6/database/Readers/UserSelect.java @@ -0,0 +1,356 @@ +package ntnu.systemutvikling.team6.database.Readers; + +import java.sql.*; +import java.time.LocalDate; +import java.util.UUID; +import ntnu.systemutvikling.team6.database.DatabaseConnection; +import ntnu.systemutvikling.team6.models.UserRegistry; +import ntnu.systemutvikling.team6.models.user.*; +import ntnu.systemutvikling.team6.security.PasswordHasher; + +/** + * Data access class responsible for reading user-related data from the database. + * + *

Provides methods to retrieve individual users (by credentials or UUID), all users, a user's + * settings, and a user's inbox. Queries use LEFT JOINs across the {@code User}, {@code Settings}, + * and {@code Messages} tables to assemble fully populated {@link User} objects in a single round + * trip where possible. + */ +public class UserSelect { + /** The database connection used for all queries in this class. */ + private final DatabaseConnection connection; + + /** + * Constructs a new {@code UserSelect} with the given database connection. + * + * @param connection the {@link DatabaseConnection} to use for executing queries; must not be + * {@code null} + */ + public UserSelect(DatabaseConnection connection) { + this.connection = connection; + } + + /** + * Retrieves a single {@link User} from the database matching the given username and password. + * + *

The password is hashed via {@link PasswordHasher} before being compared against the stored + * value. If a matching user is found, their {@link Settings} (when present) and {@link Inbox} + * (including any {@link Message} objects) are also populated. Returns {@code null} if no matching + * user is found. + * + *

Note: the current SQL query compares both parameters against {@code + * user_password}; the {@code user_name} column is not yet included in the WHERE clause, which may + * be a bug. + * + * @param username the plain-text username to look up + * @param password the plain-text password; hashed internally before the query runs + * @return the matching {@link User} with settings and inbox populated, or {@code null} if no + * match is found + * @throws RuntimeException if a {@link SQLException} occurs while executing the query + */ + public User getUserFromDBUsernameAndPassword(String username, String password) { + PasswordHasher hasher = new PasswordHasher(); + String hashedpassword = hasher.getHashPassword(password); + + User user = null; + Connection conn = null; + try { + conn = connection.getMySqlConnection(); + String sql_query = + """ + SELECT + u.UUID_User, u.user_name, u.user_email, u.user_password, u.role, + s.User_UUID_User, s.isAnonymous, s.language, s.lightmode, + m.UUID_message, m.message_title, m.message_content, m.message_date, m.sender_user_id, m.sender_charity_id, m.user_id + FROM User u + LEFT JOIN Settings s ON u.UUID_User = s.User_UUID_user + LEFT JOIN Messages m ON u.UUID_User = m.user_id + WHERE u.user_password = ? AND u.user_password = ?; + """; + PreparedStatement stmt = conn.prepareStatement(sql_query); + stmt.setString(1, username); + stmt.setString(2, hashedpassword); + + ResultSet rs = stmt.executeQuery(); + + String lastUserid = null; + while (rs.next()) { + String userId = rs.getString("UUID_User"); + if (lastUserid == null || !userId.equals(lastUserid)) { + user = + new User( + userId, + rs.getString("user_name"), + rs.getString("user_email"), + rs.getString("user_password"), + rs.getString("role")); + if (rs.getString("isAnonymous") != null) { + Settings settings = + new Settings( + rs.getBoolean("isAnonymous"), + Language.valueOf(rs.getString("language").toUpperCase()), + rs.getBoolean("lightmode")); + user.setSettings(settings); + } + user.setInbox(new Inbox()); + lastUserid = userId; + } + String messageId = rs.getString("UUID_message"); + if (messageId != null) { + Message message = + new Message( + rs.getString("message_title"), + UUID.fromString(rs.getString("sender_charity_id")), + rs.getString("message_content"), + LocalDate.parse(rs.getString("message_date"))); + + user.getInbox().addMessage(message); + } + } + } catch (SQLException e) { + e.printStackTrace(); + throw new RuntimeException("ERROR: Something went wrong during updating charities table."); + } finally { + conn = null; + } + return user; + } + + /** + * Retrieves a single {@link User} from the database by their UUID. + * + *

The returned user is fully populated with {@link Settings} (when present) and an {@link + * Inbox} containing any associated {@link Message} objects. Returns {@code null} if no user with + * the given UUID exists. + * + * @param user_id the UUID string of the user to retrieve; must not be {@code null} + * @return the matching {@link User} with settings and inbox populated, or {@code null} if no user + * is found + * @throws RuntimeException if a {@link SQLException} occurs while executing the query + */ + public User getUserFromDBUuid(String user_id) { + User user = null; + Connection conn = null; + try { + conn = connection.getMySqlConnection(); + String sql_query = + """ + SELECT + u.UUID_User, u.user_name, u.user_email, u.user_password, u.role, + s.User_UUID_User, s.isAnonymous, s.language, s.lightmode, + m.UUID_message, m.message_title, m.message_content, m.message_date, m.sender_user_id, m.sender_charity_id, m.user_id + FROM User u + LEFT JOIN Settings s ON u.UUID_User = s.User_UUID_user + LEFT JOIN Messages m ON u.UUID_User = m.user_id + WHERE u.UUID_User = ?; + """; + PreparedStatement stmt = conn.prepareStatement(sql_query); + stmt.setString(1, user_id); + ResultSet rs = stmt.executeQuery(); + + String lastUserid = null; + while (rs.next()) { + String userId = rs.getString("UUID_User"); + if (lastUserid == null || !userId.equals(lastUserid)) { + user = + new User( + userId, + rs.getString("user_name"), + rs.getString("user_email"), + rs.getString("user_password"), + rs.getString("role")); + if (rs.getString("isAnonymous") != null) { + Settings settings = + new Settings( + rs.getBoolean("isAnonymous"), + Language.valueOf(rs.getString("language").toUpperCase()), + rs.getBoolean("lightmode")); + user.setSettings(settings); + } + user.setInbox(new Inbox()); + lastUserid = userId; + } + String messageId = rs.getString("UUID_message"); + if (messageId != null) { + Message message = + new Message( + rs.getString("message_title"), + UUID.fromString(rs.getString("sender_charity_id")), + rs.getString("message_content"), + LocalDate.parse(rs.getString("message_date"))); + + user.getInbox().addMessage(message); + } + } + } catch (SQLException e) { + e.printStackTrace(); + throw new RuntimeException("ERROR: Something went wrong during updating charities table."); + } finally { + conn = null; + } + return user; + } + + /** + * Retrieves all users from the database, each fully populated with their {@link Settings} and + * {@link Inbox}. + * + *

The query LEFT JOINs {@code User}, {@code Settings}, and {@code Messages}. Multiple rows for + * the same user UUID (due to multiple messages) are collapsed into a single {@link User} object + * with all messages appended to its inbox. + * + * @return a {@link UserRegistry} containing all users found in the database; never {@code null}, + * but may be empty if no users exist + * @throws RuntimeException if a {@link SQLException} occurs while executing the query + */ + public UserRegistry getUsersFromDB() { + UserRegistry registry = new UserRegistry(); + Connection conn = null; + try { + conn = connection.getMySqlConnection(); + String sql_query = + """ + SELECT + u.UUID_User, u.user_name, u.user_email, u.user_password, u.role, + s.User_UUID_User, s.isAnonymous, s.language, s.lightmode, + m.UUID_message, m.message_title, m.message_content, m.message_date, m.sender_user_id, m.sender_charity_id, m.user_id + FROM User u + LEFT JOIN Settings s ON u.UUID_User = s.User_UUID_user + LEFT JOIN Messages m ON u.UUID_User = m.user_id + """; + Statement stmt = conn.createStatement(); + ResultSet rs = stmt.executeQuery(sql_query); + + User currentUser = null; + String lastUserid = null; + + while (rs.next()) { + String userId = rs.getString("UUID_User"); + + if (lastUserid == null || !userId.equals(lastUserid)) { + currentUser = + new User( + userId, + rs.getString("user_name"), + rs.getString("user_email"), + rs.getString("user_password"), + rs.getString("role")); + if (rs.getString("isAnonymous") != null) { + Settings settings = + new Settings( + rs.getBoolean("isAnonymous"), + Language.valueOf(rs.getString("language").toUpperCase()), + rs.getBoolean("lightmode")); + currentUser.setSettings(settings); + } + currentUser.setInbox(new Inbox()); + registry.addUser(currentUser); + lastUserid = userId; + } + String messageId = rs.getString("UUID_message"); + if (messageId != null) { + Message message = + new Message( + rs.getString("message_title"), + UUID.fromString(rs.getString("sender_charity_id")), + rs.getString("message_content"), + LocalDate.parse(rs.getString("message_date"))); + + currentUser.getInbox().addMessage(message); + } + } + } catch (SQLException e) { + e.printStackTrace(); + throw new RuntimeException("ERROR: Something went wrong during updating charities table."); + } finally { + conn = null; + } + return registry; + } + + /** + * Retrieves the {@link Settings} for a specific user by their UUID. + * + *

At most one row is fetched (via {@code setMaxRows(1)}). Returns {@code null} if no settings + * row exists for the given user. + * + * @param user_id the UUID string of the user whose settings should be retrieved; must not be + * {@code null} + * @return the user's {@link Settings}, or {@code null} if none are found + * @throws RuntimeException if a {@link SQLException} occurs while executing the query + */ + public Settings getSettingsForUser(String user_id) { + Settings settings = null; + Connection conn = null; + try { + conn = connection.getMySqlConnection(); + String sql_query = + """ + SELECT User_UUID_User, isAnonymous, language, lightmode FROM Settings + WHERE User_UUID_User = ?; + """; + PreparedStatement stmt = conn.prepareStatement(sql_query); + stmt.setString(1, user_id); + stmt.setMaxRows(1); + ResultSet rs = stmt.executeQuery(); + + while (rs.next()) { + settings = + new Settings( + rs.getBoolean("isAnonymous"), + Language.valueOf(rs.getString("language").toUpperCase()), + rs.getBoolean("lightmode")); + } + } catch (SQLException e) { + e.printStackTrace(); + throw new RuntimeException("ERROR: Something went wrong during updating charities table."); + } finally { + conn = null; + } + + return settings; + } + + /** + * Retrieves the {@link Inbox} for a specific user by their UUID, populated with all of their + * {@link Message} objects. + * + *

Returns an empty {@link Inbox} (never {@code null}) if no messages exist for the given user. + * + * @param user_id the UUID string of the user whose inbox should be retrieved; must not be {@code + * null} + * @return an {@link Inbox} containing all messages for the user; empty if no messages are found + * @throws RuntimeException if a {@link SQLException} occurs while executing the query + */ + public Inbox getInboxForUser(String user_id) { + Inbox inbox = new Inbox(); + Connection conn = null; + try { + conn = connection.getMySqlConnection(); + String sql_query = + """ + SELECT UUID_message, message_title, message_content, message_date, sender_user_id, sender_charity_id, user_id FROM Messages + WHERE user_id = ?; + """; + PreparedStatement stmt = conn.prepareStatement(sql_query); + stmt.setString(1, user_id); + ResultSet rs = stmt.executeQuery(); + + while (rs.next()) { + Message message = + new Message( + rs.getString("message_title"), + UUID.fromString(rs.getString("sender_charity_id")), + rs.getString("message_date"), + LocalDate.parse(rs.getString("message_date"))); + inbox.addMessage(message); + } + } catch (SQLException e) { + e.printStackTrace(); + throw new RuntimeException("ERROR: Something went wrong during updating charities table."); + } finally { + conn = null; + } + return inbox; + } +} diff --git a/helpmehelpapplication/src/main/java/ntnu/systemutvikling/team6/models/Charity.java b/helpmehelpapplication/src/main/java/ntnu/systemutvikling/team6/models/Charity.java index b66c21f..357e3ec 100644 --- a/helpmehelpapplication/src/main/java/ntnu/systemutvikling/team6/models/Charity.java +++ b/helpmehelpapplication/src/main/java/ntnu/systemutvikling/team6/models/Charity.java @@ -180,4 +180,11 @@ public void setKeyValues(String values) { public void setLogoBlob(byte[] logoBlob) { this.logoBlob = logoBlob; } + + /** + * Setter for + */ + public void setFeedbacks(ArrayList feedbacks){ + this.feedbacks = feedbacks; + } } diff --git a/helpmehelpapplication/src/main/java/ntnu/systemutvikling/team6/models/Feedback.java b/helpmehelpapplication/src/main/java/ntnu/systemutvikling/team6/models/Feedback.java index 490c47e..6e78c3b 100644 --- a/helpmehelpapplication/src/main/java/ntnu/systemutvikling/team6/models/Feedback.java +++ b/helpmehelpapplication/src/main/java/ntnu/systemutvikling/team6/models/Feedback.java @@ -1,6 +1,6 @@ package ntnu.systemutvikling.team6.models; -import java.time.LocalDateTime; +import java.time.LocalDate; import java.util.UUID; import ntnu.systemutvikling.team6.models.user.User; @@ -15,13 +15,13 @@ public class Feedback { private String comment; /* The date and time when the feedback was given */ - private LocalDateTime date; + private LocalDate date; /* Is the feedback given anonymously? */ private boolean isAnonymous; /** - * Constructor for creating a new feedback. + * Constructor for creating a new feedback now. * * @param user The user who gives the feedback. * @param comment The content of the feedback. @@ -30,7 +30,22 @@ public Feedback(User user, String comment) { this.feedbackId = UUID.randomUUID(); this.user = user; this.comment = comment; - this.date = LocalDateTime.now(); + this.date = LocalDate.now(); + this.isAnonymous = user.getSettings().isAnonymous(); + } + + /** + * Constructor for creating a new feedback, based on making a feedback previously made. + * + * @param user The user who gives the feedback. + * @param comment The content of the feedback. + * @param feedback_date The content of the feedback. + */ + public Feedback(String feedback_id, User user, String feedback_comment, LocalDate feedback_date) { + this.feedbackId = UUID.fromString(feedback_id); + this.user = user; + this.comment = feedback_comment; + this.date = feedback_date; this.isAnonymous = user.getSettings().isAnonymous(); } @@ -48,7 +63,7 @@ public String getComment() { return comment; } - public LocalDateTime getDate() { + public LocalDate getDate() { return date; } diff --git a/helpmehelpapplication/src/main/java/ntnu/systemutvikling/team6/models/UserRegistry.java b/helpmehelpapplication/src/main/java/ntnu/systemutvikling/team6/models/UserRegistry.java index 514cbec..454fc11 100644 --- a/helpmehelpapplication/src/main/java/ntnu/systemutvikling/team6/models/UserRegistry.java +++ b/helpmehelpapplication/src/main/java/ntnu/systemutvikling/team6/models/UserRegistry.java @@ -1,3 +1,37 @@ package ntnu.systemutvikling.team6.models; -public class UserRegistry {} +import java.util.*; +import ntnu.systemutvikling.team6.models.user.User; + +public class UserRegistry { + private final List Users; + + public UserRegistry() { + this.Users = new ArrayList<>(); + } + + public List getAllUsers() { + return Collections.unmodifiableList(Users); + } + + public Optional findUserById(UUID userUUID) { + if (userUUID == null) { + throw new IllegalArgumentException("DonationId can not be null."); + } + return Users.stream().filter(u -> userUUID.equals(u.getId())).findFirst(); + } + + public void addUser(User user) { + if (user == null) { + throw new IllegalArgumentException("Donation can not be null."); + } + Users.add(user); + } + + public boolean removeUserByUUID(UUID userUUID) { + if (userUUID == null) { + throw new IllegalArgumentException("DonationId can not be null."); + } + return Users.removeIf(user -> userUUID.equals(user.getId())); + } +} diff --git a/helpmehelpapplication/src/main/java/ntnu/systemutvikling/team6/models/user/Language.java b/helpmehelpapplication/src/main/java/ntnu/systemutvikling/team6/models/user/Language.java index c568ede..cbd66cf 100644 --- a/helpmehelpapplication/src/main/java/ntnu/systemutvikling/team6/models/user/Language.java +++ b/helpmehelpapplication/src/main/java/ntnu/systemutvikling/team6/models/user/Language.java @@ -6,5 +6,6 @@ * @author Robin Strand Prestmo */ public enum Language { + NORWEGIAN, ENGLISH } diff --git a/helpmehelpapplication/src/main/java/ntnu/systemutvikling/team6/models/user/Message.java b/helpmehelpapplication/src/main/java/ntnu/systemutvikling/team6/models/user/Message.java index b70ece1..3a2a75d 100644 --- a/helpmehelpapplication/src/main/java/ntnu/systemutvikling/team6/models/user/Message.java +++ b/helpmehelpapplication/src/main/java/ntnu/systemutvikling/team6/models/user/Message.java @@ -1,6 +1,6 @@ package ntnu.systemutvikling.team6.models.user; -import java.time.LocalDateTime; +import java.time.LocalDate; import java.util.UUID; // Enhetstester mangler @@ -13,9 +13,9 @@ public class Message { private final UUID id; private final String title; - private final String from; + private final UUID fromCharityID; private final String content; - private final LocalDateTime timeAndDate; + private final LocalDate timeAndDate; /** * Creates a message with a unique identifier. The message includes a title, a string who it's @@ -26,13 +26,13 @@ public class Message { * @param content the content of the message * @throws IllegalArgumentException if title, from or content is null or blank. */ - public Message(String title, String from, String content) { + public Message(String title, UUID from, String content) { if (title == null || title.isBlank()) { throw new IllegalArgumentException("Title cannot be null or blank."); } - if (from == null || from.isBlank()) { + if (from == null) { throw new IllegalArgumentException("From cannot be null or blank."); } @@ -42,9 +42,44 @@ public Message(String title, String from, String content) { this.id = UUID.randomUUID(); this.title = title; - this.from = from; + this.fromCharityID = from; this.content = content; - this.timeAndDate = LocalDateTime.now(); + this.timeAndDate = LocalDate.now(); + } + + /** + * Creates a message with a unique identifier. The message includes a title, a string who it's + * from, content and the time and date. This one creates a message that has been created before. + * + * @param title the title of the message + * @param from who the message is from + * @param content the content of the message + * @param date date of when the message was created + * @throws IllegalArgumentException if title, from or content is null or blank. + */ + public Message(String title, UUID from, String content, LocalDate date) { + + if (title == null || title.isBlank()) { + throw new IllegalArgumentException("Title cannot be null or blank."); + } + + if (from == null) { + throw new IllegalArgumentException("From cannot be null or blank."); + } + + if (content == null || content.isBlank()) { + throw new IllegalArgumentException("Content cannot be null or blank."); + } + + if (date == null) { + throw new IllegalArgumentException("Content cannot be null or blank."); + } + + this.id = UUID.randomUUID(); + this.title = title; + this.fromCharityID = from; + this.content = content; + this.timeAndDate = date; } public UUID getId() { @@ -55,15 +90,15 @@ public String getTitle() { return title; } - public String getFrom() { - return from; + public UUID getFrom() { + return fromCharityID; } public String getContent() { return content; } - public LocalDateTime getTimeAndDate() { + public LocalDate getTimeAndDate() { return timeAndDate; } } diff --git a/helpmehelpapplication/src/main/java/ntnu/systemutvikling/team6/models/user/User.java b/helpmehelpapplication/src/main/java/ntnu/systemutvikling/team6/models/user/User.java index ef59000..ed375dc 100644 --- a/helpmehelpapplication/src/main/java/ntnu/systemutvikling/team6/models/user/User.java +++ b/helpmehelpapplication/src/main/java/ntnu/systemutvikling/team6/models/user/User.java @@ -16,18 +16,17 @@ public class User { private static final PasswordHasher passwordHasher = new PasswordHasher(); - private final UUID id; + private UUID id; private String name; private String email; private String passwordHash; - private final Role role; - private final Settings settings; - private final Inbox inbox; + private Role role; + private Settings settings; + private Inbox inbox; /** * Creates a new user. * - * @param id gives the user a unique identifier with UUID * @param name the name of the user * @param email the email of the user * @param password the password for the user @@ -69,6 +68,45 @@ public User( this.inbox = inbox; } + /** + * Creates a new user taylored for getting info from DATABASE. Settings and inbox can be set on a + * later date throught another method in databaseManager class + * + * @param uuid gives the user a unique identifier with UUID + * @param name the name of the user + * @param email the email of the user + * @param password the password for the user + * @param role users role + * @throws IllegalArgumentException if any required argument is invalid. + */ + public User(String uuid, String name, String email, String password, String role) { + + if (uuid == null || uuid.isBlank()) { + throw new IllegalArgumentException("UUID cannot be null or blank."); + } + + if (name == null || name.isBlank()) { + throw new IllegalArgumentException("Name cannot be null or blank."); + } + + if (email == null || email.isBlank() || !email.contains("@") || !email.contains(".")) { + throw new IllegalArgumentException( + "Email cannot be null or blank," + " and must contain '@' and '.'"); + } + + if (role == null) { + throw new IllegalArgumentException("Role cannot be null"); + } + + this.id = UUID.fromString(uuid); + this.name = name; + this.email = email; + this.passwordHash = password; + this.role = Role.valueOf(role); + this.settings = null; + this.inbox = null; + } + // Add Getters public UUID getId() { @@ -146,4 +184,18 @@ public void setEmail(String email) { public boolean checkPassword(String password) { return passwordHasher.isValidPassword(password, passwordHash); } + + public void setSettings(Settings settings) { + if (settings == null) { + throw new IllegalArgumentException("Settings cannot be null"); + } + this.settings = settings; + } + + public void setInbox(Inbox inbox) { + if (inbox == null) { + throw new IllegalArgumentException("Inbox cannot be null"); + } + this.inbox = inbox; + } } diff --git a/helpmehelpapplication/src/main/java/ntnu/systemutvikling/team6/scraper/APICharityData.java b/helpmehelpapplication/src/main/java/ntnu/systemutvikling/team6/scraper/APICharityData.java index 10a489d..0cf6da8 100644 --- a/helpmehelpapplication/src/main/java/ntnu/systemutvikling/team6/scraper/APICharityData.java +++ b/helpmehelpapplication/src/main/java/ntnu/systemutvikling/team6/scraper/APICharityData.java @@ -1,6 +1,6 @@ package ntnu.systemutvikling.team6.scraper; -import ntnu.systemutvikling.team6.database.DatabaseManager; +import ntnu.systemutvikling.team6.database.DatabaseSetup; /** * Represents data parsed from the IK API JSON response. Instances are immutable; to update any @@ -9,7 +9,7 @@ *

Receives data directly from {@link APICharityScraper}. * *

{@code org_number} should be a unique number, as it is used as a primary key in {@link - * DatabaseManager}. + * DatabaseSetup}. */ public class APICharityData { private final String org_number; diff --git a/helpmehelpapplication/src/main/java/ntnu/systemutvikling/team6/service/APIToDatabaseService.java b/helpmehelpapplication/src/main/java/ntnu/systemutvikling/team6/service/APIToDatabaseService.java new file mode 100644 index 0000000..ac7b918 --- /dev/null +++ b/helpmehelpapplication/src/main/java/ntnu/systemutvikling/team6/service/APIToDatabaseService.java @@ -0,0 +1,140 @@ +package ntnu.systemutvikling.team6.service; + +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.SQLException; +import java.util.List; +import java.util.UUID; +import ntnu.systemutvikling.team6.database.DatabaseConnection; +import ntnu.systemutvikling.team6.models.Charity; + +public class APIToDatabaseService { + private final DatabaseConnection connection; + + /** + * Contractor for DatabaseManager. It uses a DatabaseConnection object that contains a connection + * credentials. + * + * @param connection + */ + public APIToDatabaseService(DatabaseConnection connection) { + this.connection = connection; + } + + /** + * This method is used to verify the integrity of the data in the {@code charities} table and to + * update it based on the data retrieved from the IK API. The param charities are retrieved from + * the IK API through the APICharityData class. Called in initialize method in + * HmHApplication.java, which is the main class of the application, to ensure that the data is up + * to date when the application starts. Uses a a temp table to ensure that the data in the + * database is consistent with the data from the API. + * + * @param charities + */ + public void addAPIDataToTable(List charities) { + Connection conn = null; + try { + conn = connection.getMySqlConnection(); + conn.setAutoCommit(false); + String sql_query = + """ + INSERT INTO Charities (UUID_charities, org_number, charity_name, charity_description, charity_link, pre_approved, status) + VALUES (?, ?, ?, ?, ?, ?, ?) + ON DUPLICATE KEY UPDATE + charity_name = VALUES(charity_name), + charity_link = VALUES(charity_link), + pre_approved = VALUES(pre_approved), + status = VALUES(status) + """; + + try (PreparedStatement ps = conn.prepareStatement(sql_query)) { + for (Charity charity : charities) { + if (charity.getUUID() == null) { + ps.setString(1, UUID.randomUUID().toString()); + } else { + ps.setString(1, charity.getUUID().toString()); + } + + ps.setString(2, charity.getOrg_number().replaceAll("\\s", "")); + ps.setString(3, charity.getName()); + ps.setString(4, "Blank until webscraber works"); + ps.setString(5, charity.getDescription()); + ps.setBoolean(6, charity.getPreApproved()); // Description is the link + ps.setString(7, charity.getStatus()); + + ps.addBatch(); + } + ps.executeBatch(); + } + + // -- Intergerty Check: + String createTemp = + """ + CREATE TEMPORARY TABLE temp_api_charities ( + org_number VARCHAR(255) PRIMARY KEY + ) + """; + + try (PreparedStatement ps = conn.prepareStatement(createTemp)) { + ps.execute(); + } + + String insertTemp = "INSERT IGNORE INTO temp_api_charities (org_number) VALUES (?)"; + + try (PreparedStatement ps = conn.prepareStatement(insertTemp)) { + + for (Charity charity : charities) { + ps.setString(1, charity.getOrg_number().replaceAll("\\s", "")); + ps.addBatch(); + } + + ps.executeBatch(); + } + + String deleteSql = + """ + DELETE FROM Charities c + WHERE NOT EXISTS ( + SELECT 1 FROM temp_api_charities t + WHERE t.org_number = c.org_number + ) + AND NOT EXISTS ( + SELECT 1 FROM Donations d WHERE d.charity_id = c.UUID_charities + ) + AND NOT EXISTS ( + SELECT 1 FROM Feedback f WHERE f.charity_id = c.UUID_charities + ) + AND NOT EXISTS ( + SELECT 1 FROM CharityUsers cu WHERE cu.Charities_UUID_charities = c.UUID_charities + ); + """; + + try (PreparedStatement ps = conn.prepareStatement(deleteSql)) { + ps.executeUpdate(); + } + + conn.commit(); + + } catch (SQLException e) { + if (conn != null) { + try { + conn.rollback(); + } catch (SQLException ex) { + ex.printStackTrace(); + } + } + e.printStackTrace(); + + throw new RuntimeException("ERROR: Something went wrong during updating charities table."); + } finally { + if (conn != null) { + try { + conn.setAutoCommit(true); + conn.close(); + } catch (SQLException e) { + e.printStackTrace(); + } + } + } + } +} diff --git a/helpmehelpapplication/src/test/java/ntnu/systemutvikling/team6/DAO/DonationDAOTest.java b/helpmehelpapplication/src/test/java/ntnu/systemutvikling/team6/DAO/DonationDAOTest.java index c90a297..122a384 100644 --- a/helpmehelpapplication/src/test/java/ntnu/systemutvikling/team6/DAO/DonationDAOTest.java +++ b/helpmehelpapplication/src/test/java/ntnu/systemutvikling/team6/DAO/DonationDAOTest.java @@ -6,8 +6,9 @@ import java.sql.PreparedStatement; import java.sql.ResultSet; import ntnu.systemutvikling.team6.database.DatabaseConnection; -import ntnu.systemutvikling.team6.database.DatabaseManager; +import ntnu.systemutvikling.team6.database.DatabaseSetup; import ntnu.systemutvikling.team6.models.Charity; +import ntnu.systemutvikling.team6.service.APIToDatabaseService; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -17,7 +18,8 @@ class DonationDAOTest { @BeforeEach void setUp() { - DatabaseManager manager = new DatabaseManager(); + DatabaseConnection conn = new DatabaseConnection(); + DatabaseSetup manager = new DatabaseSetup(conn); manager.createTables(); charity = @@ -28,7 +30,8 @@ void setUp() { true, "approved"); - manager.addAPIDataToTable(java.util.List.of(charity)); + APIToDatabaseService service = new APIToDatabaseService(conn); + service.addAPIDataToTable(java.util.List.of(charity)); } @Test diff --git a/helpmehelpapplication/src/test/java/ntnu/systemutvikling/team6/database/DatabaseSetupTest.java b/helpmehelpapplication/src/test/java/ntnu/systemutvikling/team6/database/DatabaseSetupTest.java new file mode 100644 index 0000000..809d251 --- /dev/null +++ b/helpmehelpapplication/src/test/java/ntnu/systemutvikling/team6/database/DatabaseSetupTest.java @@ -0,0 +1,62 @@ +package ntnu.systemutvikling.team6.database; + +import static org.junit.jupiter.api.Assertions.*; + +import java.sql.*; +import java.util.List; +import ntnu.systemutvikling.team6.database.Readers.CharitySelect; +import ntnu.systemutvikling.team6.database.Readers.DonationSelect; +import ntnu.systemutvikling.team6.models.Charity; +import ntnu.systemutvikling.team6.service.APIToDatabaseService; +import org.junit.jupiter.api.*; + +class DatabaseSetupTest { + + private DatabaseSetup dbManager; + private APIToDatabaseService service; + private CharitySelect charitySelect; + private DonationSelect donationSelect; + + @BeforeEach + public void setUp() throws SQLException { + DatabaseConnection conn = new DatabaseConnection(); + this.dbManager = new DatabaseSetup(conn); + this.service = new APIToDatabaseService(conn); + this.charitySelect = new CharitySelect(conn); + this.donationSelect = new DonationSelect(conn); + } + + // Make sure you're connected to the NTNU network for this to work + @Test + public void testConnectionShouldReturnTrue() { + assertTrue(dbManager.testConnection()); + } + + @Test + void createCharitiesTableShouldCreateTableSuccessfully() throws SQLException { + dbManager.createTables(); + + try (Connection conn = new DatabaseConnection().getMySqlConnection()) { + ResultSet rs = conn.getMetaData().getTables(null, null, "Charities", null); + + assertTrue(rs.next()); + } + } + + @Test + void tempTableShouldNotExistAfterUpdating() throws SQLException { + Charity charity = new Charity("99999", "https://temp.no", "Temp Charity", false, "approved"); + + service.addAPIDataToTable(List.of(charity)); + + try (Connection conn = new DatabaseConnection().getMySqlConnection()) { + PreparedStatement stmt = conn.prepareStatement("SELECT * FROM temp_api_charities"); + + assertThrows( + java.sql.SQLSyntaxErrorException.class, + () -> { + ResultSet rs = stmt.executeQuery(); + }); + } + } +} diff --git a/helpmehelpapplication/src/test/java/ntnu/systemutvikling/team6/database/Readers/CharitySelectTest.java b/helpmehelpapplication/src/test/java/ntnu/systemutvikling/team6/database/Readers/CharitySelectTest.java new file mode 100644 index 0000000..7e4bad0 --- /dev/null +++ b/helpmehelpapplication/src/test/java/ntnu/systemutvikling/team6/database/Readers/CharitySelectTest.java @@ -0,0 +1,290 @@ +package ntnu.systemutvikling.team6.database.Readers; + +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.*; + +import java.sql.*; +import java.util.ArrayList; +import ntnu.systemutvikling.team6.database.DatabaseConnection; +import ntnu.systemutvikling.team6.models.Charity; +import ntnu.systemutvikling.team6.models.CharityRegistry; +import ntnu.systemutvikling.team6.models.Feedback; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +/** + * Unit tests for {@link CharitySelect}. + * + *

Uses Mockito to mock {@link DatabaseConnection}, {@link Connection}, {@link Statement}, {@link + * PreparedStatement}, and {@link ResultSet} so that no real database connection is required. + */ +@ExtendWith(MockitoExtension.class) +class CharitySelectTest { + + @Mock private DatabaseConnection mockDatabaseConnection; + @Mock private Connection mockConnection; + @Mock private Statement mockStatement; + @Mock private PreparedStatement mockPreparedStatement; + @Mock private ResultSet mockResultSet; + + private CharitySelect charitySelect; + + @BeforeEach + void setUp() { + charitySelect = new CharitySelect(mockDatabaseConnection); + } + + // ------------------------------------------------------------------------- + // getCharitiesFromDB + // ------------------------------------------------------------------------- + + @Test + @DisplayName("getCharitiesFromDB – empty result set returns an empty registry") + void getCharitiesFromDB_emptyResultSet_returnsEmptyRegistry() throws Exception { + when(mockDatabaseConnection.getMySqlConnection()).thenReturn(mockConnection); + when(mockConnection.createStatement()).thenReturn(mockStatement); + when(mockStatement.executeQuery(anyString())).thenReturn(mockResultSet); + when(mockResultSet.next()).thenReturn(false); + + CharityRegistry registry = charitySelect.getCharitiesFromDB(); + + assertNotNull(registry); + assertTrue( + registry.getAllCharities().isEmpty(), + "Registry should contain no charities when the result set is empty"); + } + + @Test + @DisplayName("getCharitiesFromDB – single charity with no feedback is added once") + void getCharitiesFromDB_singleCharityNoFeedback_addedOnce() throws Exception { + when(mockDatabaseConnection.getMySqlConnection()).thenReturn(mockConnection); + when(mockConnection.createStatement()).thenReturn(mockStatement); + when(mockStatement.executeQuery(anyString())).thenReturn(mockResultSet); + + // One row, no feedback + when(mockResultSet.next()).thenReturn(true, false); + when(mockResultSet.getString("UUID_charities")).thenReturn("charity-uuid-1"); + when(mockResultSet.getString("org_number")).thenReturn("123456789"); + when(mockResultSet.getString("charity_link")).thenReturn("https://example.org"); + when(mockResultSet.getString("charity_name")).thenReturn("Test Charity"); + when(mockResultSet.getBoolean("pre_approved")).thenReturn(true); + when(mockResultSet.getString("status")).thenReturn("ACTIVE"); + when(mockResultSet.getString("UUID_feedback")).thenReturn(null); + + CharityRegistry registry = charitySelect.getCharitiesFromDB(); + + assertEquals( + 1, registry.getAllCharities().size(), "Registry should contain exactly one charity"); + Charity charity = registry.getAllCharities().get(0); + assertEquals("Test Charity", charity.getName()); + assertTrue(charity.getFeedbacks().isEmpty(), "Charity should have no feedback"); + } + + @Test + @DisplayName("getCharitiesFromDB – single charity with one feedback entry is populated correctly") + void getCharitiesFromDB_singleCharityWithFeedback_feedbackAdded() throws Exception { + when(mockDatabaseConnection.getMySqlConnection()).thenReturn(mockConnection); + when(mockConnection.createStatement()).thenReturn(mockStatement); + when(mockStatement.executeQuery(anyString())).thenReturn(mockResultSet); + + // One row with feedback + when(mockResultSet.next()).thenReturn(true, false); + when(mockResultSet.getString("UUID_charities")).thenReturn("charity-uuid-1"); + when(mockResultSet.getString("org_number")).thenReturn("123456789"); + when(mockResultSet.getString("charity_link")).thenReturn("https://example.org"); + when(mockResultSet.getString("charity_name")).thenReturn("Test Charity"); + when(mockResultSet.getBoolean("pre_approved")).thenReturn(false); + when(mockResultSet.getString("status")).thenReturn("PENDING"); + + when(mockResultSet.getString("UUID_feedback")).thenReturn("feedback-uuid-1"); + when(mockResultSet.getString("UUID_User")).thenReturn("user-uuid-1"); + when(mockResultSet.getString("user_name")).thenReturn("Alice"); + when(mockResultSet.getString("user_email")).thenReturn("alice@example.com"); + when(mockResultSet.getString("user_password")).thenReturn("hashedpw"); + when(mockResultSet.getString("role")).thenReturn("USER"); + when(mockResultSet.getString("feedback_comment")).thenReturn("Great work!"); + when(mockResultSet.getString("feedback_date")).thenReturn("2024-03-15"); + + CharityRegistry registry = charitySelect.getCharitiesFromDB(); + + assertEquals(1, registry.getAllCharities().size()); + Charity charity = registry.getAllCharities().get(0); + assertEquals( + 1, charity.getFeedbacks().size(), "Charity should have exactly one feedback entry"); + + Feedback feedback = charity.getFeedbacks().get(0); + assertEquals("feedback-uuid-1", feedback.getFeedbackId()); + assertEquals("Great work!", feedback.getComment()); + } + + @Test + @DisplayName("getCharitiesFromDB – two different charities across two rows are both added") + void getCharitiesFromDB_twoCharities_bothAdded() throws Exception { + when(mockDatabaseConnection.getMySqlConnection()).thenReturn(mockConnection); + when(mockConnection.createStatement()).thenReturn(mockStatement); + when(mockStatement.executeQuery(anyString())).thenReturn(mockResultSet); + + // First row: charity A, no feedback + // Second row: charity B, no feedback + when(mockResultSet.next()).thenReturn(true, true, false); + when(mockResultSet.getString("UUID_charities")).thenReturn("charity-uuid-A", "charity-uuid-B"); + when(mockResultSet.getString("org_number")).thenReturn("111111111", "222222222"); + when(mockResultSet.getString("charity_link")).thenReturn("https://a.org", "https://b.org"); + when(mockResultSet.getString("charity_name")).thenReturn("Charity A", "Charity B"); + when(mockResultSet.getBoolean("pre_approved")).thenReturn(true, false); + when(mockResultSet.getString("status")).thenReturn("ACTIVE", "INACTIVE"); + when(mockResultSet.getString("UUID_feedback")).thenReturn(null, null); + + CharityRegistry registry = charitySelect.getCharitiesFromDB(); + + assertEquals(2, registry.getAllCharities().size(), "Registry should contain two charities"); + } + + @Test + @DisplayName( + "getCharitiesFromDB – same charity UUID across two rows adds feedback without duplicating the charity") + void getCharitiesFromDB_sameCharityTwoRows_onlyOneCharityWithTwoFeedbacks() throws Exception { + when(mockDatabaseConnection.getMySqlConnection()).thenReturn(mockConnection); + when(mockConnection.createStatement()).thenReturn(mockStatement); + when(mockStatement.executeQuery(anyString())).thenReturn(mockResultSet); + + when(mockResultSet.next()).thenReturn(true, true, false); + // Both rows share the same charity UUID + when(mockResultSet.getString("UUID_charities")).thenReturn("charity-uuid-1"); + when(mockResultSet.getString("org_number")).thenReturn("123456789"); + when(mockResultSet.getString("charity_link")).thenReturn("https://example.org"); + when(mockResultSet.getString("charity_name")).thenReturn("Test Charity"); + when(mockResultSet.getBoolean("pre_approved")).thenReturn(true); + when(mockResultSet.getString("status")).thenReturn("ACTIVE"); + + when(mockResultSet.getString("UUID_feedback")).thenReturn("feedback-uuid-1", "feedback-uuid-2"); + when(mockResultSet.getString("UUID_User")).thenReturn("user-uuid-1"); + when(mockResultSet.getString("user_name")).thenReturn("Alice"); + when(mockResultSet.getString("user_email")).thenReturn("alice@example.com"); + when(mockResultSet.getString("user_password")).thenReturn("hashedpw"); + when(mockResultSet.getString("role")).thenReturn("USER"); + when(mockResultSet.getString("feedback_comment")).thenReturn("First comment", "Second comment"); + when(mockResultSet.getString("feedback_date")).thenReturn("2024-03-15"); + + CharityRegistry registry = charitySelect.getCharitiesFromDB(); + + assertEquals(1, registry.getAllCharities().size(), "The same charity should not be duplicated"); + assertEquals( + 2, + registry.getAllCharities().get(0).getFeedbacks().size(), + "Both feedback entries should be attached to the single charity"); + } + + @Test + @DisplayName("getCharitiesFromDB – SQLException is wrapped in RuntimeException") + void getCharitiesFromDB_sqlException_throwsRuntimeException() throws Exception { + when(mockDatabaseConnection.getMySqlConnection()).thenReturn(mockConnection); + when(mockConnection.createStatement()).thenThrow(new SQLException("DB error")); + + assertThrows( + RuntimeException.class, + () -> charitySelect.getCharitiesFromDB(), + "A SQLException should be rethrown as a RuntimeException"); + } + + // ------------------------------------------------------------------------- + // getFeedbackforCharityUUID + // ------------------------------------------------------------------------- + + @Test + @DisplayName("getFeedbackforCharityUUID – empty result set returns empty list") + void getFeedbackforCharityUUID_emptyResultSet_returnsEmptyList() throws Exception { + when(mockDatabaseConnection.getMySqlConnection()).thenReturn(mockConnection); + when(mockConnection.prepareStatement(anyString())).thenReturn(mockPreparedStatement); + when(mockPreparedStatement.executeQuery()).thenReturn(mockResultSet); + when(mockResultSet.next()).thenReturn(false); + + ArrayList result = charitySelect.getFeedbackforCharityUUID("charity-uuid-1"); + + assertNotNull(result); + assertTrue( + result.isEmpty(), "Should return an empty list when no feedback exists for the given UUID"); + } + + @Test + @DisplayName("getFeedbackforCharityUUID – one row returns one Feedback with correct data") + void getFeedbackforCharityUUID_oneRow_returnsSingleFeedback() throws Exception { + when(mockDatabaseConnection.getMySqlConnection()).thenReturn(mockConnection); + when(mockConnection.prepareStatement(anyString())).thenReturn(mockPreparedStatement); + when(mockPreparedStatement.executeQuery()).thenReturn(mockResultSet); + + when(mockResultSet.next()).thenReturn(true, false); + when(mockResultSet.getString("UUID_feedback")).thenReturn("feedback-uuid-1"); + when(mockResultSet.getString("UUID_User")).thenReturn("user-uuid-1"); + when(mockResultSet.getString("user_name")).thenReturn("Bob"); + when(mockResultSet.getString("user_email")).thenReturn("bob@example.com"); + when(mockResultSet.getString("user_password")).thenReturn("secret"); + when(mockResultSet.getString("role")).thenReturn("ADMIN"); + when(mockResultSet.getString("feedback_comment")).thenReturn("Very helpful!"); + when(mockResultSet.getString("feedback_date")).thenReturn("2024-06-01"); + + ArrayList result = charitySelect.getFeedbackforCharityUUID("charity-uuid-1"); + + assertEquals(1, result.size()); + Feedback feedback = result.get(0); + assertEquals("feedback-uuid-1", feedback.getFeedbackId()); + assertEquals("Very helpful!", feedback.getComment()); + assertEquals("Bob", feedback.getUser().getName()); + } + + @Test + @DisplayName("getFeedbackforCharityUUID – two rows returns two Feedback objects") + void getFeedbackforCharityUUID_twoRows_returnsTwoFeedbacks() throws Exception { + when(mockDatabaseConnection.getMySqlConnection()).thenReturn(mockConnection); + when(mockConnection.prepareStatement(anyString())).thenReturn(mockPreparedStatement); + when(mockPreparedStatement.executeQuery()).thenReturn(mockResultSet); + + when(mockResultSet.next()).thenReturn(true, true, false); + when(mockResultSet.getString("UUID_feedback")).thenReturn("fb-uuid-1", "fb-uuid-2"); + when(mockResultSet.getString("UUID_User")).thenReturn("user-uuid-1"); + when(mockResultSet.getString("user_name")).thenReturn("Carol"); + when(mockResultSet.getString("user_email")).thenReturn("carol@example.com"); + when(mockResultSet.getString("user_password")).thenReturn("pw"); + when(mockResultSet.getString("role")).thenReturn("USER"); + when(mockResultSet.getString("feedback_comment")).thenReturn("Comment one", "Comment two"); + when(mockResultSet.getString("feedback_date")).thenReturn("2024-07-10"); + + ArrayList result = charitySelect.getFeedbackforCharityUUID("charity-uuid-1"); + + assertEquals( + 2, result.size(), "Should return exactly two Feedback objects for two result rows"); + } + + @Test + @DisplayName("getFeedbackforCharityUUID – UUID is bound to the PreparedStatement parameter") + void getFeedbackforCharityUUID_correctUUIDBindingVerified() throws Exception { + String targetUuid = "charity-uuid-XYZ"; + + when(mockDatabaseConnection.getMySqlConnection()).thenReturn(mockConnection); + when(mockConnection.prepareStatement(anyString())).thenReturn(mockPreparedStatement); + when(mockPreparedStatement.executeQuery()).thenReturn(mockResultSet); + when(mockResultSet.next()).thenReturn(false); + + charitySelect.getFeedbackforCharityUUID(targetUuid); + + verify(mockPreparedStatement).setString(1, targetUuid); + } + + @Test + @DisplayName("getFeedbackforCharityUUID – exception during query is wrapped in RuntimeException") + void getFeedbackforCharityUUID_exception_throwsRuntimeException() throws Exception { + when(mockDatabaseConnection.getMySqlConnection()).thenReturn(mockConnection); + when(mockConnection.prepareStatement(anyString())) + .thenThrow(new SQLException("Prepared statement failed")); + + assertThrows( + RuntimeException.class, + () -> charitySelect.getFeedbackforCharityUUID("charity-uuid-1"), + "Any exception during query execution should be rethrown as RuntimeException"); + } +} diff --git a/helpmehelpapplication/src/test/java/ntnu/systemutvikling/team6/database/Readers/DonationSelectTest.java b/helpmehelpapplication/src/test/java/ntnu/systemutvikling/team6/database/Readers/DonationSelectTest.java new file mode 100644 index 0000000..5c3545c --- /dev/null +++ b/helpmehelpapplication/src/test/java/ntnu/systemutvikling/team6/database/Readers/DonationSelectTest.java @@ -0,0 +1,216 @@ +package ntnu.systemutvikling.team6.database.Readers; + +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.*; + +import java.sql.*; +import java.time.LocalDate; +import ntnu.systemutvikling.team6.database.DatabaseConnection; +import ntnu.systemutvikling.team6.models.Donation; +import ntnu.systemutvikling.team6.models.DonationRegistry; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +/** + * Unit tests for {@link DonationSelect}. + * + *

Uses Mockito to mock the entire JDBC stack so no real database connection is required. + */ +@ExtendWith(MockitoExtension.class) +class DonationSelectTest { + + @Mock private DatabaseConnection mockDatabaseConnection; + @Mock private Connection mockConnection; + @Mock private Statement mockStatement; + @Mock private ResultSet mockResultSet; + @Mock private Date mockSqlDate; + + private DonationSelect donationSelect; + + @BeforeEach + void setUp() { + donationSelect = new DonationSelect(mockDatabaseConnection); + } + + // ------------------------------------------------------------------------- + // getDonationFromDB + // ------------------------------------------------------------------------- + + @Test + @DisplayName("getDonationFromDB – empty result set returns an empty registry") + void getDonationFromDB_emptyResultSet_returnsEmptyRegistry() throws Exception { + when(mockDatabaseConnection.getMySqlConnection()).thenReturn(mockConnection); + when(mockConnection.createStatement()).thenReturn(mockStatement); + when(mockStatement.executeQuery(anyString())).thenReturn(mockResultSet); + when(mockResultSet.next()).thenReturn(false); + + DonationRegistry registry = donationSelect.getDonationFromDB(); + + assertNotNull(registry); + assertTrue( + registry.getAllDonations().isEmpty(), + "Registry should be empty when the result set has no rows"); + } + + @Test + @DisplayName("getDonationFromDB – single row returns one Donation with correct data") + void getDonationFromDB_singleRow_returnsSingleDonation() throws Exception { + when(mockDatabaseConnection.getMySqlConnection()).thenReturn(mockConnection); + when(mockConnection.createStatement()).thenReturn(mockStatement); + when(mockStatement.executeQuery(anyString())).thenReturn(mockResultSet); + + when(mockResultSet.next()).thenReturn(true, false); + stubCharityColumns( + "charity-uuid-1", "123456789", "Test Charity", "https://example.org", true, "ACTIVE"); + stubDonationColumns("donation-uuid-1", 250.0, LocalDate.of(2024, 5, 20)); + + DonationRegistry registry = donationSelect.getDonationFromDB(); + + assertEquals(1, registry.getAllDonations().size()); + Donation donation = registry.getAllDonations().get(0); + assertEquals("donation-uuid-1", donation.getCharityId()); + assertEquals(250.0, donation.getAmount()); + assertEquals(LocalDate.of(2024, 5, 20), donation.getDate()); + } + + @Test + @DisplayName("getDonationFromDB – single row maps charity fields onto the Donation correctly") + void getDonationFromDB_singleRow_charityMappedCorrectly() throws Exception { + when(mockDatabaseConnection.getMySqlConnection()).thenReturn(mockConnection); + when(mockConnection.createStatement()).thenReturn(mockStatement); + when(mockStatement.executeQuery(anyString())).thenReturn(mockResultSet); + + when(mockResultSet.next()).thenReturn(true, false); + stubCharityColumns( + "charity-uuid-1", "987654321", "Help Fund", "https://helpfund.org", false, "PENDING"); + stubDonationColumns("donation-uuid-1", 100.0, LocalDate.of(2024, 1, 1)); + + DonationRegistry registry = donationSelect.getDonationFromDB(); + + Donation donation = registry.getAllDonations().get(0); + assertEquals("charity-uuid-1", donation.getCharity().getUUID()); + assertEquals("987654321", donation.getCharity().getOrg_number()); + assertEquals("Help Fund", donation.getCharity().getName()); + assertEquals("https://helpfund.org", donation.getCharity().getDescription()); + assertFalse(donation.getCharity().getPreApproved()); + assertEquals("PENDING", donation.getCharity().getStatus()); + } + + @Test + @DisplayName("getDonationFromDB – two rows returns two Donation objects") + void getDonationFromDB_twoRows_returnsTwoDonations() throws Exception { + when(mockDatabaseConnection.getMySqlConnection()).thenReturn(mockConnection); + when(mockConnection.createStatement()).thenReturn(mockStatement); + when(mockStatement.executeQuery(anyString())).thenReturn(mockResultSet); + + when(mockResultSet.next()).thenReturn(true, true, false); + + when(mockResultSet.getString("UUID_charities")).thenReturn("charity-uuid-1", "charity-uuid-2"); + when(mockResultSet.getString("org_number")).thenReturn("111111111", "222222222"); + when(mockResultSet.getString("charity_name")).thenReturn("Charity A", "Charity B"); + when(mockResultSet.getString("charity_link")).thenReturn("https://a.org", "https://b.org"); + when(mockResultSet.getBoolean("pre_approved")).thenReturn(true, false); + when(mockResultSet.getString("status")).thenReturn("ACTIVE", "INACTIVE"); + + when(mockResultSet.getString("UUID_Donations")) + .thenReturn("donation-uuid-1", "donation-uuid-2"); + when(mockResultSet.getDouble("amount")).thenReturn(500.0, 750.0); + + Date sqlDate = Date.valueOf(LocalDate.of(2024, 8, 10)); + when(mockResultSet.getDate("date")).thenReturn(sqlDate); + + DonationRegistry registry = donationSelect.getDonationFromDB(); + + assertEquals( + 2, + registry.getAllDonations().size(), + "Registry should contain two donations for two result rows"); + } + + @Test + @DisplayName("getDonationFromDB – donation amount of zero is stored correctly") + void getDonationFromDB_zeroAmount_storedCorrectly() throws Exception { + when(mockDatabaseConnection.getMySqlConnection()).thenReturn(mockConnection); + when(mockConnection.createStatement()).thenReturn(mockStatement); + when(mockStatement.executeQuery(anyString())).thenReturn(mockResultSet); + + when(mockResultSet.next()).thenReturn(true, false); + stubCharityColumns( + "charity-uuid-1", "123456789", "Test Charity", "https://example.org", true, "ACTIVE"); + stubDonationColumns("donation-uuid-zero", 0.0, LocalDate.of(2024, 1, 1)); + + DonationRegistry registry = donationSelect.getDonationFromDB(); + + assertEquals(0.0, registry.getAllDonations().get(0).getAmount()); + } + + @Test + @DisplayName("getDonationFromDB – large donation amount is stored correctly") + void getDonationFromDB_largeAmount_storedCorrectly() throws Exception { + when(mockDatabaseConnection.getMySqlConnection()).thenReturn(mockConnection); + when(mockConnection.createStatement()).thenReturn(mockStatement); + when(mockStatement.executeQuery(anyString())).thenReturn(mockResultSet); + + when(mockResultSet.next()).thenReturn(true, false); + stubCharityColumns( + "charity-uuid-1", "123456789", "Test Charity", "https://example.org", true, "ACTIVE"); + stubDonationColumns("donation-uuid-big", 1_000_000.99, LocalDate.of(2024, 12, 31)); + + DonationRegistry registry = donationSelect.getDonationFromDB(); + + assertEquals(1_000_000.99, registry.getAllDonations().get(0).getAmount(), 0.001); + } + + @Test + @DisplayName("getDonationFromDB – SQLException is wrapped in RuntimeException") + void getDonationFromDB_sqlException_throwsRuntimeException() throws Exception { + when(mockDatabaseConnection.getMySqlConnection()).thenReturn(mockConnection); + when(mockConnection.createStatement()).thenThrow(new SQLException("DB error")); + + assertThrows( + RuntimeException.class, + () -> donationSelect.getDonationFromDB(), + "A SQLException should be rethrown as a RuntimeException"); + } + + @Test + @DisplayName("getDonationFromDB – RuntimeException message contains expected error text") + void getDonationFromDB_sqlException_runtimeExceptionHasExpectedMessage() throws Exception { + when(mockDatabaseConnection.getMySqlConnection()).thenReturn(mockConnection); + when(mockConnection.createStatement()).thenThrow(new SQLException("DB error")); + + RuntimeException ex = + assertThrows(RuntimeException.class, () -> donationSelect.getDonationFromDB()); + assertTrue( + ex.getMessage().contains("ERROR"), "RuntimeException message should contain 'ERROR'"); + } + + // ------------------------------------------------------------------------- + // Helpers + // ------------------------------------------------------------------------- + + /** Stubs all charity-related columns on the mock ResultSet. */ + private void stubCharityColumns( + String uuid, String orgNumber, String name, String link, boolean preApproved, String status) + throws SQLException { + when(mockResultSet.getString("UUID_charities")).thenReturn(uuid); + when(mockResultSet.getString("org_number")).thenReturn(orgNumber); + when(mockResultSet.getString("charity_name")).thenReturn(name); + when(mockResultSet.getString("charity_link")).thenReturn(link); + when(mockResultSet.getBoolean("pre_approved")).thenReturn(preApproved); + when(mockResultSet.getString("status")).thenReturn(status); + } + + /** Stubs all donation-related columns on the mock ResultSet. */ + private void stubDonationColumns(String uuid, double amount, LocalDate date) throws SQLException { + when(mockResultSet.getString("UUID_Donations")).thenReturn(uuid); + when(mockResultSet.getDouble("amount")).thenReturn(amount); + Date sqlDate = Date.valueOf(date); + when(mockResultSet.getDate("date")).thenReturn(sqlDate); + } +} diff --git a/helpmehelpapplication/src/test/java/ntnu/systemutvikling/team6/database/Readers/UserSelectTest.java b/helpmehelpapplication/src/test/java/ntnu/systemutvikling/team6/database/Readers/UserSelectTest.java new file mode 100644 index 0000000..5394fa1 --- /dev/null +++ b/helpmehelpapplication/src/test/java/ntnu/systemutvikling/team6/database/Readers/UserSelectTest.java @@ -0,0 +1,404 @@ +package ntnu.systemutvikling.team6.database.Readers; + +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.*; + +import java.sql.*; +import java.util.UUID; +import ntnu.systemutvikling.team6.database.DatabaseConnection; +import ntnu.systemutvikling.team6.models.UserRegistry; +import ntnu.systemutvikling.team6.models.user.*; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +/** + * Unit tests for {@link UserSelect}. + * + *

Uses Mockito to mock the entire JDBC stack ({@link DatabaseConnection}, {@link Connection}, + * {@link Statement}, {@link PreparedStatement}, {@link ResultSet}) so that no real database + * connection is required. + */ +@ExtendWith(MockitoExtension.class) +class UserSelectTest { + + @Mock private DatabaseConnection mockDatabaseConnection; + @Mock private Connection mockConnection; + @Mock private Statement mockStatement; + @Mock private PreparedStatement mockPreparedStatement; + @Mock private ResultSet mockResultSet; + + private static final String USER_UUID = "user-uuid-1"; + private static final String CHARITY_UUID = UUID.randomUUID().toString(); + private static final String MESSAGE_UUID = "msg-uuid-1"; + + private UserSelect userSelect; + + @BeforeEach + void setUp() { + userSelect = new UserSelect(mockDatabaseConnection); + } + + // ------------------------------------------------------------------------- + // getUserFromDBUuid + // ------------------------------------------------------------------------- + + @Test + @DisplayName("getUserFromDBUuid – no matching row returns null") + void getUserFromDBUuid_noRow_returnsNull() throws Exception { + when(mockDatabaseConnection.getMySqlConnection()).thenReturn(mockConnection); + when(mockConnection.prepareStatement(anyString())).thenReturn(mockPreparedStatement); + when(mockPreparedStatement.executeQuery()).thenReturn(mockResultSet); + when(mockResultSet.next()).thenReturn(false); + + User result = userSelect.getUserFromDBUuid(USER_UUID); + + assertNull(result, "Should return null when no user is found"); + } + + @Test + @DisplayName("getUserFromDBUuid – single row without settings returns User with null settings") + void getUserFromDBUuid_noSettings_userSettingsNull() throws Exception { + when(mockDatabaseConnection.getMySqlConnection()).thenReturn(mockConnection); + when(mockConnection.prepareStatement(anyString())).thenReturn(mockPreparedStatement); + when(mockPreparedStatement.executeQuery()).thenReturn(mockResultSet); + + when(mockResultSet.next()).thenReturn(true, false); + stubCoreUserColumns(); + when(mockResultSet.getString("isAnonymous")).thenReturn(null); + when(mockResultSet.getString("UUID_message")).thenReturn(null); + + User result = userSelect.getUserFromDBUuid(USER_UUID); + + assertNotNull(result); + assertNull(result.getSettings(), "Settings should be null when isAnonymous is null"); + assertNotNull(result.getInbox(), "Inbox should always be initialised"); + } + + @Test + @DisplayName("getUserFromDBUuid – single row with settings populates Settings correctly") + void getUserFromDBUuid_withSettings_settingsPopulated() throws Exception { + when(mockDatabaseConnection.getMySqlConnection()).thenReturn(mockConnection); + when(mockConnection.prepareStatement(anyString())).thenReturn(mockPreparedStatement); + when(mockPreparedStatement.executeQuery()).thenReturn(mockResultSet); + + when(mockResultSet.next()).thenReturn(true, false); + stubCoreUserColumns(); + stubSettingsColumns(false, "ENGLISH", true); + when(mockResultSet.getString("UUID_message")).thenReturn(null); + + User result = userSelect.getUserFromDBUuid(USER_UUID); + + assertNotNull(result.getSettings()); + assertFalse(result.getSettings().isAnonymous()); + assertEquals(Language.ENGLISH, result.getSettings().getLanguage()); + assertTrue(result.getSettings().isLightMode()); + } + + @Test + @DisplayName("getUserFromDBUuid – row with a message adds it to the inbox") + void getUserFromDBUuid_withMessage_messageAddedToInbox() throws Exception { + when(mockDatabaseConnection.getMySqlConnection()).thenReturn(mockConnection); + when(mockConnection.prepareStatement(anyString())).thenReturn(mockPreparedStatement); + when(mockPreparedStatement.executeQuery()).thenReturn(mockResultSet); + + when(mockResultSet.next()).thenReturn(true, false); + stubCoreUserColumns(); + when(mockResultSet.getString("isAnonymous")).thenReturn(null); + stubMessageColumns(); + + User result = userSelect.getUserFromDBUuid(USER_UUID); + + assertEquals( + 1, result.getInbox().getMessages().size(), "Inbox should contain exactly one message"); + } + + @Test + @DisplayName("getUserFromDBUuid – two rows for same UUID adds two messages, one User") + void getUserFromDBUuid_twoRowsSameUuid_oneUserTwoMessages() throws Exception { + when(mockDatabaseConnection.getMySqlConnection()).thenReturn(mockConnection); + when(mockConnection.prepareStatement(anyString())).thenReturn(mockPreparedStatement); + when(mockPreparedStatement.executeQuery()).thenReturn(mockResultSet); + + when(mockResultSet.next()).thenReturn(true, true, false); + when(mockResultSet.getString("UUID_User")).thenReturn(USER_UUID); + when(mockResultSet.getString("user_name")).thenReturn("Alice"); + when(mockResultSet.getString("user_email")).thenReturn("alice@example.com"); + when(mockResultSet.getString("user_password")).thenReturn("hashedpw"); + when(mockResultSet.getString("role")).thenReturn("USER"); + when(mockResultSet.getString("isAnonymous")).thenReturn(null); + when(mockResultSet.getString("UUID_message")).thenReturn("msg-1", "msg-2"); + when(mockResultSet.getString("message_title")).thenReturn("Title 1", "Title 2"); + when(mockResultSet.getString("sender_charity_id")).thenReturn(CHARITY_UUID); + when(mockResultSet.getString("message_content")).thenReturn("Content"); + when(mockResultSet.getString("message_date")).thenReturn("2024-04-01"); + + User result = userSelect.getUserFromDBUuid(USER_UUID); + + assertEquals(2, result.getInbox().getMessages().size()); + } + + @Test + @DisplayName("getUserFromDBUuid – UUID is bound to PreparedStatement parameter 1") + void getUserFromDBUuid_uuidBoundCorrectly() throws Exception { + when(mockDatabaseConnection.getMySqlConnection()).thenReturn(mockConnection); + when(mockConnection.prepareStatement(anyString())).thenReturn(mockPreparedStatement); + when(mockPreparedStatement.executeQuery()).thenReturn(mockResultSet); + when(mockResultSet.next()).thenReturn(false); + + userSelect.getUserFromDBUuid(USER_UUID); + + verify(mockPreparedStatement).setString(1, USER_UUID); + } + + @Test + @DisplayName("getUserFromDBUuid – SQLException is wrapped in RuntimeException") + void getUserFromDBUuid_sqlException_throwsRuntimeException() throws Exception { + when(mockDatabaseConnection.getMySqlConnection()).thenReturn(mockConnection); + when(mockConnection.prepareStatement(anyString())).thenThrow(new SQLException("DB error")); + + assertThrows(RuntimeException.class, () -> userSelect.getUserFromDBUuid(USER_UUID)); + } + + // ------------------------------------------------------------------------- + // getUsersFromDB + // ------------------------------------------------------------------------- + + @Test + @DisplayName("getUsersFromDB – empty result set returns empty registry") + void getUsersFromDB_emptyResultSet_returnsEmptyRegistry() throws Exception { + when(mockDatabaseConnection.getMySqlConnection()).thenReturn(mockConnection); + when(mockConnection.createStatement()).thenReturn(mockStatement); + when(mockStatement.executeQuery(anyString())).thenReturn(mockResultSet); + when(mockResultSet.next()).thenReturn(false); + + UserRegistry registry = userSelect.getUsersFromDB(); + + assertNotNull(registry); + assertTrue(registry.getAllUsers().isEmpty()); + } + + @Test + @DisplayName("getUsersFromDB – two distinct UUIDs produce two User objects") + void getUsersFromDB_twoDistinctUuids_twoUsersInRegistry() throws Exception { + when(mockDatabaseConnection.getMySqlConnection()).thenReturn(mockConnection); + when(mockConnection.createStatement()).thenReturn(mockStatement); + when(mockStatement.executeQuery(anyString())).thenReturn(mockResultSet); + + when(mockResultSet.next()).thenReturn(true, true, false); + when(mockResultSet.getString("UUID_User")).thenReturn("uuid-A", "uuid-B"); + when(mockResultSet.getString("user_name")).thenReturn("Alice", "Bob"); + when(mockResultSet.getString("user_email")).thenReturn("a@x.com", "b@x.com"); + when(mockResultSet.getString("user_password")).thenReturn("pw1", "pw2"); + when(mockResultSet.getString("role")).thenReturn("USER", "ADMIN"); + when(mockResultSet.getString("isAnonymous")).thenReturn(null); + when(mockResultSet.getString("UUID_message")).thenReturn(null); + + UserRegistry registry = userSelect.getUsersFromDB(); + + assertEquals(2, registry.getAllUsers().size()); + } + + @Test + @DisplayName("getUsersFromDB – same UUID across two rows deduplicates to one User") + void getUsersFromDB_sameUuidTwoRows_oneUserWithTwoMessages() throws Exception { + when(mockDatabaseConnection.getMySqlConnection()).thenReturn(mockConnection); + when(mockConnection.createStatement()).thenReturn(mockStatement); + when(mockStatement.executeQuery(anyString())).thenReturn(mockResultSet); + + when(mockResultSet.next()).thenReturn(true, true, false); + when(mockResultSet.getString("UUID_User")).thenReturn(USER_UUID); + when(mockResultSet.getString("user_name")).thenReturn("Alice"); + when(mockResultSet.getString("user_email")).thenReturn("alice@example.com"); + when(mockResultSet.getString("user_password")).thenReturn("hashedpw"); + when(mockResultSet.getString("role")).thenReturn("USER"); + when(mockResultSet.getString("isAnonymous")).thenReturn(null); + when(mockResultSet.getString("UUID_message")).thenReturn("msg-1", "msg-2"); + when(mockResultSet.getString("message_title")).thenReturn("T1", "T2"); + when(mockResultSet.getString("sender_charity_id")).thenReturn(CHARITY_UUID); + when(mockResultSet.getString("message_content")).thenReturn("Body"); + when(mockResultSet.getString("message_date")).thenReturn("2024-05-01"); + + UserRegistry registry = userSelect.getUsersFromDB(); + + assertEquals(1, registry.getAllUsers().size(), "Same UUID should not produce duplicate users"); + assertEquals(2, registry.getAllUsers().get(0).getInbox().getMessages().size()); + } + + @Test + @DisplayName("getUsersFromDB – SQLException is wrapped in RuntimeException") + void getUsersFromDB_sqlException_throwsRuntimeException() throws Exception { + when(mockDatabaseConnection.getMySqlConnection()).thenReturn(mockConnection); + when(mockConnection.createStatement()).thenThrow(new SQLException("DB error")); + + assertThrows(RuntimeException.class, () -> userSelect.getUsersFromDB()); + } + + // ------------------------------------------------------------------------- + // getSettingsForUser + // ------------------------------------------------------------------------- + + @Test + @DisplayName("getSettingsForUser – no row returns null") + void getSettingsForUser_noRow_returnsNull() throws Exception { + when(mockDatabaseConnection.getMySqlConnection()).thenReturn(mockConnection); + when(mockConnection.prepareStatement(anyString())).thenReturn(mockPreparedStatement); + when(mockPreparedStatement.executeQuery()).thenReturn(mockResultSet); + when(mockResultSet.next()).thenReturn(false); + + Settings result = userSelect.getSettingsForUser(USER_UUID); + + assertNull(result, "Should return null when no settings row exists"); + } + + @Test + @DisplayName("getSettingsForUser – matching row returns populated Settings") + void getSettingsForUser_matchingRow_returnsSettings() throws Exception { + when(mockDatabaseConnection.getMySqlConnection()).thenReturn(mockConnection); + when(mockConnection.prepareStatement(anyString())).thenReturn(mockPreparedStatement); + when(mockPreparedStatement.executeQuery()).thenReturn(mockResultSet); + + when(mockResultSet.next()).thenReturn(true, false); + stubSettingsColumns(true, "NORWEGIAN", false); + + Settings result = userSelect.getSettingsForUser(USER_UUID); + + assertNotNull(result); + assertTrue(result.isAnonymous()); + assertEquals(Language.NORWEGIAN, result.getLanguage()); + assertFalse(result.isLightMode()); + } + + @Test + @DisplayName("getSettingsForUser – UUID is bound to PreparedStatement and maxRows set to 1") + void getSettingsForUser_correctBindingAndMaxRows() throws Exception { + when(mockDatabaseConnection.getMySqlConnection()).thenReturn(mockConnection); + when(mockConnection.prepareStatement(anyString())).thenReturn(mockPreparedStatement); + when(mockPreparedStatement.executeQuery()).thenReturn(mockResultSet); + when(mockResultSet.next()).thenReturn(false); + + userSelect.getSettingsForUser(USER_UUID); + + verify(mockPreparedStatement).setString(1, USER_UUID); + verify(mockPreparedStatement).setMaxRows(1); + } + + @Test + @DisplayName("getSettingsForUser – SQLException is wrapped in RuntimeException") + void getSettingsForUser_sqlException_throwsRuntimeException() throws Exception { + when(mockDatabaseConnection.getMySqlConnection()).thenReturn(mockConnection); + when(mockConnection.prepareStatement(anyString())).thenThrow(new SQLException("DB error")); + + assertThrows(RuntimeException.class, () -> userSelect.getSettingsForUser(USER_UUID)); + } + + // ------------------------------------------------------------------------- + // getInboxForUser + // ------------------------------------------------------------------------- + + @Test + @DisplayName("getInboxForUser – no messages returns empty Inbox (never null)") + void getInboxForUser_noMessages_returnsEmptyInbox() throws Exception { + when(mockDatabaseConnection.getMySqlConnection()).thenReturn(mockConnection); + when(mockConnection.prepareStatement(anyString())).thenReturn(mockPreparedStatement); + when(mockPreparedStatement.executeQuery()).thenReturn(mockResultSet); + when(mockResultSet.next()).thenReturn(false); + + Inbox result = userSelect.getInboxForUser(USER_UUID); + + assertNotNull(result, "Inbox should never be null"); + assertTrue(result.getMessages().isEmpty()); + } + + @Test + @DisplayName("getInboxForUser – one message row returns Inbox with one Message") + void getInboxForUser_oneRow_inboxContainsOneMessage() throws Exception { + when(mockDatabaseConnection.getMySqlConnection()).thenReturn(mockConnection); + when(mockConnection.prepareStatement(anyString())).thenReturn(mockPreparedStatement); + when(mockPreparedStatement.executeQuery()).thenReturn(mockResultSet); + + when(mockResultSet.next()).thenReturn(true, false); + when(mockResultSet.getString("message_title")).thenReturn("Hello"); + when(mockResultSet.getString("sender_charity_id")).thenReturn(CHARITY_UUID); + when(mockResultSet.getString("message_date")).thenReturn("2024-06-15"); + + Inbox result = userSelect.getInboxForUser(USER_UUID); + + assertEquals(1, result.getMessages().size()); + assertEquals("Hello", result.getMessages().get(0).getTitle()); + } + + @Test + @DisplayName("getInboxForUser – two message rows returns Inbox with two Messages") + void getInboxForUser_twoRows_inboxContainsTwoMessages() throws Exception { + when(mockDatabaseConnection.getMySqlConnection()).thenReturn(mockConnection); + when(mockConnection.prepareStatement(anyString())).thenReturn(mockPreparedStatement); + when(mockPreparedStatement.executeQuery()).thenReturn(mockResultSet); + + when(mockResultSet.next()).thenReturn(true, true, false); + when(mockResultSet.getString("message_title")).thenReturn("Msg 1", "Msg 2"); + when(mockResultSet.getString("sender_charity_id")).thenReturn(CHARITY_UUID); + when(mockResultSet.getString("message_date")).thenReturn("2024-06-15"); + + Inbox result = userSelect.getInboxForUser(USER_UUID); + + assertEquals(2, result.getMessages().size()); + } + + @Test + @DisplayName("getInboxForUser – UUID is bound to PreparedStatement parameter 1") + void getInboxForUser_uuidBoundCorrectly() throws Exception { + when(mockDatabaseConnection.getMySqlConnection()).thenReturn(mockConnection); + when(mockConnection.prepareStatement(anyString())).thenReturn(mockPreparedStatement); + when(mockPreparedStatement.executeQuery()).thenReturn(mockResultSet); + when(mockResultSet.next()).thenReturn(false); + + userSelect.getInboxForUser(USER_UUID); + + verify(mockPreparedStatement).setString(1, USER_UUID); + } + + @Test + @DisplayName("getInboxForUser – SQLException is wrapped in RuntimeException") + void getInboxForUser_sqlException_throwsRuntimeException() throws Exception { + when(mockDatabaseConnection.getMySqlConnection()).thenReturn(mockConnection); + when(mockConnection.prepareStatement(anyString())).thenThrow(new SQLException("DB error")); + + assertThrows(RuntimeException.class, () -> userSelect.getInboxForUser(USER_UUID)); + } + + // ------------------------------------------------------------------------- + // Helpers + // ------------------------------------------------------------------------- + + /** Stubs the core User columns on the mock ResultSet. */ + private void stubCoreUserColumns() throws SQLException { + when(mockResultSet.getString("UUID_User")).thenReturn(USER_UUID); + when(mockResultSet.getString("user_name")).thenReturn("Alice"); + when(mockResultSet.getString("user_email")).thenReturn("alice@example.com"); + when(mockResultSet.getString("user_password")).thenReturn("hashedpw"); + when(mockResultSet.getString("role")).thenReturn("USER"); + } + + /** Stubs the Settings columns on the mock ResultSet. */ + private void stubSettingsColumns(boolean isAnonymous, String language, boolean lightmode) + throws SQLException { + when(mockResultSet.getString("isAnonymous")).thenReturn(String.valueOf(isAnonymous)); + when(mockResultSet.getBoolean("isAnonymous")).thenReturn(isAnonymous); + when(mockResultSet.getString("language")).thenReturn(language); + when(mockResultSet.getBoolean("lightmode")).thenReturn(lightmode); + } + + /** Stubs the Message columns on the mock ResultSet for a single message row. */ + private void stubMessageColumns() throws SQLException { + when(mockResultSet.getString("UUID_message")).thenReturn(MESSAGE_UUID); + when(mockResultSet.getString("message_title")).thenReturn("Test Message"); + when(mockResultSet.getString("sender_charity_id")).thenReturn(CHARITY_UUID); + when(mockResultSet.getString("message_content")).thenReturn("Hello!"); + when(mockResultSet.getString("message_date")).thenReturn("2024-03-01"); + } +} diff --git a/helpmehelpapplication/src/test/java/ntnu/systemutvikling/team6/models/FeedbackTest.java b/helpmehelpapplication/src/test/java/ntnu/systemutvikling/team6/models/FeedbackTest.java index 222e29b..29fd72e 100644 --- a/helpmehelpapplication/src/test/java/ntnu/systemutvikling/team6/models/FeedbackTest.java +++ b/helpmehelpapplication/src/test/java/ntnu/systemutvikling/team6/models/FeedbackTest.java @@ -3,6 +3,7 @@ import static org.junit.jupiter.api.Assertions.*; import java.time.LocalDateTime; +import java.time.chrono.ChronoLocalDate; import ntnu.systemutvikling.team6.models.user.Inbox; import ntnu.systemutvikling.team6.models.user.Role; import ntnu.systemutvikling.team6.models.user.Settings; @@ -36,7 +37,9 @@ void testFeedbackInitialization() { assertEquals(user, feedback.getUser()); // Date should be between before and after - assertTrue(!feedback.getDate().isBefore(before) && !feedback.getDate().isAfter(after)); + assertTrue( + !feedback.getDate().isBefore(ChronoLocalDate.from(before)) + && !feedback.getDate().isAfter(ChronoLocalDate.from(after))); } @Test diff --git a/helpmehelpapplication/src/test/java/ntnu/systemutvikling/team6/models/user/InboxTest.java b/helpmehelpapplication/src/test/java/ntnu/systemutvikling/team6/models/user/InboxTest.java index 2dc7fa2..f3dc6ad 100644 --- a/helpmehelpapplication/src/test/java/ntnu/systemutvikling/team6/models/user/InboxTest.java +++ b/helpmehelpapplication/src/test/java/ntnu/systemutvikling/team6/models/user/InboxTest.java @@ -16,8 +16,8 @@ public class InboxTest { @BeforeEach public void setup() { inbox = new Inbox(); - newMessage = new Message("Title", "Someone", "Somewhere"); - newMessage2 = new Message("Title2", "Someone2", "Somewhere2"); + newMessage = new Message("Title", UUID.randomUUID(), "Somewhere"); + newMessage2 = new Message("Title2", UUID.randomUUID(), "Somewhere2"); } @Test diff --git a/helpmehelpapplication/src/test/java/ntnu/systemutvikling/team6/models/user/MessegeTest.java b/helpmehelpapplication/src/test/java/ntnu/systemutvikling/team6/models/user/MessegeTest.java index faae3ad..1faa986 100644 --- a/helpmehelpapplication/src/test/java/ntnu/systemutvikling/team6/models/user/MessegeTest.java +++ b/helpmehelpapplication/src/test/java/ntnu/systemutvikling/team6/models/user/MessegeTest.java @@ -11,10 +11,10 @@ public class MessegeTest { void shouldThrowExceptionIfNameIsNullOrEmpty() { assertThrows( IllegalArgumentException.class, - () -> new Message(null, "Someone", "Something Somewhere Somehow")); + () -> new Message(null, UUID.randomUUID(), "Something Somewhere Somehow")); assertThrows( IllegalArgumentException.class, - () -> new Message("", "Someone", "Something Somewhere Somehow")); + () -> new Message("", UUID.randomUUID(), "Something Somewhere Somehow")); } @Test @@ -22,24 +22,23 @@ void shouldThrowExceptionIfFromIsNullOrEmpty() { assertThrows( IllegalArgumentException.class, () -> new Message("Title", null, "Something Somewhere Somehow")); - assertThrows( - IllegalArgumentException.class, - () -> new Message("Title", "", "Something Somewhere Somehow")); } @Test void shouldThrowExceptionIfContentIsNullOrEmpty() { - assertThrows(IllegalArgumentException.class, () -> new Message("Title", "Someone", null)); - assertThrows(IllegalArgumentException.class, () -> new Message("Title", "Someone", "")); + assertThrows( + IllegalArgumentException.class, () -> new Message("Title", UUID.randomUUID(), null)); + assertThrows(IllegalArgumentException.class, () -> new Message("Title", UUID.randomUUID(), "")); } @Test void GettersWork() { - Message newMessage = new Message("Title", "Someone", "Somewhere"); + UUID uuid = UUID.randomUUID(); + Message newMessage = new Message("Title", uuid, "Somewhere"); assertInstanceOf(UUID.class, newMessage.getId()); assertEquals("Title", newMessage.getTitle()); - assertEquals("Someone", newMessage.getFrom()); + assertEquals(uuid, newMessage.getFrom()); assertEquals("Somewhere", newMessage.getContent()); - assertEquals(LocalDate.now(), newMessage.getTimeAndDate().toLocalDate()); + assertEquals(LocalDate.now(), newMessage.getTimeAndDate()); } }