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 @@
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 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 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 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 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());
}
}