diff --git a/docs/ER-DiagramFile.mwb b/docs/ER-DiagramFile.mwb deleted file mode 100644 index c05526f1..00000000 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 24c783ba..00000000 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 00000000..af63917c 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 00000000..bffbaf8a Binary files /dev/null and b/docs/SqlDatabase/ER-Diagram v4.png differ diff --git a/docs/SqlDatabase/ER-Diagram v5.png b/docs/SqlDatabase/ER-Diagram v5.png new file mode 100644 index 00000000..df229943 Binary files /dev/null and b/docs/SqlDatabase/ER-Diagram v5.png differ diff --git a/docs/SqlDatabase/ER-DiagramFile.mwb b/docs/SqlDatabase/ER-DiagramFile.mwb new file mode 100644 index 00000000..15edab11 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 00000000..c348da33 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 00000000..63104556 --- /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 00000000..4acb7589 --- /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 00000000..529aac8f --- /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/.ai/mcp/mcp.json b/helpmehelpapplication/.ai/mcp/mcp.json new file mode 100644 index 00000000..e69de29b diff --git a/helpmehelpapplication/.gitignore b/helpmehelpapplication/.gitignore index e673575a..3fbc17a8 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 1794a9e3..ec74b68b 100644 --- a/helpmehelpapplication/pom.xml +++ b/helpmehelpapplication/pom.xml @@ -33,7 +33,7 @@ org.seleniumhq.selenium selenium-java - 4.41.0 + 4.43.0 com.opencsv @@ -62,6 +62,13 @@ 2.4.240 test + + + org.mockito + mockito-junit-jupiter + 5.23.0 + test + @@ -111,6 +118,4 @@ - - \ No newline at end of file diff --git a/helpmehelpapplication/src/main/java/ntnu/systemutvikling/team6/HmHApplication.java b/helpmehelpapplication/src/main/java/ntnu/systemutvikling/team6/HmHApplication.java index 293aa2da..966cf897 100644 --- a/helpmehelpapplication/src/main/java/ntnu/systemutvikling/team6/HmHApplication.java +++ b/helpmehelpapplication/src/main/java/ntnu/systemutvikling/team6/HmHApplication.java @@ -4,15 +4,18 @@ import java.net.http.HttpClient; import java.util.Objects; + import javafx.application.Application; import javafx.fxml.FXMLLoader; import javafx.scene.Scene; import javafx.scene.image.Image; import javafx.stage.Stage; -import ntnu.systemutvikling.team6.database.DatabaseManager; +import ntnu.systemutvikling.team6.database.DatabaseConnection; +import ntnu.systemutvikling.team6.database.DatabaseSetup; import ntnu.systemutvikling.team6.models.Charity; -import ntnu.systemutvikling.team6.models.CharityRegistry; -import ntnu.systemutvikling.team6.scraper.APICharityScraper; +import ntnu.systemutvikling.team6.models.registry.CharityRegistry; +import ntnu.systemutvikling.team6.scraper.scraperComponents.APICharityScraper; +import ntnu.systemutvikling.team6.service.APIToDatabaseService; public class HmHApplication extends Application { @Override @@ -37,7 +40,8 @@ public void start(Stage stage) throws Exception { public void init() { /* Test and create tables to MySQL if ain't any */ try { - DatabaseManager db = new DatabaseManager(); + DatabaseConnection conn = new DatabaseConnection(); + DatabaseSetup db = new DatabaseSetup(conn); db.testConnection(); db.createTables(); } catch (Exception e) { @@ -47,7 +51,9 @@ public void init() { try { HttpClient https = HttpClient.newHttpClient(); APICharityScraper scraper = new APICharityScraper(https); - DatabaseManager db = new DatabaseManager(); + + DatabaseConnection conn = new DatabaseConnection(); + APIToDatabaseService db = new APIToDatabaseService(conn); if (scraper.checkConnection()) { CharityRegistry charityRegistry = scraper.parseJSON(scraper.getJSONData()); @@ -59,6 +65,7 @@ public void init() { } catch (Exception e) { e.printStackTrace(); } + System.out.println("-- \n Init complete \n --"); } public static void main(String[] args) { diff --git a/helpmehelpapplication/src/main/java/ntnu/systemutvikling/team6/controller/AvailableOrganizationController.java b/helpmehelpapplication/src/main/java/ntnu/systemutvikling/team6/controller/AvailableOrganizationController.java new file mode 100644 index 00000000..ee3a19f1 --- /dev/null +++ b/helpmehelpapplication/src/main/java/ntnu/systemutvikling/team6/controller/AvailableOrganizationController.java @@ -0,0 +1,141 @@ +package ntnu.systemutvikling.team6.controller; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; +import javafx.event.ActionEvent; +import javafx.fxml.FXML; +import javafx.fxml.FXMLLoader; +import javafx.scene.Parent; +import javafx.scene.control.TextField; +import javafx.scene.layout.FlowPane; +import ntnu.systemutvikling.team6.controller.components.BaseController; +import ntnu.systemutvikling.team6.controller.components.LoaderScene; +import ntnu.systemutvikling.team6.controller.components.NavbarFooterController; +import ntnu.systemutvikling.team6.controller.components.OrganizationCardController; +import ntnu.systemutvikling.team6.database.DatabaseConnection; +import ntnu.systemutvikling.team6.database.Readers.CharitySelect; +import ntnu.systemutvikling.team6.models.Charity; +import ntnu.systemutvikling.team6.models.registry.CharityRegistry; + +/** + * This controller represents the available organization page, where the user can search for a + * charity and choose to donate to it. It also has a button to return to the front page. The user + * can search for a charity by typing in the search field, and the charities that match the search + * query will be displayed. The user can click on a charity to see more details about it, or click + * on the featured charity to see more details about it. The user can also switch to the charity + * page or donation page for the selected charity. + */ +public class AvailableOrganizationController extends BaseController implements NavbarFooterController { + + @FXML private TextField searchField; + @FXML private FlowPane cardsContainer; + + private Charity charity; + private List allCharities; + + /** + * This method is used to initialize the available organization page. It retrieves all charities + * from the database and sets up a listener on the search field to filter the charities based on + * the user's input. It also clears the cards container to prepare for displaying the filtered + * charities. The method is called automatically when the page is loaded, and it sets up the + * initial state of the page. + */ + @FXML + public void initialize() { + DatabaseConnection conn = new DatabaseConnection(); + CharitySelect db = new CharitySelect(conn); + CharityRegistry charities = db.getCharitiesFromDB(); + allCharities = charities.getAllCharities(); + + cardsContainer.getChildren().clear(); + + searchField + .textProperty() + .addListener( + (observable, oldValue, newValue) -> displayCharities(filterCharities(newValue))); + } + + /** + * This method filters the charities based on the user's input in the search field. + * + * @param query is the user's input in the search field, which is used to filter the charities. + * @return a list of charities that match the search query. + */ + private List filterCharities(String query) { + List matches = new ArrayList<>(); + + query = query.toLowerCase().trim(); + + if (query.isEmpty()) { + return matches; + } + + for (Charity charity : allCharities) { + String name = charity.getName().toLowerCase(); + String description = charity.getDescription().toLowerCase(); + + if (name.contains(query) || description.contains(query)) { + matches.add(charity); + } + } + return matches; + } + + /** + * This method displays the charities in the cards container. + * + * @param charities is a list of charities to be displayed. + */ + private void displayCharities(List charities) { + cardsContainer.getChildren().clear(); + + for (Charity charity : charities) { + try { + FXMLLoader loader = new FXMLLoader(getClass().getResource("/fxml/components/organizationCard.fxml")); + Parent card = loader.load(); + + OrganizationCardController cardController = loader.getController(); + cardController.setOrganization(charity); + + cardsContainer.getChildren().add(card); + } catch (IOException e) { + throw new RuntimeException("Could not load organization card.", e); + } + } + } + + /** + * This method is used to set the initial search query in the search field. + * + * @param query is the initial search query. + */ + @FXML + public void setInitialSearch(String query) { + if (query == null || query.isBlank()) { + return; + } + + searchField.setText(query); + } + + + /** + * This method is used to switch to the charity page for the selected charity. + * + * @param event action event from button click + */ + public void switchToCharityPage(ActionEvent event) { + LoaderScene.LoadScene("CharityPage", event, charity, null); + } + + /** + * This method is used to switch to the donation page. + * + * @param event action event from button click + */ + @FXML + public void switchToDonationPage(ActionEvent event) { + LoaderScene.LoadScene("DonationPage", event, charity, null); + } +} diff --git a/helpmehelpapplication/src/main/java/ntnu/systemutvikling/team6/controller/CharityPageController.java b/helpmehelpapplication/src/main/java/ntnu/systemutvikling/team6/controller/CharityPageController.java index 8eb853f6..121ad3be 100644 --- a/helpmehelpapplication/src/main/java/ntnu/systemutvikling/team6/controller/CharityPageController.java +++ b/helpmehelpapplication/src/main/java/ntnu/systemutvikling/team6/controller/CharityPageController.java @@ -3,6 +3,8 @@ import javafx.event.ActionEvent; import javafx.fxml.FXML; import javafx.scene.control.Label; +import javafx.scene.control.TextField; +import ntnu.systemutvikling.team6.controller.components.LoaderScene; import ntnu.systemutvikling.team6.models.Charity; /** @@ -10,6 +12,8 @@ * to donate to it. It also has a button to return to the front page. */ public class CharityPageController { + @FXML private TextField charitySearchField; + @FXML private Label CharityDescription; @FXML private Label CharityName; @@ -37,25 +41,31 @@ public void setCharity(Charity charity) { CharityName.setText(charity.getName()); } + /** - * This method is used to switch to the front page. + * This method is used to switch to the donation page. * * @param event */ @FXML - public void switchToFrontPage(ActionEvent event) { + public void switchToDonationPage(ActionEvent event) { System.out.println("Click"); - LoaderScene.LoadScene("FrontPage", event, charity); + LoaderScene.LoadScene("donationPage", event, charity, null); } /** - * This method is used to switch to the donation page. + * This method is used to search for charities based on the input in the search field. * - * @param event + * @param event is the event that triggered the search. */ @FXML - public void switchToDonationPage(ActionEvent event) { - System.out.println("Click"); - LoaderScene.LoadScene("donationPage", event, charity); + public void handleSearch(ActionEvent event) { + String query = charitySearchField.getText().trim(); + + if (query.isEmpty()) { + return; + } + + LoaderScene.LoadScene("availableOrganization", event, null, query); } } diff --git a/helpmehelpapplication/src/main/java/ntnu/systemutvikling/team6/controller/DonationPageController.java b/helpmehelpapplication/src/main/java/ntnu/systemutvikling/team6/controller/DonationPageController.java index 839a3e85..4a69ed54 100644 --- a/helpmehelpapplication/src/main/java/ntnu/systemutvikling/team6/controller/DonationPageController.java +++ b/helpmehelpapplication/src/main/java/ntnu/systemutvikling/team6/controller/DonationPageController.java @@ -7,8 +7,11 @@ import javafx.scene.control.ButtonType; import javafx.scene.control.Label; import javafx.scene.control.TextField; -import ntnu.systemutvikling.team6.DAO.DonationDAO; +import ntnu.systemutvikling.team6.controller.components.LoaderScene; +import ntnu.systemutvikling.team6.database.DAO.DonationDAO; +import ntnu.systemutvikling.team6.database.DatabaseConnection; import ntnu.systemutvikling.team6.models.Charity; +import ntnu.systemutvikling.team6.models.user.User; /** * This controller represents the donation page, where the user can enter a donation amount and @@ -21,6 +24,10 @@ public class DonationPageController { @FXML private Label CharityName; + @FXML private TextField donationSearchField; + + private DonationDAO donationSender = new DonationDAO(new DatabaseConnection()); + /** * Initialize method for the donation page. Sets the charity name label to the name of the charity * that is being donated to. The charity is set from the original page it was called from when the @@ -41,7 +48,7 @@ public void setCharity(Charity charity) { * @param event */ public void switchToFrontPage(ActionEvent event) { - LoaderScene.LoadScene("FrontPage", event, null); + LoaderScene.LoadScene("FrontPage", event, null, null); } /** @@ -50,7 +57,7 @@ public void switchToFrontPage(ActionEvent event) { * @param event */ public void switchToCharityPage(ActionEvent event) { - LoaderScene.LoadScene("charityPage", event, charity); + LoaderScene.LoadScene("charityPage", event, charity, null); } /** @@ -95,13 +102,13 @@ public void Donate(ActionEvent event) { if (result.isPresent() && result.get() == ButtonType.OK) { // Process donation - processDonation(charity, amount); + //processDonation(charity, user, amount); showAlert( Alert.AlertType.INFORMATION, "Thank you!", "You have donated " + amount + " to " + charity.getName()); donatioAmount.clear(); - LoaderScene.LoadScene("FrontPage", event, null); + LoaderScene.LoadScene("FrontPage", event, null, null); } } @@ -112,8 +119,8 @@ public void Donate(ActionEvent event) { * @param charity * @param amount */ - public void processDonation(Charity charity, double amount) { - DonationDAO.addDonation(charity, amount); + public void processDonation(Charity charity, User user, double amount) { + donationSender.addDonation(charity, user, amount); } /** @@ -130,4 +137,20 @@ private void showAlert(Alert.AlertType type, String title, String message) { alert.setContentText(message); alert.showAndWait(); } + + /** + * This method is used to handle the search action when the user clicks the search button. + * + * @param event is the event that triggered the search. + */ + @FXML + public void handleSearch(ActionEvent event) { + String query = donationSearchField.getText().trim(); + + if (query.isEmpty()) { + return; + } + + LoaderScene.LoadScene("availableOrganization", event, null, query); + } } diff --git a/helpmehelpapplication/src/main/java/ntnu/systemutvikling/team6/controller/FrontpageController.java b/helpmehelpapplication/src/main/java/ntnu/systemutvikling/team6/controller/FrontpageController.java index f8ffc2a7..b5b45226 100644 --- a/helpmehelpapplication/src/main/java/ntnu/systemutvikling/team6/controller/FrontpageController.java +++ b/helpmehelpapplication/src/main/java/ntnu/systemutvikling/team6/controller/FrontpageController.java @@ -1,17 +1,28 @@ package ntnu.systemutvikling.team6.controller; +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; import java.util.Random; import javafx.event.ActionEvent; import javafx.fxml.FXML; import javafx.fxml.FXMLLoader; import javafx.scene.Parent; +import javafx.scene.control.CheckBox; import javafx.scene.control.Label; +import javafx.scene.control.TextField; import javafx.scene.layout.FlowPane; -import ntnu.systemutvikling.team6.database.DatabaseManager; +import ntnu.systemutvikling.team6.controller.components.BaseController; +import ntnu.systemutvikling.team6.controller.components.LoaderScene; +import ntnu.systemutvikling.team6.controller.components.NavbarFooterController; +import ntnu.systemutvikling.team6.controller.components.OrganizationCardController; +import ntnu.systemutvikling.team6.database.DatabaseConnection; +import ntnu.systemutvikling.team6.database.Readers.CharitySelect; +import ntnu.systemutvikling.team6.database.Readers.DonationSelect; import ntnu.systemutvikling.team6.models.Charity; -import ntnu.systemutvikling.team6.models.CharityRegistry; import ntnu.systemutvikling.team6.models.Donation; -import ntnu.systemutvikling.team6.models.DonationRegistry; +import ntnu.systemutvikling.team6.models.registry.CharityRegistry; +import ntnu.systemutvikling.team6.models.registry.DonationRegistry; /** * Landing page's controller. This is the first page the user sees when they open the application. @@ -20,82 +31,80 @@ * it, or click on the featured charity to see more details about it. It also has buttons to switch * to the charity page and the donation page for the featured charity */ -public class FrontpageController { +public class FrontpageController extends BaseController implements NavbarFooterController { @FXML private Charity featuredCharity; - @FXML private FlowPane cardsContainer; - @FXML private Label Carosel_Organisasjon; - @FXML private Label Carosel_Beskrivelse; - @FXML private Label Total_Orgnisasjon; - @FXML private Label Total_Donations; - @FXML private Label PreApproved_Percentage; + @FXML private TextField frontSearchField; + @FXML private CheckBox verifiedFilter; + @FXML private CheckBox childrenFilter; + @FXML private CheckBox healthFilter; + @FXML private CheckBox emergencyAidFilter; + + private List allCharities = new ArrayList<>(); + + @Override + protected void authTokenisSet(){ + + } /** * Initialize method for the front page. This method is called when the front page is loaded. It * retrieves the list of charities and donations from the database. The list of charities is * displayed as a list of cards, where each card represents a charity from the - * Innsamlingskontrollen A random charity is selected to be featured on the page, and its name and - * description are displayed in the carousel section. The total number of charities, total amount - * of donations, and percentage of pre-approved charities are also displayed on the page. + * Innsamlingskontrollen. A random charity is selected to be featured on the page, and its name + * and description are displayed in the carousel section. The total number of charities, total + * amount of donations, and percentage of pre-approved charities are also displayed on the page. */ @FXML public void initialize() { try { - DatabaseManager db = new DatabaseManager(); - CharityRegistry Charities = db.getCharitiesFromDB(); - DonationRegistry Donations = db.getDonationFromDB(); - for (Charity ch : Charities.getAllCharities()) { - - FXMLLoader loader = new FXMLLoader(getClass().getResource("/fxml/organizationCard.fxml")); - - Parent card = loader.load(); - - OrganizationCardController cardController = loader.getController(); + DatabaseConnection conn = new DatabaseConnection(); + CharitySelect cdb = new CharitySelect(conn); + DonationSelect ddb = new DonationSelect(conn); + CharityRegistry charities = cdb.getCharitiesFromDB(); + DonationRegistry donations = ddb.getDonationFromDB(); - // System.out.println("Added Name: " + ch.getName() + " Added Description: " + - // ch.getDescription()); - cardController.setOrganization(ch); + allCharities = new ArrayList<>(charities.getAllCharities()); + displayCharities(allCharities); - cardsContainer.getChildren().add(card); - } - int Charities_size = Charities.getAllCharities().size(); + int charitiesSize = charities.getAllCharities().size(); Random random = new Random(); - int randomIndex = random.nextInt(Charities_size); - Charity randomCharity = Charities.getAllCharities().get(randomIndex); + int randomIndex = random.nextInt(charitiesSize); + Charity randomCharity = charities.getAllCharities().get(randomIndex); this.featuredCharity = randomCharity; Carosel_Organisasjon.setText(randomCharity.getName()); Carosel_Beskrivelse.setText(randomCharity.getDescription()); - Total_Orgnisasjon.setText(Integer.toString(Charities_size)); + Total_Orgnisasjon.setText(Integer.toString(charitiesSize)); Total_Donations.setText( Double.toString( - Donations.getAllDonations().stream().mapToDouble(Donation::getAmount).sum())); + donations.getAllDonations().stream().mapToDouble(Donation::getAmount).sum())); PreApproved_Percentage.setText( String.format( "%.2f", - Charities.getAllCharities().stream().filter(Charity::getPreApproved).count() + charities.getAllCharities().stream().filter(Charity::getPreApproved).count() * 100.0 - / Charities_size) + / charitiesSize) + "%"); - } catch (Exception e) { e.printStackTrace(); } } + /** * This method is used to switch to the charity page for the selected charity * * @param event */ public void switchToCharityPage(ActionEvent event) { - LoaderScene.LoadScene("CharityPage", event, featuredCharity); + LoaderScene.LoadScene("CharityPage", event, featuredCharity, null); } /** @@ -104,6 +113,121 @@ public void switchToCharityPage(ActionEvent event) { * @param event */ public void switchToDonationPage(ActionEvent event) { - LoaderScene.LoadScene("DonationPage", event, featuredCharity); + LoaderScene.LoadScene("DonationPage", event, featuredCharity, null); + } + + /** + * This method is used to filter the charities based on the selected filters. + * + * @param event is the event that triggered the filter. + */ + @FXML + public void handleCategoryFilterChange(ActionEvent event) { + displayCharities(getFilteredCharities()); + } + + /** + * This method is used to search for charities based on the input in the search field. + * + * @param event is the event that triggered the search. + */ + @FXML + public void handleFrontSearch(ActionEvent event) { + String query = frontSearchField.getText().trim(); + + if (query.isEmpty()) { + return; + } + + LoaderScene.LoadScene("availableOrganization", event, null, query); + } + + /** + * This method is used to filter the charities based on the selected filters. + * + * @return a list of filtered charities. + */ + private List getFilteredCharities() { + if (!verifiedFilter.isSelected() + && !childrenFilter.isSelected() + && !healthFilter.isSelected() + && !emergencyAidFilter.isSelected()) { + return allCharities; + } + + List filteredCharities = new ArrayList<>(); + for (Charity charity : allCharities) { + if (matchesSelectedFilters(charity)) { + filteredCharities.add(charity); + } + } + return filteredCharities; + } + + /** + * This method is used to check if a charity matches the selected filters. + * + * @param charity is the charity to be checked. + * @return true if the charity matches the selected filters, false otherwise. + */ + private boolean matchesSelectedFilters(Charity charity) { + return (verifiedFilter.isSelected() && charity.getPreApproved()) + || (childrenFilter.isSelected() && matchesKeywordCategory(charity, "children")) + || (healthFilter.isSelected() && matchesKeywordCategory(charity, "health")) + || (emergencyAidFilter.isSelected() && matchesKeywordCategory(charity, "emergency")); + } + + /** + * This method is used to check if a charity matches a specific category. + * + * @param charity is the charity to be checked. + * @param category is the category to check against. + * @return true if the charity matches the category, false otherwise. + */ + private boolean matchesKeywordCategory(Charity charity, String category) { + String text = (charity.getName() + " " + charity.getDescription()).toLowerCase(); + + return switch (category) { + case "children" -> + text.contains("child") + || text.contains("children") + || text.contains("barn") + || text.contains("youth") + || text.contains("young"); + case "health" -> + text.contains("health") + || text.contains("medical") + || text.contains("helse") + || text.contains("hospital") + || text.contains("care"); + case "emergency" -> + text.contains("emergency") + || text.contains("relief") + || text.contains("crisis") + || text.contains("aid") + || text.contains("disaster"); + default -> false; + }; + } + + /** + * This method is used to display the charities in the cards container. + * + * @param charities is the list of charities to be displayed. + */ + private void displayCharities(List charities) { + cardsContainer.getChildren().clear(); + + for (Charity charity : charities) { + try { + FXMLLoader loader = new FXMLLoader(getClass().getResource("/fxml/components/organizationCard.fxml")); + Parent card = loader.load(); + OrganizationCardController cardController = loader.getController(); + cardController.setOrganization(charity); + cardsContainer.getChildren().add(card); + } catch (IOException e) { + throw new RuntimeException("Could not load organization card.", e); + } + } } } diff --git a/helpmehelpapplication/src/main/java/ntnu/systemutvikling/team6/controller/components/BaseController.java b/helpmehelpapplication/src/main/java/ntnu/systemutvikling/team6/controller/components/BaseController.java new file mode 100644 index 00000000..da4af23a --- /dev/null +++ b/helpmehelpapplication/src/main/java/ntnu/systemutvikling/team6/controller/components/BaseController.java @@ -0,0 +1,17 @@ +package ntnu.systemutvikling.team6.controller.components; + +import ntnu.systemutvikling.team6.service.AuthenticationService; + +public abstract class BaseController { + protected AuthenticationService authToken; + + public void setAuthToken(AuthenticationService authToken){ + this.authToken = authToken; + authTokenisSet(); + }; + protected void authTokenisSet(){} // Do stuff after authtoken is set, on each controller + + protected boolean isLoggedin(){ + return authToken.isLoggedin() && authToken != null; + } +} diff --git a/helpmehelpapplication/src/main/java/ntnu/systemutvikling/team6/controller/LoaderScene.java b/helpmehelpapplication/src/main/java/ntnu/systemutvikling/team6/controller/components/LoaderScene.java similarity index 82% rename from helpmehelpapplication/src/main/java/ntnu/systemutvikling/team6/controller/LoaderScene.java rename to helpmehelpapplication/src/main/java/ntnu/systemutvikling/team6/controller/components/LoaderScene.java index 83ace1ea..30236e9a 100644 --- a/helpmehelpapplication/src/main/java/ntnu/systemutvikling/team6/controller/LoaderScene.java +++ b/helpmehelpapplication/src/main/java/ntnu/systemutvikling/team6/controller/components/LoaderScene.java @@ -1,4 +1,4 @@ -package ntnu.systemutvikling.team6.controller; +package ntnu.systemutvikling.team6.controller.components; import java.io.IOException; import java.util.Objects; @@ -10,6 +10,9 @@ import javafx.scene.image.Image; import javafx.stage.Stage; import ntnu.systemutvikling.team6.HmHApplication; +import ntnu.systemutvikling.team6.controller.AvailableOrganizationController; +import ntnu.systemutvikling.team6.controller.CharityPageController; +import ntnu.systemutvikling.team6.controller.DonationPageController; import ntnu.systemutvikling.team6.models.Charity; /** @@ -28,7 +31,7 @@ public class LoaderScene { * @param event * @param charity */ - public static void LoadScene(String sceneName, ActionEvent event, Charity charity) { + public static void LoadScene(String sceneName, ActionEvent event, Charity charity, String query) { try { System.out.println(HmHApplication.class.getResource("/fxml/" + sceneName + ".fxml")); FXMLLoader fxmlLoader = @@ -45,6 +48,9 @@ public static void LoadScene(String sceneName, ActionEvent event, Charity charit if (controller instanceof DonationPageController donationController) { donationController.setCharity(charity); } + if (controller instanceof AvailableOrganizationController availableOrganizationController) { + availableOrganizationController.setInitialSearch(query); + } Stage stage = (Stage) ((Node) event.getSource()).getScene().getWindow(); Image icon = diff --git a/helpmehelpapplication/src/main/java/ntnu/systemutvikling/team6/controller/components/NavbarFooterController.java b/helpmehelpapplication/src/main/java/ntnu/systemutvikling/team6/controller/components/NavbarFooterController.java new file mode 100644 index 00000000..0075893f --- /dev/null +++ b/helpmehelpapplication/src/main/java/ntnu/systemutvikling/team6/controller/components/NavbarFooterController.java @@ -0,0 +1,33 @@ +package ntnu.systemutvikling.team6.controller.components; + +import javafx.event.ActionEvent; +import javafx.fxml.FXML; +import javafx.scene.control.TextField; + +public interface NavbarFooterController { + + default void switchToFrontPage(ActionEvent event) { + System.out.println("Click!"); + LoaderScene.LoadScene("FrontPage", event, null, null); + } + + default void switchToAboutPage(ActionEvent event) { + System.out.println("Click!"); + LoaderScene.LoadScene("aboutPage", event, null, null); + } + + default void switchToProfilePage(ActionEvent event) { + System.out.println("Click!"); + LoaderScene.LoadScene("aboutPage", event, null, null); + } + + default void handleSearch(ActionEvent event) { + System.out.println("Click!"); + LoaderScene.LoadScene("aboutPage", event, null, null); + } + + default void switchToLoginPage(ActionEvent event) { + System.out.println("Click!"); + LoaderScene.LoadScene("aboutPage", event, null, null); + } +} diff --git a/helpmehelpapplication/src/main/java/ntnu/systemutvikling/team6/controller/OrganizationCardController.java b/helpmehelpapplication/src/main/java/ntnu/systemutvikling/team6/controller/components/OrganizationCardController.java similarity index 83% rename from helpmehelpapplication/src/main/java/ntnu/systemutvikling/team6/controller/OrganizationCardController.java rename to helpmehelpapplication/src/main/java/ntnu/systemutvikling/team6/controller/components/OrganizationCardController.java index b51e530e..7644bbf8 100644 --- a/helpmehelpapplication/src/main/java/ntnu/systemutvikling/team6/controller/OrganizationCardController.java +++ b/helpmehelpapplication/src/main/java/ntnu/systemutvikling/team6/controller/components/OrganizationCardController.java @@ -1,4 +1,4 @@ -package ntnu.systemutvikling.team6.controller; +package ntnu.systemutvikling.team6.controller.components; import javafx.event.ActionEvent; import javafx.fxml.FXML; @@ -27,10 +27,10 @@ public void setOrganization(Charity charity) { /* EVENTS */ public void switchToCharityPage(ActionEvent event) { - LoaderScene.LoadScene("CharityPage", event, charity); + LoaderScene.LoadScene("CharityPage", event, charity, null); } public void switchToDonationPage(ActionEvent event) { - LoaderScene.LoadScene("DonationPage", event, charity); + LoaderScene.LoadScene("DonationPage", event, charity, null); } } diff --git a/helpmehelpapplication/src/main/java/ntnu/systemutvikling/team6/DAO/DonationDAO.java b/helpmehelpapplication/src/main/java/ntnu/systemutvikling/team6/database/DAO/DonationDAO.java similarity index 72% rename from helpmehelpapplication/src/main/java/ntnu/systemutvikling/team6/DAO/DonationDAO.java rename to helpmehelpapplication/src/main/java/ntnu/systemutvikling/team6/database/DAO/DonationDAO.java index c99056f9..730accac 100644 --- a/helpmehelpapplication/src/main/java/ntnu/systemutvikling/team6/DAO/DonationDAO.java +++ b/helpmehelpapplication/src/main/java/ntnu/systemutvikling/team6/database/DAO/DonationDAO.java @@ -1,29 +1,34 @@ -package ntnu.systemutvikling.team6.DAO; +package ntnu.systemutvikling.team6.database.DAO; import java.sql.*; import java.util.UUID; import ntnu.systemutvikling.team6.database.DatabaseConnection; import ntnu.systemutvikling.team6.models.Charity; +import ntnu.systemutvikling.team6.models.user.User; /** * This class is responsible for sending concurrent information about the donation to the Donation * Database. Usally called from the DonationPageController, where the user confirms their donation. */ public class DonationDAO { - private static final DatabaseConnection connection = new DatabaseConnection(); + private final DatabaseConnection connection; - /** + public DonationDAO(DatabaseConnection connection) { + this.connection = connection; + } + + /** * Gets the total ammount of donations for a given charity, and sends it to the database throught * MySQL. * * @param charity * @param amount */ - public static void addDonation(Charity charity, double amount) { + public void addDonation(Charity charity, User user, double amount) { String sql_query = """ - INSERT INTO Donations (UUID_Donations, amount, date, Charities_UUID_charities) - VALUES (?, ?, ?, ?) + INSERT INTO Donations (UUID_Donations, amount, date, charity_id, user_id) + VALUES (?, ?, ?, ?, ?) """; try (Connection conn = connection.getMySqlConnection(); PreparedStatement ps = conn.prepareStatement(sql_query)) { @@ -33,6 +38,8 @@ INSERT INTO Donations (UUID_Donations, amount, date, Charities_UUID_charities) ps.setDouble(2, amount); ps.setDate(3, new Date(System.currentTimeMillis())); ps.setString(4, charity.getUUID().toString()); + ps.setString(5, user.getId().toString()); + ps.executeUpdate(); conn.commit(); diff --git a/helpmehelpapplication/src/main/java/ntnu/systemutvikling/team6/database/DAO/UserDAO.java b/helpmehelpapplication/src/main/java/ntnu/systemutvikling/team6/database/DAO/UserDAO.java new file mode 100644 index 00000000..2125a088 --- /dev/null +++ b/helpmehelpapplication/src/main/java/ntnu/systemutvikling/team6/database/DAO/UserDAO.java @@ -0,0 +1,94 @@ +package ntnu.systemutvikling.team6.database.DAO; + +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.SQLException; +import ntnu.systemutvikling.team6.database.DatabaseConnection; +import ntnu.systemutvikling.team6.models.user.User; + +/** + * This class is responsible for sending concurrent information about the user to the User database, + * and user settings to the settings database. + * + * @author Robin Strand Prestmo + */ +public class UserDAO { + + private final DatabaseConnection connection; + + public UserDAO(DatabaseConnection connection) { + this.connection = connection; + } + + /** + * Gets the user and settings information and sends it to the database through MySQL. + * + * + * @param user the user to be saved in the database. + * @throws RuntimeException if a database error occurs during the operation + * @return true or false based on if the register is a success or not + */ + public boolean registerUser(User user) { + String userSql = + """ + INSERT INTO User ( + UUID_User, + user_displayname, + user_name, + user_email, + user_password, + role + ) + VALUES (?, ?, ?, ?, ?, ?) + """; + + String settingsSql = + """ + INSERT INTO Settings ( + User_UUID_User, + isAnonymous, + language, + lightmode + ) + VALUES (?, ?, ?, ?) + """; + + int psUserRows = 0; + int psSettingsRows = 0; + + try (Connection conn = connection.getMySqlConnection()) { + + conn.setAutoCommit(false); + + try (PreparedStatement psUser = conn.prepareStatement(userSql)) { + + psUser.setString(1, user.getId().toString()); + psUser.setString(2, user.getDisplayName()); // display name + psUser.setString(3, user.getUsername()); // username + psUser.setString(4, user.getEmail()); + psUser.setString(5, user.getPasswordHash()); + psUser.setString(6, user.getRole().name()); + + psUserRows = psUser.executeUpdate(); + } + + try (PreparedStatement psSettings = conn.prepareStatement(settingsSql)) { + + psSettings.setString(1, user.getId().toString()); + psSettings.setBoolean(2, user.getSettings().isAnonymous()); + psSettings.setString(3, user.getSettings().getLanguage().name()); + psSettings.setBoolean(4, user.getSettings().isLightMode()); + + psSettingsRows = psSettings.executeUpdate(); + + } + + conn.commit(); + + return psUserRows > 0 && psSettingsRows > 0; + } catch (SQLException e) { + e.printStackTrace(); + return false; + } + } +} diff --git a/helpmehelpapplication/src/main/java/ntnu/systemutvikling/team6/database/DatabaseManager.java b/helpmehelpapplication/src/main/java/ntnu/systemutvikling/team6/database/DatabaseManager.java deleted file mode 100644 index a4007a05..00000000 --- a/helpmehelpapplication/src/main/java/ntnu/systemutvikling/team6/database/DatabaseManager.java +++ /dev/null @@ -1,299 +0,0 @@ -package ntnu.systemutvikling.team6.database; - -import java.sql.*; -import java.util.List; -import java.util.UUID; -import ntnu.systemutvikling.team6.models.Charity; -import ntnu.systemutvikling.team6.models.CharityRegistry; -import ntnu.systemutvikling.team6.models.Donation; -import ntnu.systemutvikling.team6.models.DonationRegistry; -import ntnu.systemutvikling.team6.scraper.APICharityData; - -/** - * Manages the Database with MySQL tables and JDBC. - * - *

This class is responsible for creating the tables needed for the application, if not done - * already and maintaining the {@code charities} table based on data retrieved from the IK API. It - * is also responsible for retrieving the data from the database and sending it to the application - * through the CharityRegistry and DonationRegistry. It is used by the FrontpageController to - * retrieve the data needed to display the charities - */ -public class DatabaseManager { - private final DatabaseConnection connection; - - /** - * Contractor for DatabaseManager. It uses a DatabaseConnection object that contains a connection - * credentials. - */ - public DatabaseManager() { - this.connection = new DatabaseConnection(); - } - - /** - * 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. - * - *

The table structure for Charities is based on fields from {@link APICharityData}. - * - * @throws RuntimeException if a {@link SQLException} occurs while creating the table - */ - public void createTables() { - String sql_query1 = - """ - -- ----------------------------------------------------- - -- Table `HelpMeHelp`.`Charities` - -- ----------------------------------------------------- - CREATE TABLE IF NOT EXISTS Charities ( - UUID_charities CHAR(36) PRIMARY KEY, - org_number VARCHAR(255) NOT NULL, - charity_name VARCHAR(255) NOT NULL, - charity_link VARCHAR(255) NOT NULL, - pre_approved TINYINT NOT NULL, - status VARCHAR(255) NOT NULL, - UNIQUE KEY unique_org_number (org_number) - ) ENGINE=InnoDB; - - - """; - String sql_query2 = - """ - -- ----------------------------------------------------- - -- Table `HelpMeHelp`.`Donations` - -- ----------------------------------------------------- - CREATE TABLE IF NOT EXISTS Donations ( - `UUID_Donations` CHAR(36) NOT NULL, - `amount` DECIMAL NOT NULL, - `date` DATE NOT NULL, - `Charities_UUID_charities` CHAR(36) NOT NULL, - PRIMARY KEY (`UUID_Donations`), - INDEX `fk_Donations_Charities_idx` (`Charities_UUID_charities` ASC) VISIBLE, - CONSTRAINT `fk_Donations_Charities` - FOREIGN KEY (`Charities_UUID_charities`) - REFERENCES Charities (`UUID_charities`) - ON DELETE CASCADE - ON UPDATE CASCADE) - ENGINE = InnoDB; - """; - - try (Connection conn = connection.getMySqlConnection(); - Statement s = conn.createStatement()) { - - s.execute(sql_query1); - s.execute(sql_query2); - } catch (SQLException e) { - e.printStackTrace(); - throw new RuntimeException("Error creating table."); - } - } - - /** - * This method is used to verify the integrity of the data in the {@code charities} table and to - * update it based on the data retrieved from the IK API. The param charities are retrieved from - * the IK API through the APICharityData class. Called in initialize method in - * HmHApplication.java, which is the main class of the application, to ensure that the data is up - * to date when the application starts. Uses a a temp table to ensure that the data in the - * database is consistent with the data from the API. - * - * @param charities - */ - public void addAPIDataToTable(List charities) { - Connection conn = null; - try { - conn = connection.getMySqlConnection(); - conn.setAutoCommit(false); - String sql_query = - """ - INSERT INTO Charities (UUID_charities, org_number, charity_name, charity_link, pre_approved, status) - VALUES (?, ?, ?, ?, ?, ?) - ON DUPLICATE KEY UPDATE - charity_name = VALUES(charity_name), - charity_link = VALUES(charity_link), - pre_approved = VALUES(pre_approved), - status = VALUES(status) - """; - - try (PreparedStatement ps = conn.prepareStatement(sql_query)) { - for (Charity charity : charities) { - if (charity.getUUID() == null) { - ps.setString(1, UUID.randomUUID().toString()); - } else { - ps.setString(1, charity.getUUID().toString()); - } - - ps.setString(2, charity.getOrg_number().replaceAll("\\s", "")); - ps.setString(3, charity.getName()); - ps.setString(4, charity.getDescription()); - ps.setBoolean(5, charity.getPreApproved()); // Description is the link - ps.setString(6, charity.getStatus()); - - ps.addBatch(); - } - ps.executeBatch(); - } - - // -- Intergerty Check: - String createTemp = - """ - CREATE TEMPORARY TABLE temp_api_charities ( - org_number VARCHAR(20) PRIMARY KEY - ) - """; - - try (PreparedStatement ps = conn.prepareStatement(createTemp)) { - ps.execute(); - } - - String insertTemp = "INSERT IGNORE INTO temp_api_charities (org_number) VALUES (?)"; - - try (PreparedStatement ps = conn.prepareStatement(insertTemp)) { - - for (Charity charity : charities) { - ps.setString(1, charity.getOrg_number().replaceAll("\\s", "")); - ps.addBatch(); - } - - ps.executeBatch(); - } - - String deleteSql = - """ - DELETE FROM Charities c - WHERE NOT EXISTS ( - SELECT 1 - FROM temp_api_charities t - WHERE t.org_number = c.org_number - ) - """; - - try (PreparedStatement ps = conn.prepareStatement(deleteSql)) { - ps.executeUpdate(); - } - - conn.commit(); - - } catch (SQLException e) { - if (conn != null) { - try { - conn.rollback(); - } catch (SQLException ex) { - ex.printStackTrace(); - } - } - e.printStackTrace(); - - throw new RuntimeException("ERROR: Something went wrong during updating charities table."); - } finally { - if (conn != null) { - try { - conn.setAutoCommit(true); - conn.close(); - } catch (SQLException e) { - e.printStackTrace(); - } - } - } - } - - public CharityRegistry getCharitiesFromDB() { - CharityRegistry registry = null; - Connection conn = null; - try { - conn = connection.getMySqlConnection(); - String sql_query = - "SELECT UUID_charities, org_number, charity_name, charity_link, pre_approved, status FROM Charities"; - Statement stmt = conn.createStatement(); - ResultSet rs = stmt.executeQuery(sql_query); - - registry = new CharityRegistry(); - while (rs.next()) { - Charity charity = - 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(charity); - } - } catch (SQLException e) { - e.printStackTrace(); - throw new RuntimeException("ERROR: Something went wrong during updating charities table."); - } - return registry; - } - - 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/DatabaseSetup.java b/helpmehelpapplication/src/main/java/ntnu/systemutvikling/team6/database/DatabaseSetup.java new file mode 100644 index 00000000..e809dd43 --- /dev/null +++ b/helpmehelpapplication/src/main/java/ntnu/systemutvikling/team6/database/DatabaseSetup.java @@ -0,0 +1,289 @@ +package ntnu.systemutvikling.team6.database; + +import java.sql.*; +import ntnu.systemutvikling.team6.models.*; +import ntnu.systemutvikling.team6.models.user.*; + +/** + * Manages the Database with MySQL tables and test connection. + * + *

This class object is able to create MySQL to ntnu localized database and able to + * testConnection to it. + */ +public class DatabaseSetup { + private final DatabaseConnection connection; + + /** + * Contractor for DatabaseManager. It uses a DatabaseConnection object that contains a connection + * credentials. + */ + public DatabaseSetup(DatabaseConnection connection) { + this.connection = connection; + } + + /** + * Connection test for the Database. Does a simple SELECT SQL query and returns either true og and + * Exception if failed + * + * @return true if Sucsedd or SQLExepction if failed + */ + public boolean testConnection() { + try (Connection conn = connection.getMySqlConnection(); + Statement stmt = conn.createStatement()) { + + ResultSet rs = stmt.executeQuery("SELECT 1"); + + if (rs.next()) { + System.out.println("Database connection verified."); + return true; + } + + } catch (SQLException e) { + System.out.println("Database connection failed."); + e.printStackTrace(); + } + return false; + } + + /** + * Creates the {@code Charities} and {@code Donations} tables if they do not already exist. + * + * @throws RuntimeException if a {@link SQLException} occurs while creating the table + */ + public void createTables() { + String charitiesTable = + """ + -- ----------------------------------------------------- + -- Table `HelpMeHelp`.`Charities` + -- ----------------------------------------------------- + CREATE TABLE IF NOT EXISTS `apbaluna`.`Charities` ( + `UUID_charities` CHAR(36) NOT NULL, + `org_number` VARCHAR(255) NOT NULL, + `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` ( + `UUID_user` CHAR(36) NOT NULL, + `isAnonymous` TINYINT NOT NULL, + `language` VARCHAR(45) NOT NULL, + `lightmode` TINYINT NOT NULL, + PRIMARY KEY (`UUID_user`), + INDEX `fk_Settings_User1_idx` (`UUID_user` ASC) VISIBLE, + CONSTRAINT `fk_Settings_User1` + FOREIGN KEY (`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; + """; + String charityVanityTable = + """ + CREATE TABLE IF NOT EXISTS `apbaluna`.`CharityVanity` ( + `UUID_charity` CHAR(36) NOT NULL, + `charity_name` VARCHAR(255) NOT NULL, + `charity_link` VARCHAR(255) NOT NULL, + `description` TEXT NULL, + `logoURL` TEXT NULL, + `key_values` TEXT NULL, + `logoBLOB` MEDIUMBLOB NULL, + INDEX `fk_CharityVanity_Charities1_idx` (`UUID_charity` ASC) VISIBLE, + PRIMARY KEY (`UUID_charity`), + CONSTRAINT `fk_CharityVanity_Charities1` + FOREIGN KEY (`UUID_charity`) + REFERENCES `apbaluna`.`Charities` (`UUID_charities`) + 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); + s.execute(charityVanityTable); + } 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 00000000..cb39b223 --- /dev/null +++ b/helpmehelpapplication/src/main/java/ntnu/systemutvikling/team6/database/Readers/CharitySelect.java @@ -0,0 +1,197 @@ +package ntnu.systemutvikling.team6.database.Readers; + +import java.sql.*; +import java.time.LocalDate; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.Set; +import ntnu.systemutvikling.team6.database.DatabaseConnection; +import ntnu.systemutvikling.team6.models.Charity; +import ntnu.systemutvikling.team6.models.Feedback; +import ntnu.systemutvikling.team6.models.registry.CharityRegistry; +import ntnu.systemutvikling.team6.models.user.Language; +import ntnu.systemutvikling.team6.models.user.Settings; +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}, {@code + * User}, {@code CharityVanity}, and {@code category(s)} 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 and categories 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.pre_approved, c.status, + f.UUID_feedback, f.feedback_comment, f.feedback_date, f.isAnonymous, f.charity_id, f.user_id, + cv.charity_name, cv.charity_link, cv.description, cv.logoURL, cv.key_values, cv.logoBLOB, + u.UUID_user, u.user_displayname, u.user_name, u.user_email, u.user_password, u.role, + cat.category + 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 + LEFT JOIN Charity_Categories cc ON cc.Charities_UUID_charities = c.UUID_charities + LEFT JOIN Cat|egories cat ON cat.category_id = cc.Categories_category_id + INNER JOIN CharityVanity cv ON cv.UUID_charity = c.UUID_charities; + """; + Statement stmt = conn.createStatement(); + ResultSet rs = stmt.executeQuery(sql_query); + + Charity currentCharity = null; + String lastCharity = null; + + Set seenFeedbackIds = new HashSet<>(); + + 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_name"), + rs.getString("charity_link"), + rs.getString("status"), + rs.getBoolean("pre_approved"), + rs.getString("description"), + rs.getString("logoURL"), + rs.getString("key_values"), + rs.getBytes("logoBLOB")); + registry.addCharity(currentCharity); + lastCharity = currentId; + seenFeedbackIds.clear(); + } + + String categoryName = rs.getString("category"); + if (categoryName != null & !currentCharity.getCategory().contains(categoryName)) { + currentCharity.getCategory().add(categoryName); + } + + String feedbackId = rs.getString("UUID_feedback"); + if (feedbackId != null && !seenFeedbackIds.contains(feedbackId)) { + seenFeedbackIds.add(feedbackId); + User userWithMinimalSettingsAndInbox = + new User( + rs.getString("UUID_User"), + rs.getString("user_displayname"), + rs.getString("user_name"), + rs.getString("user_email"), + rs.getString("user_password"), + rs.getString("role")); + userWithMinimalSettingsAndInbox.setSettings(new Settings(false, Language.ENGLISH, false)); + + Feedback feedback = + new Feedback( + rs.getString("UUID_feedback"), + userWithMinimalSettingsAndInbox, + rs.getString("feedback_comment"), + LocalDate.parse(rs.getString("feedback_date"))); + + currentCharity.getFeedbacks().add(feedback); + } + } + + } catch (SQLException e) { + e.printStackTrace(); + throw new RuntimeException("ERROR: Something went wrong during updating charities table."); + } + return registry; + } + + /** + * A helper function that retrieves all feedback entries associated with a specific charity, + * identified by its UUID. Currently, has no use. + * + *

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

Retrieves donations along with their associated {@link Charity} by performing an INNER JOIN + * between the {@code Donations} and {@code Charities} tables. Only donations with a matching + * charity record are included. Donor ({@link User}) and {@code CharityVanity} details are + * intentionally excluded to keep this query lightweight — join those separately if richer data is + * needed. + * + *

Note: {@code CharityVanity} fields (name, link, description, logo) are NOT fetched here since + * they live in a separate table. The {@link Charity} objects returned will only contain the core + * fields present in the {@code Charities} table. + */ +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.isAnonymous, d.date, d.charity_id, d.user_id, + c.UUID_charities, c.org_number, c,pre_approved, c.status, + u.UUID_User, u.user_displayname u.user_name, u.user_email, u.user_password, u.role + FROM Donations d + INNER JOIN Charities c ON d.charity_id = c.UUID_charities + INNER JOIN User u ON d.user_id = u.UUID_user + """; + 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.getBoolean("pre_approved"), + rs.getString("status")); + + User user = + new User( + rs.getString("UUID_User"), + rs.getString("user_displayname"), + rs.getString("user_name"), + rs.getString("user_email"), + rs.getString("user_password"), + rs.getString("role")); + Donation donation = + new Donation( + rs.getString("UUID_Donations"), + rs.getDouble("amount"), + rs.getDate("date").toLocalDate(), + charity, + user, + rs.getBoolean("isAnonymous")); + 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 00000000..4f467457 --- /dev/null +++ b/helpmehelpapplication/src/main/java/ntnu/systemutvikling/team6/database/Readers/UserSelect.java @@ -0,0 +1,383 @@ +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.registry.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; + } + + public boolean isUsernameTaken(String username){ + try (Connection conn = connection.getMySqlConnection(); + Statement stmt = conn.createStatement()) { + + String mysql = + """ + SELECT UUID_User FROM User WHERE user_name = ? + """; + PreparedStatement statement = conn.prepareStatement(mysql); + statement.setString(1, username); + ResultSet rs = statement.executeQuery(); + + if (rs.next()) { + System.out.println("Username Taken already"); + return true; + } + + } catch (SQLException e) { + e.printStackTrace(); + } + return false; + } + + + /** + * 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_displayname, 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_name = ? 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_displayname"), + 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_displayname 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_displayname"), + 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_displayname 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_displayname"), + 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 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_content"), + 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 d97d9968..e019aead 100644 --- a/helpmehelpapplication/src/main/java/ntnu/systemutvikling/team6/models/Charity.java +++ b/helpmehelpapplication/src/main/java/ntnu/systemutvikling/team6/models/Charity.java @@ -20,8 +20,8 @@ public class Charity { /* Name of the charity */ private String name; - /* Description of the charity's mission and activities */ - private String description; + /* URL of the charity */ + private String url; /* Is the charity verified? */ private String status; @@ -29,11 +29,40 @@ public class Charity { private boolean is_pre_approved; /* Category for the charity */ - private String category; + private List category; + + /* Description for the charity */ + private String description; + + /* URL for the logo of the charity */ + private String logoURL; + + /* Key values for the charity */ + private String keyValues; /* List that contains the charity's Feedbacks */ private List feedbacks; + /* Bytecode for the charity logo */ + private byte[] logoBlob; + + /** + * Minimal contructor JUST FOR DONATIONSSELECT. Just cause donation object needs to only contain + * information about receiver {@code Chairty } and donator {@code User}, and not necessarily Urls, + * logos, and etc. + * + * @param uuid from DonationSelect + * @param org_number matches from DonationSelect + * @param is_pre_approved name matches from DonationSelect + * @param status name matches from DonationSelect + */ + public Charity(String uuid, String org_number, Boolean is_pre_approved, String status) { + this.UUID = java.util.UUID.fromString(uuid); + this.org_number = org_number.replaceAll("\\s", ""); + this.is_pre_approved = is_pre_approved; + this.status = status; + } + /** * Contructor for creating a new charity. Taylored to match data given from Api. Other attributes * will just be initialized as empty @@ -48,16 +77,17 @@ public Charity( this.UUID = java.util.UUID.randomUUID(); this.org_number = org_number.replaceAll("\\s", ""); this.name = name; - this.description = "Les mer her: " + link; + this.url = link; this.is_pre_approved = is_pre_approved; this.status = status; this.feedbacks = new ArrayList<>(); - this.category = ""; + this.category = new ArrayList<>(); } /** - * Contructor for creating a new charity. Taylored to match data given from DATABASE. Other - * attributes will just be initialized as empty + * Contructor for creating a new charity. Taylored to match data given from DATABASE. Expects + * paramaters that will fill all attributes. EXECPT for feedbacks and categories (which is done + * right after). * * @param org_number matches from innsamlingkontrollen * @param name matches from innsamlingkontrollen @@ -67,18 +97,26 @@ public Charity( public Charity( String uuid, String org_number, - String link, String name, + String url, + String status, boolean is_pre_approved, - String status) { + String description, + String logoURL, + String keyValues, + byte[] logblob) { this.UUID = UUID.fromString(uuid); this.org_number = org_number.replaceAll("\\s", ""); this.name = name; - this.description = link; + this.url = url; this.is_pre_approved = is_pre_approved; this.status = status; + this.category = new ArrayList<>(); + this.description = description; + this.logoURL = logoURL; + this.keyValues = keyValues; this.feedbacks = new ArrayList<>(); - this.category = ""; + this.logoBlob = logblob; } /** Getters for the charity's attributes. */ @@ -102,7 +140,7 @@ public List getFeedbacks() { return feedbacks; } - public String getCategory() { + public List getCategory() { return category; } @@ -110,8 +148,24 @@ public String getName() { return name; } + public String getURL() { + return this.url; + } + public String getDescription() { - return description; + return this.description; + } + + public String getLogoURL() { + return this.logoURL; + } + + public String getKeyValues() { + return this.keyValues; + } + + public byte[] getLogoBlob() { + return this.logoBlob; } /** Setter for verification status. This one sets the charity as verified. */ @@ -123,4 +177,34 @@ public void setVerified() { public void setUnverified() { this.status = "Veto"; } + + /** Setter for categories. */ + public void setCategory(List category) { + this.category = category; + } + + /** Setter for description. */ + public void setDescription(String description) { + this.description = description; + } + + /** Setter for the URL of the charity's logo. */ + public void setLogoURL(String url) { + this.logoURL = url; + } + + /** Setter for the charity's key values. */ + public void setKeyValues(String values) { + this.keyValues = values; + } + + /** Setter for the charity's logo Blob. */ + public void setLogoBlob(byte[] logoBlob) { + this.logoBlob = logoBlob; + } + + /** Setter for setting feedbacks */ + public void setFeedbacks(ArrayList feedbacks) { + this.feedbacks = feedbacks; + } } diff --git a/helpmehelpapplication/src/main/java/ntnu/systemutvikling/team6/models/Donation.java b/helpmehelpapplication/src/main/java/ntnu/systemutvikling/team6/models/Donation.java index 64d87331..c058688a 100644 --- a/helpmehelpapplication/src/main/java/ntnu/systemutvikling/team6/models/Donation.java +++ b/helpmehelpapplication/src/main/java/ntnu/systemutvikling/team6/models/Donation.java @@ -6,7 +6,7 @@ public class Donation { /* UUID for uniquely identifying each donation */ - private UUID charityId; + private UUID donationID; /* Ammount of money donated */ private double amount; @@ -33,7 +33,7 @@ public class Donation { * @param donor */ public Donation(double amount, LocalDate date, Charity charity, User donor) { - this.charityId = UUID.randomUUID(); + this.donationID = UUID.randomUUID(); this.amount = amount; this.date = date; this.charity = charity; @@ -42,21 +42,28 @@ public Donation(double amount, LocalDate date, Charity charity, User donor) { } /** - * Constructor for creating a new donation. Taylored for getting info FROM DATABASE. NEEDS TO BE - * CHANGED in phase 3. + * Constructor for creating a donation reed from the database. * - * @param amount - * @param date - * @param charity - * @param uuid + * @param donationId the stored UUID string for this donation; must not be {@code null} + * @param amount the donated amount + * @param date the date the donation was made; must not be {@code null} + * @param charity the receiving charity; must not be {@code null} + * @param donor the donating user, or {@code null} if anonymous + * @param isAnonymous whether the donation was recorded as anonymous */ - public Donation(String uuid, double amount, LocalDate date, Charity charity) { - this.charityId = UUID.fromString(uuid); + public Donation( + String donationId, + double amount, + LocalDate date, + Charity charity, + User donor, + boolean isAnonymous) { + this.donationID = UUID.fromString(donationId); this.amount = amount; this.date = date; this.charity = charity; - this.donor = null; - this.isAnonymous = true; + this.donor = donor; + this.isAnonymous = isAnonymous; } /* Getters for the donation's attributes */ @@ -65,7 +72,11 @@ public boolean isAnonymous() { } public UUID getCharityId() { - return charityId; + return charity.getUUID(); + } + + public UUID getDonationID() { + return donationID; } public double getAmount() { diff --git a/helpmehelpapplication/src/main/java/ntnu/systemutvikling/team6/models/Feedback.java b/helpmehelpapplication/src/main/java/ntnu/systemutvikling/team6/models/Feedback.java index 490c47e6..6e78c3b2 100644 --- a/helpmehelpapplication/src/main/java/ntnu/systemutvikling/team6/models/Feedback.java +++ b/helpmehelpapplication/src/main/java/ntnu/systemutvikling/team6/models/Feedback.java @@ -1,6 +1,6 @@ package ntnu.systemutvikling.team6.models; -import java.time.LocalDateTime; +import java.time.LocalDate; import java.util.UUID; import ntnu.systemutvikling.team6.models.user.User; @@ -15,13 +15,13 @@ public class Feedback { private String comment; /* The date and time when the feedback was given */ - private LocalDateTime date; + private LocalDate date; /* Is the feedback given anonymously? */ private boolean isAnonymous; /** - * Constructor for creating a new feedback. + * Constructor for creating a new feedback now. * * @param user The user who gives the feedback. * @param comment The content of the feedback. @@ -30,7 +30,22 @@ public Feedback(User user, String comment) { this.feedbackId = UUID.randomUUID(); this.user = user; this.comment = comment; - this.date = LocalDateTime.now(); + this.date = LocalDate.now(); + this.isAnonymous = user.getSettings().isAnonymous(); + } + + /** + * Constructor for creating a new feedback, based on making a feedback previously made. + * + * @param user The user who gives the feedback. + * @param comment The content of the feedback. + * @param feedback_date The content of the feedback. + */ + public Feedback(String feedback_id, User user, String feedback_comment, LocalDate feedback_date) { + this.feedbackId = UUID.fromString(feedback_id); + this.user = user; + this.comment = feedback_comment; + this.date = feedback_date; this.isAnonymous = user.getSettings().isAnonymous(); } @@ -48,7 +63,7 @@ public String getComment() { return comment; } - public LocalDateTime getDate() { + public LocalDate getDate() { return date; } diff --git a/helpmehelpapplication/src/main/java/ntnu/systemutvikling/team6/models/UserRegistry.java b/helpmehelpapplication/src/main/java/ntnu/systemutvikling/team6/models/UserRegistry.java deleted file mode 100644 index 514cbec1..00000000 --- a/helpmehelpapplication/src/main/java/ntnu/systemutvikling/team6/models/UserRegistry.java +++ /dev/null @@ -1,3 +0,0 @@ -package ntnu.systemutvikling.team6.models; - -public class UserRegistry {} diff --git a/helpmehelpapplication/src/main/java/ntnu/systemutvikling/team6/models/CharityRegistry.java b/helpmehelpapplication/src/main/java/ntnu/systemutvikling/team6/models/registry/CharityRegistry.java similarity index 93% rename from helpmehelpapplication/src/main/java/ntnu/systemutvikling/team6/models/CharityRegistry.java rename to helpmehelpapplication/src/main/java/ntnu/systemutvikling/team6/models/registry/CharityRegistry.java index 962b8338..c6aabbdd 100644 --- a/helpmehelpapplication/src/main/java/ntnu/systemutvikling/team6/models/CharityRegistry.java +++ b/helpmehelpapplication/src/main/java/ntnu/systemutvikling/team6/models/registry/CharityRegistry.java @@ -1,6 +1,7 @@ -package ntnu.systemutvikling.team6.models; +package ntnu.systemutvikling.team6.models.registry; import java.util.*; +import ntnu.systemutvikling.team6.models.Charity; public class CharityRegistry { private final List charities; diff --git a/helpmehelpapplication/src/main/java/ntnu/systemutvikling/team6/models/DonationRegistry.java b/helpmehelpapplication/src/main/java/ntnu/systemutvikling/team6/models/registry/DonationRegistry.java similarity index 81% rename from helpmehelpapplication/src/main/java/ntnu/systemutvikling/team6/models/DonationRegistry.java rename to helpmehelpapplication/src/main/java/ntnu/systemutvikling/team6/models/registry/DonationRegistry.java index b06009dd..d132a710 100644 --- a/helpmehelpapplication/src/main/java/ntnu/systemutvikling/team6/models/DonationRegistry.java +++ b/helpmehelpapplication/src/main/java/ntnu/systemutvikling/team6/models/registry/DonationRegistry.java @@ -1,6 +1,7 @@ -package ntnu.systemutvikling.team6.models; +package ntnu.systemutvikling.team6.models.registry; import java.util.*; +import ntnu.systemutvikling.team6.models.Donation; public class DonationRegistry { private final List donations; @@ -18,7 +19,7 @@ public Optional findDonationById(UUID donationId) { throw new IllegalArgumentException("DonationId can not be null."); } return donations.stream() - .filter(donations -> donationId.equals(donations.getCharityId())) + .filter(donations -> donationId.equals(donations.getDonationID())) .findFirst(); } @@ -33,6 +34,6 @@ public boolean removeDonation(UUID donationId) { if (donationId == null) { throw new IllegalArgumentException("DonationId can not be null."); } - return donations.removeIf(donation -> donationId.equals(donation.getCharityId())); + return donations.removeIf(donation -> donationId.equals(donation.getDonationID())); } } diff --git a/helpmehelpapplication/src/main/java/ntnu/systemutvikling/team6/models/registry/UserRegistry.java b/helpmehelpapplication/src/main/java/ntnu/systemutvikling/team6/models/registry/UserRegistry.java new file mode 100644 index 00000000..7aa85fb3 --- /dev/null +++ b/helpmehelpapplication/src/main/java/ntnu/systemutvikling/team6/models/registry/UserRegistry.java @@ -0,0 +1,37 @@ +package ntnu.systemutvikling.team6.models.registry; + +import java.util.*; +import ntnu.systemutvikling.team6.models.user.User; + +public class UserRegistry { + private final List Users; + + public UserRegistry() { + this.Users = new ArrayList<>(); + } + + public List getAllUsers() { + return Collections.unmodifiableList(Users); + } + + public Optional findUserById(UUID userUUID) { + if (userUUID == null) { + throw new IllegalArgumentException("DonationId can not be null."); + } + return Users.stream().filter(u -> userUUID.equals(u.getId())).findFirst(); + } + + public void addUser(User user) { + if (user == null) { + throw new IllegalArgumentException("Donation can not be null."); + } + Users.add(user); + } + + public boolean removeUserByUUID(UUID userUUID) { + if (userUUID == null) { + throw new IllegalArgumentException("DonationId can not be null."); + } + return Users.removeIf(user -> userUUID.equals(user.getId())); + } +} diff --git a/helpmehelpapplication/src/main/java/ntnu/systemutvikling/team6/models/user/Language.java b/helpmehelpapplication/src/main/java/ntnu/systemutvikling/team6/models/user/Language.java index c568ede3..cbd66cf4 100644 --- a/helpmehelpapplication/src/main/java/ntnu/systemutvikling/team6/models/user/Language.java +++ b/helpmehelpapplication/src/main/java/ntnu/systemutvikling/team6/models/user/Language.java @@ -6,5 +6,6 @@ * @author Robin Strand Prestmo */ public enum Language { + NORWEGIAN, ENGLISH } diff --git a/helpmehelpapplication/src/main/java/ntnu/systemutvikling/team6/models/user/Message.java b/helpmehelpapplication/src/main/java/ntnu/systemutvikling/team6/models/user/Message.java index b70ece1b..3a2a75d2 100644 --- a/helpmehelpapplication/src/main/java/ntnu/systemutvikling/team6/models/user/Message.java +++ b/helpmehelpapplication/src/main/java/ntnu/systemutvikling/team6/models/user/Message.java @@ -1,6 +1,6 @@ package ntnu.systemutvikling.team6.models.user; -import java.time.LocalDateTime; +import java.time.LocalDate; import java.util.UUID; // Enhetstester mangler @@ -13,9 +13,9 @@ public class Message { private final UUID id; private final String title; - private final String from; + private final UUID fromCharityID; private final String content; - private final LocalDateTime timeAndDate; + private final LocalDate timeAndDate; /** * Creates a message with a unique identifier. The message includes a title, a string who it's @@ -26,13 +26,13 @@ public class Message { * @param content the content of the message * @throws IllegalArgumentException if title, from or content is null or blank. */ - public Message(String title, String from, String content) { + public Message(String title, UUID from, String content) { if (title == null || title.isBlank()) { throw new IllegalArgumentException("Title cannot be null or blank."); } - if (from == null || from.isBlank()) { + if (from == null) { throw new IllegalArgumentException("From cannot be null or blank."); } @@ -42,9 +42,44 @@ public Message(String title, String from, String content) { this.id = UUID.randomUUID(); this.title = title; - this.from = from; + this.fromCharityID = from; this.content = content; - this.timeAndDate = LocalDateTime.now(); + this.timeAndDate = LocalDate.now(); + } + + /** + * Creates a message with a unique identifier. The message includes a title, a string who it's + * from, content and the time and date. This one creates a message that has been created before. + * + * @param title the title of the message + * @param from who the message is from + * @param content the content of the message + * @param date date of when the message was created + * @throws IllegalArgumentException if title, from or content is null or blank. + */ + public Message(String title, UUID from, String content, LocalDate date) { + + if (title == null || title.isBlank()) { + throw new IllegalArgumentException("Title cannot be null or blank."); + } + + if (from == null) { + throw new IllegalArgumentException("From cannot be null or blank."); + } + + if (content == null || content.isBlank()) { + throw new IllegalArgumentException("Content cannot be null or blank."); + } + + if (date == null) { + throw new IllegalArgumentException("Content cannot be null or blank."); + } + + this.id = UUID.randomUUID(); + this.title = title; + this.fromCharityID = from; + this.content = content; + this.timeAndDate = date; } public UUID getId() { @@ -55,15 +90,15 @@ public String getTitle() { return title; } - public String getFrom() { - return from; + public UUID getFrom() { + return fromCharityID; } public String getContent() { return content; } - public LocalDateTime getTimeAndDate() { + public LocalDate getTimeAndDate() { return timeAndDate; } } diff --git a/helpmehelpapplication/src/main/java/ntnu/systemutvikling/team6/models/user/Settings.java b/helpmehelpapplication/src/main/java/ntnu/systemutvikling/team6/models/user/Settings.java index d8142836..086ddb9a 100644 --- a/helpmehelpapplication/src/main/java/ntnu/systemutvikling/team6/models/user/Settings.java +++ b/helpmehelpapplication/src/main/java/ntnu/systemutvikling/team6/models/user/Settings.java @@ -8,13 +8,13 @@ * @author Robin Strand Prestmo */ public class Settings { - private boolean lightMode; - private Language language; private boolean anonymous; + private Language language; + private boolean lightMode; /** Sets standard settings. LightMode enabled, language set to English, Anonymous disabled */ public Settings() { - this(true, Language.ENGLISH, false); + this(false, Language.ENGLISH, true); } /** @@ -24,7 +24,7 @@ public Settings() { * @param language choose language * @param anonymous choose if user is anonymous */ - public Settings(boolean lightMode, Language language, boolean anonymous) { + public Settings(boolean anonymous, Language language, boolean lightMode) { if (language == null) { throw new IllegalArgumentException("Language cannot be null"); } diff --git a/helpmehelpapplication/src/main/java/ntnu/systemutvikling/team6/models/user/User.java b/helpmehelpapplication/src/main/java/ntnu/systemutvikling/team6/models/user/User.java index ef590006..04fc6969 100644 --- a/helpmehelpapplication/src/main/java/ntnu/systemutvikling/team6/models/user/User.java +++ b/helpmehelpapplication/src/main/java/ntnu/systemutvikling/team6/models/user/User.java @@ -17,18 +17,19 @@ public class User { private static final PasswordHasher passwordHasher = new PasswordHasher(); private final UUID id; - private String name; + private String displayName; + private String username; private String email; private String passwordHash; private final Role role; - private final Settings settings; - private final Inbox inbox; + private Settings settings; + private Inbox inbox; /** * Creates a new user. * - * @param id gives the user a unique identifier with UUID - * @param name the name of the user + * @param displayName the name of the user that is shown + * @param username unqiue username used for login * @param email the email of the user * @param password the password for the user * @param role users role @@ -37,9 +38,12 @@ public class User { * @throws IllegalArgumentException if any required argument is invalid. */ public User( - String name, String email, String password, Role role, Settings settings, Inbox inbox) { + String displayName, String username, String email, String password, Role role, Settings settings, Inbox inbox) { + if (displayName == null || username.isBlank()){ + throw new IllegalArgumentException("displayName cannot be null or blank."); + } - if (name == null || name.isBlank()) { + if (username == null || username.isBlank()) { throw new IllegalArgumentException("Name cannot be null or blank."); } @@ -61,7 +65,8 @@ public User( } this.id = UUID.randomUUID(); - this.name = name; + this.displayName = displayName; + this.username = username; this.email = email; this.passwordHash = passwordHasher.getHashPassword(password); this.role = role; @@ -69,20 +74,69 @@ public User( this.inbox = inbox; } + /** + * Creates a new user taylored for getting info from DATABASE. Settings and inbox can be set on a + * later date throught another method in databaseManager class + * + * @param uuid gives the user a unique identifier with UUID + * @param displayName the name of the user that is shown + * @param username the name of the user + * @param email the email of the user + * @param password the password for the user + * @param role users role + * @throws IllegalArgumentException if any required argument is invalid. + */ + public User(String uuid, String displayName, String username, String email, String password, String role) { + + if (uuid == null || uuid.isBlank()) { + throw new IllegalArgumentException("UUID cannot be null or blank."); + } + + if (username == null || username.isBlank()) { + throw new IllegalArgumentException("Name cannot be null or blank."); + } + + if (email == null || email.isBlank() || !email.contains("@") || !email.contains(".")) { + throw new IllegalArgumentException( + "Email cannot be null or blank," + " and must contain '@' and '.'"); + } + + if (role == null) { + throw new IllegalArgumentException("Role cannot be null"); + } + + this.id = UUID.fromString(uuid); + this.displayName = displayName; + this.username = username; + this.email = email; + this.passwordHash = password; + this.role = Role.valueOf(role); + this.settings = null; + this.inbox = null; + } + // Add Getters public UUID getId() { return id; } - public String getName() { - return name; + public String getDisplayName() { + return displayName; + } + + public String getUsername() { + return username; } public String getEmail() { return email; } + public String getPasswordHash() { + return passwordHash; + } + public Role getRole() { return role; } @@ -103,11 +157,11 @@ public Inbox getInbox() { * @param name the new name * @throws IllegalArgumentException if the name is null or blank */ - public void setName(String name) { + public void setUsername(String name) { if (name == null || name.isBlank()) { throw new IllegalArgumentException("Name cannot be null or blank."); } - this.name = name; + this.username = name; } /** @@ -146,4 +200,18 @@ public void setEmail(String email) { public boolean checkPassword(String password) { return passwordHasher.isValidPassword(password, passwordHash); } + + public void setSettings(Settings settings) { + if (settings == null) { + throw new IllegalArgumentException("Settings cannot be null"); + } + this.settings = settings; + } + + public void setInbox(Inbox inbox) { + if (inbox == null) { + throw new IllegalArgumentException("Inbox cannot be null"); + } + this.inbox = inbox; + } } diff --git a/helpmehelpapplication/src/main/java/ntnu/systemutvikling/team6/scraper/FullCharityScrape.java b/helpmehelpapplication/src/main/java/ntnu/systemutvikling/team6/scraper/FullCharityScrape.java new file mode 100644 index 00000000..a69e3a9b --- /dev/null +++ b/helpmehelpapplication/src/main/java/ntnu/systemutvikling/team6/scraper/FullCharityScrape.java @@ -0,0 +1,106 @@ +package ntnu.systemutvikling.team6.scraper; + +import java.io.IOException; +import java.net.URISyntaxException; +import java.net.http.HttpClient; +import ntnu.systemutvikling.team6.models.Charity; +import ntnu.systemutvikling.team6.models.registry.CharityRegistry; +import ntnu.systemutvikling.team6.scraper.scraperComponents.APICharityScraper; +import ntnu.systemutvikling.team6.scraper.scraperComponents.URLCharityScraper; + +/** + * Orchestrates a full charity data scrape by combining two data sources: + * + *

    + *
  1. The external charity API (via {@link APICharityScraper}), which provides structured data + * such as organisation numbers, approval status, and charity URLs. + *
  2. Individual charity web pages (via {@link URLCharityScraper}), which provide richer + * presentation data such as descriptions, logos, categories, and key values. + *
+ * + *

This class acts as a facade — callers only need to invoke {@link #getAPIAndURLCharityData()} + * to receive a fully populated {@link CharityRegistry}. + */ +public class FullCharityScrape { + private final APICharityScraper apiScraper; + private final LogoDownloader logoDownloader; + + /** + * Constructs a {@code FullCharityScrape} instance and initialises the {@link APICharityScraper} + * with a new {@link HttpClient}. + * + * @throws URISyntaxException if the API endpoint URI used by {@link APICharityScraper} is + * malformed + */ + public FullCharityScrape() throws URISyntaxException { + HttpClient https = HttpClient.newHttpClient(); + this.apiScraper = new APICharityScraper(https); + this.logoDownloader = new LogoDownloader(); + } + + /** + * Performs a full two-phase scrape and returns a {@link CharityRegistry} populated with all + * available charity data. + * + *

Phase 1 — API scrape: Calls {@link APICharityScraper#checkConnection()} to verify + * availability, then fetches and parses the JSON payload into a {@link CharityRegistry}. + * + *

Phase 2 — URL scrape: Iterates over every {@link Charity} in the registry and uses a + * {@link URLCharityScraper} to enrich each entry with its description, logo URL, logo blob, + * categories, and key values scraped from the charity's own web page. + * + *

If {@link APICharityScraper#checkConnection()} throws an exception, it propagates to the + * caller and {@code null} is returned. If the connection check passes but returns {@code false}, + * {@code null} is also returned. + * + * @return a fully populated {@link CharityRegistry}, or {@code null} if the API is unreachable + * @throws IOException if an I/O error occurs during the API request or URL scraping + * @throws InterruptedException if the HTTP request thread is interrupted + */ + public CharityRegistry getAPIAndURLCharityData() throws IOException, InterruptedException { + try { + if (!apiScraper.checkConnection()) { + throw new RuntimeException("Connection check returned false"); + } + } catch (Exception e) { + e.printStackTrace(); + } + + CharityRegistry charityRegistry = apiScraper.parseJSON(apiScraper.getJSONData()); + int charityCounter = 0; + + for (Charity charity : charityRegistry.getAllCharities()) { + System.out.println(charity.getName()); + } + // Scrapes description, logo, categories, and key values from IK + for (Charity charity : charityRegistry.getAllCharities()) { + charityCounter++; + + System.out.println( + "Scraping charity vanity details: " + + charityCounter + + " of " + + charityRegistry.getAllCharities().size()); + try { + URLCharityScraper urlScraper = new URLCharityScraper(charity.getURL()); + urlScraper.scrapeCharityPage(); + + charity.setDescription(urlScraper.getDescription()); + charity.setCategory(urlScraper.getCategories()); + charity.setLogoURL(urlScraper.getLogoURL()); + charity.setKeyValues(urlScraper.getKeyValues()); + byte[] logoBlob = LogoDownloader.downloadImageAsBlob(charity.getLogoURL()); + charity.setLogoBlob(logoBlob); + } catch (Exception e) { + throw new RuntimeException( + "Failed to Scrape for: [" + + charityCounter + + "]: " + + charity.getName() + + ": " + + e.getMessage()); + } + } + return charityRegistry; + } +} diff --git a/helpmehelpapplication/src/main/java/ntnu/systemutvikling/team6/scraper/LogoDownloader.java b/helpmehelpapplication/src/main/java/ntnu/systemutvikling/team6/scraper/LogoDownloader.java new file mode 100644 index 00000000..2a871ffb --- /dev/null +++ b/helpmehelpapplication/src/main/java/ntnu/systemutvikling/team6/scraper/LogoDownloader.java @@ -0,0 +1,54 @@ +package ntnu.systemutvikling.team6.scraper; + +import java.io.InputStream; +import java.net.URL; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; + +/** + * Facilitates downloading of .png images from the individual charity's page on IK, converting them + * to bytecode (blob), and then back to a .png. + */ +public class LogoDownloader { + + /** + * Downloads a image from the given URL and converts it to a blob. + * + * @param imageUrl the URL of the image + * @return a blob containing the image data + */ + public static byte[] downloadImageAsBlob(String imageUrl) { + if (imageUrl == null || imageUrl.isBlank()) return null; + + try (InputStream in = new URL(imageUrl).openStream()) { + return in.readAllBytes(); + } catch (Exception e) { + System.out.println("Error: Something went wrong when downloading the image."); + return null; + } + } + + /** + * Converts a blob of image data back to a .png image file. + * + * @param imageBytes the blob containing the image data + * @param fileName the filename of the .png image file + */ + public static void convertBlobToPNG(byte[] imageBytes, String fileName) { + if (imageBytes == null) { + return; + } + try { + Path folder = Paths.get("target", "logo"); + Files.createDirectories(folder); + + Path filePath = folder.resolve(fileName + ".png"); + + Files.write(filePath, imageBytes); + + } catch (Exception e) { + System.out.println("Error: Something went wrong when converting blob to png."); + } + } +} diff --git a/helpmehelpapplication/src/main/java/ntnu/systemutvikling/team6/scraper/APICharityData.java b/helpmehelpapplication/src/main/java/ntnu/systemutvikling/team6/scraper/scraperComponents/APICharityData.java similarity index 94% rename from helpmehelpapplication/src/main/java/ntnu/systemutvikling/team6/scraper/APICharityData.java rename to helpmehelpapplication/src/main/java/ntnu/systemutvikling/team6/scraper/scraperComponents/APICharityData.java index 10a489d7..35b10d5d 100644 --- a/helpmehelpapplication/src/main/java/ntnu/systemutvikling/team6/scraper/APICharityData.java +++ b/helpmehelpapplication/src/main/java/ntnu/systemutvikling/team6/scraper/scraperComponents/APICharityData.java @@ -1,6 +1,6 @@ -package ntnu.systemutvikling.team6.scraper; +package ntnu.systemutvikling.team6.scraper.scraperComponents; -import ntnu.systemutvikling.team6.database.DatabaseManager; +import ntnu.systemutvikling.team6.database.DatabaseSetup; /** * Represents data parsed from the IK API JSON response. Instances are immutable; to update any @@ -9,7 +9,7 @@ *

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

{@code org_number} should be a unique number, as it is used as a primary key in {@link - * DatabaseManager}. + * DatabaseSetup}. */ public class APICharityData { private final String org_number; diff --git a/helpmehelpapplication/src/main/java/ntnu/systemutvikling/team6/scraper/APICharityScraper.java b/helpmehelpapplication/src/main/java/ntnu/systemutvikling/team6/scraper/scraperComponents/APICharityScraper.java similarity index 93% rename from helpmehelpapplication/src/main/java/ntnu/systemutvikling/team6/scraper/APICharityScraper.java rename to helpmehelpapplication/src/main/java/ntnu/systemutvikling/team6/scraper/scraperComponents/APICharityScraper.java index 89422a1a..16be61f7 100644 --- a/helpmehelpapplication/src/main/java/ntnu/systemutvikling/team6/scraper/APICharityScraper.java +++ b/helpmehelpapplication/src/main/java/ntnu/systemutvikling/team6/scraper/scraperComponents/APICharityScraper.java @@ -1,4 +1,4 @@ -package ntnu.systemutvikling.team6.scraper; +package ntnu.systemutvikling.team6.scraper.scraperComponents; import com.google.gson.Gson; import java.io.IOException; @@ -7,7 +7,7 @@ import java.net.http.HttpRequest; import java.net.http.HttpResponse; import ntnu.systemutvikling.team6.models.Charity; -import ntnu.systemutvikling.team6.models.CharityRegistry; +import ntnu.systemutvikling.team6.models.registry.CharityRegistry; /** * Fetches JSON information from the IK API and parses the JSON into a list of {@link @@ -81,6 +81,9 @@ public CharityRegistry parseJSON(String JSONData) { CharityRegistry charityRegistry = new CharityRegistry(); for (APICharityData apiCharityData : charityData) { + if (apiCharityData.getStatus().equalsIgnoreCase("obs")) { + continue; + } Charity charity = new Charity( apiCharityData.getOrg_number(), diff --git a/helpmehelpapplication/src/main/java/ntnu/systemutvikling/team6/scraper/scraperComponents/URLCharityScraper.java b/helpmehelpapplication/src/main/java/ntnu/systemutvikling/team6/scraper/scraperComponents/URLCharityScraper.java new file mode 100644 index 00000000..f9e98b37 --- /dev/null +++ b/helpmehelpapplication/src/main/java/ntnu/systemutvikling/team6/scraper/scraperComponents/URLCharityScraper.java @@ -0,0 +1,290 @@ +package ntnu.systemutvikling.team6.scraper.scraperComponents; + +import java.time.Duration; +import java.util.ArrayList; +import java.util.List; +import org.openqa.selenium.By; +import org.openqa.selenium.WebDriver; +import org.openqa.selenium.WebElement; +import org.openqa.selenium.chrome.ChromeDriver; +import org.openqa.selenium.chrome.ChromeOptions; +import org.openqa.selenium.support.ui.ExpectedConditions; +import org.openqa.selenium.support.ui.WebDriverWait; + +/** + * Class for scraping the description, URL of the logo, string of categories, and key values of the + * charities registered in IK. + */ +public class URLCharityScraper { + private final String url; + private final WebDriver driver; + private String description; + private String logoURL; + private final List categories; + private final List keyValues; + + /** + * Constructor used for production code. + * + *

It initializes the lists used for categories and keyValues, as well as defining the + * parameters used for the selenium Chromium-based browser that does the scraping. + * + * @param url the URL for the charity's webpage on IK + */ + public URLCharityScraper(String url) { + this.categories = new ArrayList<>(); + this.keyValues = new ArrayList<>(); + + ChromeOptions options = new ChromeOptions(); + options.addArguments("--headless=new"); + options.addArguments("--window-size=1920,1080"); + options.addArguments("--disable-gpu"); + options.addArguments("--no-sandbox"); + options.addArguments("--disable-dev-shm-usage"); + + this.url = url; + this.driver = new ChromeDriver(options); + } + + /** + * Constructor used for testing. + * + *

It accepts both a url (should ideally be a dud) and a {@link WebDriver} as parameters. The + * WebDriver is passed to make testing easier. + * + * @param url the URL for the charity's webpage on IK (for this constructor it should not be a + * real URL) + * @param driver the {@code WebDriver} object used for scraping + */ + public URLCharityScraper(String url, WebDriver driver) { + this.categories = new ArrayList<>(); + this.keyValues = new ArrayList<>(); + this.url = url; + this.driver = driver; + } + + /** + * Creates a {@link WebDriverWait} object for halting scraping until the correct pre-conditions + * are met. + * + * @return the {@code WebDriverWait} object to be used in the methods + */ + protected WebDriverWait createWait() { + return new WebDriverWait(driver, Duration.ofSeconds(30)); + } + + /** + * Calls the {@code findElements} method from the {@code WebDriver} object and returns a list of + * the returned {@link WebElement} objects. + * + * @param by a selector for {@code WebElement} objects + * @return a list of found {@code WebElement} objects matching the given selector + */ + public List findElements(By by) { + return driver.findElements(by); + } + + /** + * Calls the {@code findElement} method from the {@code WebDriver} object and returns a list of + * the returned {@code WebElement} objects. + * + * @param by a selector for {@code WebElement} objects + * @return a list of found {@code WebElement} objects matching the given selector + */ + protected WebElement findElement(By by) { + return driver.findElement(by); + } + + /** Quits the driver instance, making it unusable. */ + protected void closeDriver() { + driver.quit(); + } + + /** Scrapes the URL for the paragraphs containing the description of the charity. */ + protected void updateDescription() { + try { + WebDriverWait wait = createWait(); + StringBuilder descriptionString = new StringBuilder(); + + List readMoreLinks = findElements(By.cssSelector("a.read-more")); + + if (!readMoreLinks.isEmpty()) { + WebElement readMore = findElement(By.cssSelector("a.read-more")); + readMore.click(); + + wait.until(ExpectedConditions.visibilityOfElementLocated(By.cssSelector(".extra-info"))); + } + + wait.until( + ExpectedConditions.numberOfElementsToBeMoreThan(By.cssSelector(".information"), 0)); + + Thread.sleep(5000); + List firstDescription = findElements(By.cssSelector(".information")); + + for (WebElement element : firstDescription) { + if (!element.getText().isBlank()) { + descriptionString.append(element.getText()).append("\n\n"); + } + } + + this.description = descriptionString.toString(); + + } catch (Exception e) { + System.out.println("No description found for " + driver.getCurrentUrl()); + } + } + + /** Scrapes the URL for the image URL of the logo for the charity. */ + void updateLogo() { + try { + WebDriverWait wait = createWait(); + wait.until(ExpectedConditions.visibilityOfElementLocated(By.cssSelector(".logo > img"))); + Thread.sleep(5000); + + WebElement logo = findElement(By.cssSelector(".logo > img")); + this.logoURL = logo.getAttribute("src"); + + } catch (Exception e) { + System.out.println("No logo found for " + driver.getCurrentUrl()); + } + } + + /** Scrapes the URL for the category labels containing the categories for the charity. */ + void updateCategories() { + try { + WebDriverWait wait = createWait(); + + wait.until(ExpectedConditions.presenceOfElementLocated(By.cssSelector(".tag-label"))); + Thread.sleep(5000); + + List elements = findElements(By.cssSelector(".tag-label")); + + for (WebElement element : elements) { + this.categories.add(element.getText()); + } + + } catch (Exception e) { + System.out.println("No categories found for " + driver.getCurrentUrl()); + } + } + + /** + * Scrapes the URL for the statistics of the charity; the percentage collected, the percentage + * that goes to the administration, and the percentage that is put towards the cause. + */ + void updateKeyValues() { + try { + WebDriverWait wait = createWait(); + + String percentage; + WebElement element; + + wait.until( + ExpectedConditions.visibilityOfElementLocated( + By.xpath( + "//li[.//h2[normalize-space()='Innsamlingsprosent']]//div[@class='graph']"))); + Thread.sleep(5000); + element = + findElement( + By.xpath("//li[.//h2[normalize-space()='Innsamlingsprosent']]//div[@class='graph']")); + percentage = element.getAttribute("data-percentage"); + this.keyValues.add(percentage); + + wait.until( + ExpectedConditions.visibilityOfElementLocated( + By.xpath( + "//li[.//h2[normalize-space()='Administrasjonsprosent']]//div[@class='graph']"))); + + element = + findElement( + By.xpath( + "//li[.//h2[normalize-space()='Administrasjonsprosent']]//div[@class='graph']")); + percentage = element.getAttribute("data-percentage"); + this.keyValues.add(percentage); + + wait.until( + ExpectedConditions.visibilityOfElementLocated( + By.xpath("//li[.//h2[normalize-space()='Formålsprosent']]//div[@class='graph']"))); + + element = + findElement( + By.xpath("//li[.//h2[normalize-space()='Formålsprosent']]//div[@class='graph']")); + percentage = element.getAttribute("data-percentage"); + this.keyValues.add(percentage); + } catch (Exception e) { + System.out.println("No key values found for " + driver.getCurrentUrl()); + } + } + + /** Runs all the scraper methods at once, updating the object parameters. */ + public void scrapeCharityPage() { + try { + driver.get(this.url); + updateDescription(); + updateLogo(); + updateCategories(); + updateKeyValues(); + Thread.sleep(1000); + + } catch (InterruptedException e) { + throw new RuntimeException(e); + } finally { + closeDriver(); + } + } + + /** + * Returns the description of the charity. + * + * @return a String containing the description of the charity. + */ + public String getDescription() { + return description; + } + + /** + * Returns the URL of the logo for the charity. + * + * @return a String containing the URL for the logo of the charity. + */ + public String getLogoURL() { + return logoURL; + } + + /** + * Returns a String of the categories for the charity with ',' as a delimiter. + * + * @return a String of strings containing the categories for the charity + */ + public List getCategories() { + /* + StringBuilder categoriesString = new StringBuilder(); + + for (int i = 0; i < this.categories.size(); i++) { + categoriesString.append(this.categories.get(i)); + if (i < this.categories.size() - 1) { + categoriesString.append(","); + } + } + */ + return categories; + } + + /** + * Returns a String of the key value percentages for the charity with ':' as a delimiter, verified + * by IK. + * + * @return a String of the key values for the charity- + */ + public String getKeyValues() { + StringBuilder keyValuesString = new StringBuilder(); + + for (int i = 0; i < this.keyValues.size(); i++) { + keyValuesString.append(this.keyValues.get(i)); + if (i < this.keyValues.size() - 1) { + keyValuesString.append(":"); + } + } + return keyValuesString.toString(); + } +} 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 00000000..3edfce7d --- /dev/null +++ b/helpmehelpapplication/src/main/java/ntnu/systemutvikling/team6/service/APIToDatabaseService.java @@ -0,0 +1,167 @@ +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 APIToDatabaseService. 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 and the charity's URL. 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 temp table to ensure that the data in + * the database is consistent with the data from the API. + * + *

Uses a URLScraper object to get data not contained in the API, and static methods from + * LogoDownloader to get the charity's logo as a blob. + * + * @param charities a list of {@code Charity} objects to add to the database + */ + public void addAPIDataToTable(List charities) { + Connection conn = null; + // Everything charity related except categories + try { + conn = connection.getMySqlConnection(); + conn.setAutoCommit(false); + String sql1 = """ + INSERT INTO Charities (UUID_charities, org_number, pre_approved, status) + VALUES (?, ?, ?, ?) + ON DUPLICATE KEY UPDATE + pre_approved = VALUES(pre_approved), + status = VALUES(status); + """; + + String sql2 = """ + INSERT INTO CharityVanity (UUID_charity, charity_name, charity_link, description, logoURL, key_values, logoBlob) + VALUES (?, ?, ?, ?, ?, ?, ?) + ON DUPLICATE KEY UPDATE + charity_name = VALUES(charity_name), + charity_link = VALUES(charity_link), + description = VALUES(description), + logoURL = VALUES(logoURL), + key_values = VALUES(key_values), + logoBlob = VALUES(logoBlob); + """; + + try (PreparedStatement ps1 = conn.prepareStatement(sql1); + PreparedStatement ps2 = conn.prepareStatement(sql2)) { + + for (Charity charity : charities) { + String uuid = charity.getUUID() == null + ? UUID.randomUUID().toString() + : charity.getUUID().toString(); + + ps1.setString(1, uuid); + ps1.setString(2, charity.getOrg_number().replaceAll("\\s", "")); + ps1.setBoolean(3, charity.getPreApproved()); + ps1.setString(4, charity.getStatus()); + ps1.addBatch(); + + ps2.setString(1, uuid); + ps2.setString(2, charity.getName()); + ps2.setString(3, charity.getURL()); + ps2.setString(4, charity.getDescription()); + ps2.setString(5, charity.getLogoURL()); + ps2.setString(6, charity.getKeyValues()); + ps2.setBytes(7, charity.getLogoBlob()); + ps2.addBatch(); + } + + ps1.executeBatch(); + ps2.executeBatch(); + } catch (Exception e) { + e.printStackTrace(); + throw new RuntimeException(e); + } + + // -- Intergerty Check: + String createTemp = + """ + CREATE TEMPORARY TABLE temp_api_charities ( + org_number VARCHAR(255) PRIMARY KEY + ) + """; + + try (PreparedStatement ps = conn.prepareStatement(createTemp)) { + ps.execute(); + } + + String insertTemp = "INSERT IGNORE INTO temp_api_charities (org_number) VALUES (?)"; + + try (PreparedStatement ps = conn.prepareStatement(insertTemp)) { + + for (Charity charity : charities) { + ps.setString(1, charity.getOrg_number().replaceAll("\\s", "")); + ps.addBatch(); + } + + ps.executeBatch(); + } + + String deleteSql = + """ + DELETE FROM Charities c + WHERE NOT EXISTS ( + SELECT 1 FROM temp_api_charities t + WHERE t.org_number = c.org_number + ) + AND NOT EXISTS ( + SELECT 1 FROM Donations d WHERE d.charity_id = c.UUID_charities + ) + AND NOT EXISTS ( + SELECT 1 FROM Feedback f WHERE f.charity_id = c.UUID_charities + ) + AND NOT EXISTS ( + SELECT 1 FROM CharityVanity cv WHERE cv.UUID_charity = c.UUID_charities + ) + AND NOT EXISTS ( + SELECT 1 FROM CharityUsers cu WHERE cu.Charities_UUID_charities = c.UUID_charities + ); + """; + + try (PreparedStatement ps = conn.prepareStatement(deleteSql)) { + ps.executeUpdate(); + } + + conn.commit(); + + } catch (SQLException e) { + if (conn != null) { + try { + conn.rollback(); + } catch (SQLException ex) { + ex.printStackTrace(); + } + } + e.printStackTrace(); + + throw new RuntimeException("ERROR: Something went wrong during updating charities table."); + } finally { + if (conn != null) { + try { + conn.setAutoCommit(true); + conn.close(); + } catch (SQLException e) { + e.printStackTrace(); + } + } + } + } +} diff --git a/helpmehelpapplication/src/main/java/ntnu/systemutvikling/team6/service/AuthenticationService.java b/helpmehelpapplication/src/main/java/ntnu/systemutvikling/team6/service/AuthenticationService.java index 8b137891..ee780582 100644 --- a/helpmehelpapplication/src/main/java/ntnu/systemutvikling/team6/service/AuthenticationService.java +++ b/helpmehelpapplication/src/main/java/ntnu/systemutvikling/team6/service/AuthenticationService.java @@ -1 +1,117 @@ +package ntnu.systemutvikling.team6.service; +import ntnu.systemutvikling.team6.database.DAO.UserDAO; +import ntnu.systemutvikling.team6.database.Readers.UserSelect; +import ntnu.systemutvikling.team6.models.user.Inbox; +import ntnu.systemutvikling.team6.models.user.Role; +import ntnu.systemutvikling.team6.models.user.Settings; +import ntnu.systemutvikling.team6.models.user.User; + + +/** + * Service class responsible for handling user authentication operations, + * including login, registration, and logout functionality. + *

+ * Maintains the state of the currently authenticated user throughout the session. + *

+ */ +public class AuthenticationService { + /** Handles read operations for user data from the database. */ + private final UserSelect userDataReader; + /** Handles write operations for user data to the database. */ + private final UserDAO userDataSender; + + /** The currently authenticated user, or {@code null} if no user is logged in. */ + private User currentUser; + + /** + * Constructs an {@code AuthenticationService} with the specified data access objects. + * + * @param userDataReader the data reader used to query user information from the database + * @param userDataSender the DAO used to persist new user registrations to the database + */ + public AuthenticationService(UserSelect userDataReader, UserDAO userDataSender) { + this.userDataReader = userDataReader; + this.userDataSender = userDataSender; + } + + /** + * Attempts to authenticate a user with the given credentials. + *

+ * If a matching user is found in the database, they are set as the current user + * and the method returns {@code true}. + *

+ * + * @param username the username of the user attempting to log in + * @param password the password of the user attempting to log in + * @return {@code true} if authentication was successful; {@code false} otherwise + */ + public boolean login(String username, String password){ + User user = userDataReader.getUserFromDBUsernameAndPassword(username, password); + + if (user != null){ + currentUser = user; + return true; + } + + return false; + } + + /** + * Registers a new user account with the provided details. + *

+ * The new user is assigned the {@link Role#NORMAL_USER} role and default + * {@link Settings} and {@link Inbox}. Registration will fail if the + * username is already taken or if the database operation is unsuccessful. + * On success, the new user is set as the current user. + *

+ * + * @param displayName the display name shown on the user's profile + * @param username the unique username for the new account + * @param email the email address associated with the new account + * @param password the password for the new account + * @return {@code true} if registration was successful; {@code false} if the + * username is already taken or the database operation failed + */ + public boolean register(String displayName, String username, String email, String password ){ + User newUser = new User(displayName, username, email, password, Role.NORMAL_USER, new Settings(), new Inbox()); + + if(userDataReader.isUsernameTaken(username)){ + return false; + } + + boolean success = userDataSender.registerUser(newUser); + + if (success){ + currentUser = newUser; + return true; + } + return false; + } + + /** + * Logs out the currently authenticated user by clearing the current user state. + */ + public void logout (){ + currentUser = null; + } + + + /** + * Returns the currently authenticated user. + * + * @return the current {@link User}, or {@code null} if no user is logged in + */ + public User getCurrentUser(){ + return currentUser; + } + + /** + * Checks whether a user is currently logged in. + * + * @return {@code true} if a user is authenticated; {@code false} otherwise + */ + public boolean isLoggedin(){ + return currentUser != null; + } +} diff --git a/helpmehelpapplication/src/main/main.iml b/helpmehelpapplication/src/main/main.iml new file mode 100644 index 00000000..908ad4f5 --- /dev/null +++ b/helpmehelpapplication/src/main/main.iml @@ -0,0 +1,11 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/helpmehelpapplication/src/main/resources/fxml/aboutPage.fxml b/helpmehelpapplication/src/main/resources/fxml/aboutPage.fxml index 10e12e81..26dfe910 100644 --- a/helpmehelpapplication/src/main/resources/fxml/aboutPage.fxml +++ b/helpmehelpapplication/src/main/resources/fxml/aboutPage.fxml @@ -1,10 +1,8 @@ - - @@ -20,442 +18,398 @@ - + - + - + - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + - - - - - + + + - + - - - - - - + + + + + + - - - + + - - - - - - + - - - - + + + + + - - - + + + - - + + - - - - - - - - - - - - + + - - - - - - - + + + + + + + + + + + + + + + + + + + + + + - + - - - - - - - - - - - - + - - - - - - - + + + + + + + + + + + + + + + + + + + + + + - + - - - - - - - - - - - - + - - - - - - - + + + + + + + + + + + + + + + + + + + + + + - + - - - - - - - - - - - - + - - - - - - - + + + + + + + + + + + + + + + + + + + + + + - + - - - - - - - - - - - - + - - - - - - - + + + + + + + + + + + + + + + + + + + + + + - + - - - - - - - - - - - - + - - - - - - - + + + + + + + + + + + + + + + + + + + + + + - + - - + - - - + + - + + + + + + - - - - - + + - - - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + - + + + + - \ No newline at end of file + diff --git a/helpmehelpapplication/src/main/resources/fxml/available_organizations.fxml b/helpmehelpapplication/src/main/resources/fxml/available_organizations.fxml new file mode 100644 index 00000000..373a4888 --- /dev/null +++ b/helpmehelpapplication/src/main/resources/fxml/available_organizations.fxml @@ -0,0 +1,1039 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
\ No newline at end of file diff --git a/helpmehelpapplication/src/main/resources/fxml/charityPage.fxml b/helpmehelpapplication/src/main/resources/fxml/charityPage.fxml index 041b51b0..ae37eb60 100644 --- a/helpmehelpapplication/src/main/resources/fxml/charityPage.fxml +++ b/helpmehelpapplication/src/main/resources/fxml/charityPage.fxml @@ -23,7 +23,7 @@ - + @@ -91,7 +91,7 @@ - + @@ -219,6 +219,7 @@ + diff --git a/helpmehelpapplication/src/main/resources/fxml/components/navbar.fxml b/helpmehelpapplication/src/main/resources/fxml/components/navbar.fxml new file mode 100644 index 00000000..f3cca80f --- /dev/null +++ b/helpmehelpapplication/src/main/resources/fxml/components/navbar.fxml @@ -0,0 +1,89 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/helpmehelpapplication/src/main/resources/fxml/organizationCard.fxml b/helpmehelpapplication/src/main/resources/fxml/components/organizationCard.fxml similarity index 97% rename from helpmehelpapplication/src/main/resources/fxml/organizationCard.fxml rename to helpmehelpapplication/src/main/resources/fxml/components/organizationCard.fxml index 47ddc788..94cf11ff 100644 --- a/helpmehelpapplication/src/main/resources/fxml/organizationCard.fxml +++ b/helpmehelpapplication/src/main/resources/fxml/components/organizationCard.fxml @@ -6,7 +6,7 @@ - + diff --git a/helpmehelpapplication/src/main/resources/fxml/creater_user_site.fxml b/helpmehelpapplication/src/main/resources/fxml/creater_user_site.fxml new file mode 100644 index 00000000..45e9ac78 --- /dev/null +++ b/helpmehelpapplication/src/main/resources/fxml/creater_user_site.fxml @@ -0,0 +1,393 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/helpmehelpapplication/src/main/resources/fxml/dashboard.fxml b/helpmehelpapplication/src/main/resources/fxml/dashboard.fxml new file mode 100644 index 00000000..b1b6bbec --- /dev/null +++ b/helpmehelpapplication/src/main/resources/fxml/dashboard.fxml @@ -0,0 +1,584 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/helpmehelpapplication/src/main/resources/fxml/profile_org_Inbox.fxml b/helpmehelpapplication/src/main/resources/fxml/profile_org_Inbox.fxml new file mode 100644 index 00000000..e369d53c --- /dev/null +++ b/helpmehelpapplication/src/main/resources/fxml/profile_org_Inbox.fxml @@ -0,0 +1,308 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +