diff --git a/helpmehelpapplication/src/main/java/ntnu/systemutvikling/team6/HmHApplication.java b/helpmehelpapplication/src/main/java/ntnu/systemutvikling/team6/HmHApplication.java index a4818f3..a78be83 100644 --- a/helpmehelpapplication/src/main/java/ntnu/systemutvikling/team6/HmHApplication.java +++ b/helpmehelpapplication/src/main/java/ntnu/systemutvikling/team6/HmHApplication.java @@ -9,7 +9,7 @@ import ntnu.systemutvikling.team6.database.DatabaseSetup; import ntnu.systemutvikling.team6.models.Charity; import ntnu.systemutvikling.team6.models.registry.CharityRegistry; -import ntnu.systemutvikling.team6.scraper.APICharityScraper; +import ntnu.systemutvikling.team6.scraper.scraperComponents.APICharityScraper; import ntnu.systemutvikling.team6.service.APIToDatabaseService; public class HmHApplication extends Application { 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 4f6d3ae..0000000 --- a/helpmehelpapplication/src/main/java/ntnu/systemutvikling/team6/database/DatabaseManager.java +++ /dev/null @@ -1,384 +0,0 @@ -package ntnu.systemutvikling.team6.database; - -import java.sql.*; -import java.util.*; - -import ntnu.systemutvikling.team6.models.Charity; -import ntnu.systemutvikling.team6.models.registry.CharityRegistry; -import ntnu.systemutvikling.team6.models.Donation; -import ntnu.systemutvikling.team6.models.registry.DonationRegistry; -import ntnu.systemutvikling.team6.scraper.APICharityData; -import ntnu.systemutvikling.team6.scraper.LogoDownloader; -import ntnu.systemutvikling.team6.scraper.URLCharityScraper; - -/** - * 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, - description TEXT, - logoURL TEXT, - categories TEXT, - key_values TEXT, - logoBlob MEDIUMBLOB, - 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 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; - int charityCounter = 0; - - // Scrapes description, logo, categories, and key values from IK - for (Charity charity : charities) { - charityCounter++; - - System.out.println("Scraping charity " + charityCounter + " of " + charities.size()); - 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); - } - try { - conn = connection.getMySqlConnection(); - conn.setAutoCommit(false); - String sql_query = - """ - INSERT INTO Charities (UUID_charities, org_number, charity_name, charity_link, pre_approved, status, description, logoURL, categories, key_values, logoBlob) - VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) - ON DUPLICATE KEY UPDATE - charity_name = VALUES(charity_name), - charity_link = VALUES(charity_link), - pre_approved = VALUES(pre_approved), - status = VALUES(status), - description = VALUES(description), - logoURL = VALUES(logoURL), - categories = VALUES(categories), - key_values = VALUES(key_values), - logoBlob = VALUES(logoBlob) - """; - - 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.getURL()); - ps.setBoolean(5, charity.getPreApproved()); - ps.setString(6, charity.getStatus()); - ps.setString(7, charity.getDescription()); - ps.setString(8, charity.getLogoURL()); - ps.setString(9, charity.getCategory()); - ps.setString(10, charity.getKeyValues()); - ps.setBytes(11, charity.getLogoBlob()); - - 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(); - } - } - } - } - - /** - * Fetches the data stored in the database and converts it to a list of Charity objects - * in the form of a registry (CharityRegistry). - * - * @return a CharityRegistry of all the charities registered in the database - */ - 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, description, logoURL, " + - "categories, key_values, logoBlob 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")); - charity.setDescription(rs.getString("description")); - charity.setLogoURL(rs.getString("logoURL")); - charity.setCategory(rs.getString("categories")); - charity.setKeyValues(rs.getString("key_values")); - charity.setLogoBlob(rs.getBytes("logoBlob")); - - registry.addCharity(charity); - } - } catch (SQLException e) { - e.printStackTrace(); - throw new RuntimeException("ERROR: Something went wrong during updating charities table."); - } - return registry; - } - - public List getCategoriesFromDB() { - Map categoryMap = new HashMap<>(); - - String sql_query = "SELECT categories FROM Charities"; - - try (Connection conn = connection.getMySqlConnection(); - Statement stmt = conn.createStatement(); - ResultSet rs = stmt.executeQuery(sql_query)) { - - while (rs.next()) { - String categoriesStr = rs.getString("categories"); - - if (categoriesStr != null && !categoriesStr.isEmpty()) { - String[] splitCategories = categoriesStr.split(","); - - for (String category : splitCategories) { - String trimmed = category.trim(); - - if (!trimmed.isEmpty()) { - categoryMap.putIfAbsent(trimmed.toLowerCase(), trimmed); - } - } - } - } - } catch (SQLException e) { - e.printStackTrace(); - throw new RuntimeException("ERROR: Something went wrong while fetching categories from the database."); - } - - var categories = new ArrayList<>(categoryMap.values()); - Collections.sort(categories); - - return categories; - } - - 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; - } -} \ No newline at end of file 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 index cdcee40..65c4f3e 100644 --- a/helpmehelpapplication/src/main/java/ntnu/systemutvikling/team6/database/Readers/CharitySelect.java +++ b/helpmehelpapplication/src/main/java/ntnu/systemutvikling/team6/database/Readers/CharitySelect.java @@ -3,6 +3,9 @@ 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.registry.CharityRegistry; @@ -34,11 +37,11 @@ public CharitySelect(DatabaseConnection connection) { * Retrieves all charities from the database, including their associated feedback and the users * who submitted each piece of feedback. * - *

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

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 are still included in the result due to the LEFT JOIN. + *

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) @@ -52,12 +55,17 @@ public CharityRegistry getCharitiesFromDB() { String sql_query = """ SELECT - c.UUID_charities, c.org_number, c.charity_name, c.charity_link, c.pre_approved, c.status, + 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_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 Cateegories 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); @@ -65,6 +73,8 @@ public CharityRegistry getCharitiesFromDB() { Charity currentCharity = null; String lastCharity = null; + Set seenFeedbackIds = new HashSet<>(); + registry = new CharityRegistry(); while (rs.next()) { String currentId = rs.getString("UUID_charities"); @@ -74,15 +84,28 @@ public CharityRegistry getCharitiesFromDB() { new Charity( rs.getString("UUID_charities"), rs.getString("org_number"), - rs.getString("charity_link"), rs.getString("charity_name"), + rs.getString("charity_link"), + rs.getString("status"), rs.getBoolean("pre_approved"), - rs.getString("status")); + rs.getString("description"), + rs.getString("logoURL"), + rs.getString("keyValues"), + 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) { + if (feedbackId != null && !seenFeedbackIds.contains(feedbackId)) { + seenFeedbackIds.add(feedbackId); User userWithNoSettingsAndInbox = new User( rs.getString("UUID_User"), @@ -101,6 +124,7 @@ public CharityRegistry getCharitiesFromDB() { currentCharity.getFeedbacks().add(feedback); } } + } catch (SQLException e) { e.printStackTrace(); throw new RuntimeException("ERROR: Something went wrong during updating charities table."); 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 a5768de..66b2f30 100644 --- a/helpmehelpapplication/src/main/java/ntnu/systemutvikling/team6/models/Charity.java +++ b/helpmehelpapplication/src/main/java/ntnu/systemutvikling/team6/models/Charity.java @@ -69,7 +69,7 @@ public Charity( /** * Contructor for creating a new charity. Taylored to match data given from DATABASE. - * Expects paramaters that will fill all attributes. EXECPT for feedbacks. + * Expects paramaters that will fill all attributes. EXECPT for feedbacks and categories. * * @param org_number matches from innsamlingkontrollen * @param name matches from innsamlingkontrollen @@ -82,7 +82,6 @@ public Charity(String uuid, String url, String status, boolean is_pre_approved, - List categories, String description, String logoURL, String keyValues, @@ -93,7 +92,7 @@ public Charity(String uuid, this.url = url; this.is_pre_approved = is_pre_approved; this.status = status; - this.category = categories; + this.category = new ArrayList<>(); this.description = description; this.logoURL = logoURL; this.keyValues = keyValues;