diff --git "a/docs/M\303\270tedokumenter/Internmeeting 2026.02.05.pdf" "b/docs/M\303\270tedokumenter/Internmeeting 2026.02.05.pdf" new file mode 100644 index 0000000..e921157 Binary files /dev/null and "b/docs/M\303\270tedokumenter/Internmeeting 2026.02.05.pdf" differ diff --git "a/docs/M\303\270tedokumenter/Internmeeting, 2026.03.19.pdf" "b/docs/M\303\270tedokumenter/Internmeeting, 2026.03.19.pdf" new file mode 100644 index 0000000..2f01609 Binary files /dev/null and "b/docs/M\303\270tedokumenter/Internmeeting, 2026.03.19.pdf" differ diff --git "a/docs/M\303\270tedokumenter/Retroperspektiv 1, 2026.02.12.pdf" "b/docs/M\303\270tedokumenter/Retroperspektiv 1, 2026.02.12.pdf" new file mode 100644 index 0000000..bcb1d3d Binary files /dev/null and "b/docs/M\303\270tedokumenter/Retroperspektiv 1, 2026.02.12.pdf" differ diff --git "a/docs/M\303\270tedokumenter/Retroperspektiv 2, 2026.03.19.pdf" "b/docs/M\303\270tedokumenter/Retroperspektiv 2, 2026.03.19.pdf" new file mode 100644 index 0000000..4a60910 Binary files /dev/null and "b/docs/M\303\270tedokumenter/Retroperspektiv 2, 2026.03.19.pdf" differ diff --git "a/docs/M\303\270tedokumenter/Team meeting 2, 2026.03.17.pdf" "b/docs/M\303\270tedokumenter/Team meeting 2, 2026.03.17.pdf" new file mode 100644 index 0000000..0990c3d Binary files /dev/null and "b/docs/M\303\270tedokumenter/Team meeting 2, 2026.03.17.pdf" differ diff --git "a/docs/M\303\270tedokumenter/Thursdays Meeting 2026.02.19 (With LA).pdf" "b/docs/M\303\270tedokumenter/Thursdays Meeting 2026.02.19 (With LA).pdf" new file mode 100644 index 0000000..8f5beca Binary files /dev/null and "b/docs/M\303\270tedokumenter/Thursdays Meeting 2026.02.19 (With LA).pdf" differ diff --git "a/docs/M\303\270tedokumenter/Thursdays Meeting 2026.03.12 (With LA).pdf" "b/docs/M\303\270tedokumenter/Thursdays Meeting 2026.03.12 (With LA).pdf" new file mode 100644 index 0000000..c6efc55 Binary files /dev/null and "b/docs/M\303\270tedokumenter/Thursdays Meeting 2026.03.12 (With LA).pdf" differ diff --git a/helpmehelpapplication/pom.xml b/helpmehelpapplication/pom.xml index 1794a9e..347518e 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 diff --git a/helpmehelpapplication/src/main/java/ntnu/systemutvikling/team6/database/DatabaseManager.java b/helpmehelpapplication/src/main/java/ntnu/systemutvikling/team6/database/DatabaseManager.java index e411642..6276bdf 100644 --- a/helpmehelpapplication/src/main/java/ntnu/systemutvikling/team6/database/DatabaseManager.java +++ b/helpmehelpapplication/src/main/java/ntnu/systemutvikling/team6/database/DatabaseManager.java @@ -1,13 +1,14 @@ package ntnu.systemutvikling.team6.database; import java.sql.*; -import java.util.List; -import java.util.UUID; +import java.util.*; + 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; +import ntnu.systemutvikling.team6.scraper.LogoDownloader; import ntnu.systemutvikling.team6.scraper.URLCharityScraper; /** @@ -79,6 +80,7 @@ status VARCHAR(255) NOT NULL, logoURL TEXT, categories TEXT, key_values TEXT, + logoBlob MEDIUMBLOB, UNIQUE KEY unique_org_number (org_number) ) ENGINE=InnoDB; @@ -117,23 +119,43 @@ REFERENCES Charities (`UUID_charities`) /** * 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 + * 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 a temp table to ensure that the data in the - * database is consistent with the data from the API. + * 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 + * @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) - VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?) + 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), @@ -142,7 +164,8 @@ INSERT INTO Charities (UUID_charities, org_number, charity_name, charity_link, p description = VALUES(description), logoURL = VALUES(logoURL), categories = VALUES(categories), - key_values = VALUES(key_values) + key_values = VALUES(key_values), + logoBlob = VALUES(logoBlob) """; try (PreparedStatement ps = conn.prepareStatement(sql_query)) { @@ -152,14 +175,6 @@ INSERT INTO Charities (UUID_charities, org_number, charity_name, charity_link, p } else { ps.setString(1, charity.getUUID().toString()); } - // Scrapes description, logo, categories, and key values from IK - URLCharityScraper urlScraper = new URLCharityScraper(charity.getURL()); - urlScraper.scrapeCharityPage(); - - charity.setDescription(urlScraper.getDescription()); - charity.setCategory(urlScraper.getCategories()); - charity.setLogoURL(urlScraper.getLogoURL()); - charity.setKeyValues(urlScraper.getKeyValues()); ps.setString(2, charity.getOrg_number().replaceAll("\\s", "")); ps.setString(3, charity.getName()); @@ -170,6 +185,7 @@ INSERT INTO Charities (UUID_charities, org_number, charity_name, charity_link, p ps.setString(8, charity.getLogoURL()); ps.setString(9, charity.getCategory()); ps.setString(10, charity.getKeyValues()); + ps.setBytes(11, charity.getLogoBlob()); ps.addBatch(); } @@ -239,13 +255,20 @@ WHERE NOT EXISTS ( } } + /** + * 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 FROM Charities"; + "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); @@ -259,6 +282,12 @@ public CharityRegistry getCharitiesFromDB() { 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) { @@ -268,6 +297,41 @@ public CharityRegistry getCharitiesFromDB() { 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; @@ -317,4 +381,4 @@ public DonationRegistry getDonationFromDB() { } return registry; } -} +} \ No newline at end of file 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 4643d87..b66c21f 100644 --- a/helpmehelpapplication/src/main/java/ntnu/systemutvikling/team6/models/Charity.java +++ b/helpmehelpapplication/src/main/java/ntnu/systemutvikling/team6/models/Charity.java @@ -43,6 +43,9 @@ public class Charity { /* List that contains the charity's Feedbacks */ private List feedbacks; + /* Bytecode for the charity logo */ + private byte[] logoBlob; + /** * Contructor for creating a new charity. Taylored to match data given from Api. Other attributes * will just be initialized as empty @@ -91,6 +94,7 @@ public Charity( this.logoURL = ""; this.keyValues = ""; this.feedbacks = new ArrayList<>(); + this.logoBlob = null; } /** Getters for the charity's attributes. */ @@ -138,6 +142,10 @@ public String getKeyValues() { return this.keyValues; } + public byte[] getLogoBlob() { + return this.logoBlob; + } + /** Setter for verification status. This one sets the charity as verified. */ public void setVerified() { this.status = "approved"; @@ -167,4 +175,9 @@ public void setLogoURL(String url) { public void setKeyValues(String values) { this.keyValues = values; } + + /** Setter for the charity's logo Blob. */ + public void setLogoBlob(byte[] logoBlob) { + this.logoBlob = logoBlob; + } } diff --git a/helpmehelpapplication/src/main/java/ntnu/systemutvikling/team6/scraper/APICharityScraper.java b/helpmehelpapplication/src/main/java/ntnu/systemutvikling/team6/scraper/APICharityScraper.java index 89422a1..ac920ac 100644 --- a/helpmehelpapplication/src/main/java/ntnu/systemutvikling/team6/scraper/APICharityScraper.java +++ b/helpmehelpapplication/src/main/java/ntnu/systemutvikling/team6/scraper/APICharityScraper.java @@ -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/LogoDownloader.java b/helpmehelpapplication/src/main/java/ntnu/systemutvikling/team6/scraper/LogoDownloader.java new file mode 100644 index 0000000..e190699 --- /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/URLCharityScraper.java b/helpmehelpapplication/src/main/java/ntnu/systemutvikling/team6/scraper/URLCharityScraper.java index ce563c9..88be3dd 100644 --- a/helpmehelpapplication/src/main/java/ntnu/systemutvikling/team6/scraper/URLCharityScraper.java +++ b/helpmehelpapplication/src/main/java/ntnu/systemutvikling/team6/scraper/URLCharityScraper.java @@ -12,267 +12,277 @@ 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. + * 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 - */ - protected 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() { - WebDriverWait wait = createWait(); - StringBuilder descriptionString = new StringBuilder(); - - wait.until(ExpectedConditions.numberOfElementsToBeMoreThan( - By.cssSelector(".information div"), 0)); - - List firstDescription = - findElements(By.cssSelector(".information div p")); - - for (WebElement element : firstDescription) { - if (!element.getText().isBlank()) { - descriptionString.append(element.getText()).append("\n\n"); - } - } - - 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"))); + 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 + */ + protected 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"); } + } - List extraDescription = - findElements(By.cssSelector(".extra-info p")); - - for (WebElement element : extraDescription) { - if (!element.getText().isBlank()) { - descriptionString.append(element.getText()).append("\n\n"); - } - } + this.description = descriptionString.toString(); - 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() { - WebDriverWait wait = createWait(); + /** 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); - wait.until(ExpectedConditions.visibilityOfElementLocated( - By.cssSelector(".logo > img"))); + WebElement logo = findElement(By.cssSelector(".logo > img")); + this.logoURL = logo.getAttribute("src"); - 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() { - WebDriverWait wait = createWait(); + /** Scrapes the URL for the category labels containing the categories for the charity. */ + void updateCategories() { + try { + WebDriverWait wait = createWait(); - wait.until(ExpectedConditions.visibilityOfElementLocated( - By.cssSelector(".tag-label"))); + wait.until(ExpectedConditions.presenceOfElementLocated(By.cssSelector(".tag-label"))); + Thread.sleep(5000); - List elements = - findElements(By.cssSelector(".tag-label")); + List elements = findElements(By.cssSelector(".tag-label")); - for (WebElement element : elements) { - this.categories.add(element.getText()); - } - } - - /** - * 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() { - WebDriverWait wait = createWait(); - - String percentage; - WebElement element; - - wait.until(ExpectedConditions.visibilityOfElementLocated( - By.xpath("//li[.//h2[normalize-space()='Innsamlingsprosent']]//div[@class='graph']"))); - - 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); - } - - /** - * Runs all the scraper methods at once, updating the object parameters. - */ - public void scrapeCharityPage() { - try { - driver.get(this.url); - - updateDescription(); - updateLogo(); - updateCategories(); - updateKeyValues(); + for (WebElement element : elements) { + this.categories.add(element.getText()); + } - } finally { - closeDriver(); - } + } catch (Exception e) { + System.out.println("No categories found for " + driver.getCurrentUrl()); } - - /** - * Returns the description of the charity. - * - * @return a String containing the description of the charity. - */ - public String getDescription() { - return description; + } + + /** + * 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()); } - - /** - * 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; + } + + /** 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 a String of the categories for the charity with ',' as a delimiter. - * - * @return a String of strings containing the categories for the charity - */ - public String 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 categoriesString.toString(); + } + + /** + * 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 String 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(","); + } } - - /** - * 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(); + return categoriesString.toString(); + } + + /** + * 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(":"); + } } -} \ No newline at end of file + return keyValuesString.toString(); + } +} diff --git a/helpmehelpapplication/src/test/java/ntnu/systemutvikling/team6/database/DatabaseManagerTest.java b/helpmehelpapplication/src/test/java/ntnu/systemutvikling/team6/database/DatabaseManagerTest.java index 78be78a..8b8ac6e 100644 --- a/helpmehelpapplication/src/test/java/ntnu/systemutvikling/team6/database/DatabaseManagerTest.java +++ b/helpmehelpapplication/src/test/java/ntnu/systemutvikling/team6/database/DatabaseManagerTest.java @@ -7,6 +7,7 @@ import java.util.List; import ntnu.systemutvikling.team6.models.Charity; import ntnu.systemutvikling.team6.models.CharityRegistry; +import ntnu.systemutvikling.team6.scraper.LogoDownloader; import org.junit.jupiter.api.*; class DatabaseManagerTest { @@ -19,7 +20,7 @@ public void setUp() throws SQLException { } @Test - public void test() { + public void blobImageTest() { dbManager.createTables(); String org_number = "12345"; @@ -31,8 +32,10 @@ public void test() { Charity charity = new Charity(org_number, url, name, is_pre_approved, status); dbManager.addAPIDataToTable(List.of(charity)); + LogoDownloader.convertBlobToPNG(charity.getLogoBlob(), charity.getUUID().toString()); } + // Make sure you're connected to the NTNU network for this to work @Test public void testConnectionShouldReturnTrue() { diff --git a/helpmehelpapplication/src/test/java/ntnu/systemutvikling/team6/scraper/LogoDownloaderTest.java b/helpmehelpapplication/src/test/java/ntnu/systemutvikling/team6/scraper/LogoDownloaderTest.java new file mode 100644 index 0000000..c933025 --- /dev/null +++ b/helpmehelpapplication/src/test/java/ntnu/systemutvikling/team6/scraper/LogoDownloaderTest.java @@ -0,0 +1,21 @@ +package ntnu.systemutvikling.team6.scraper; + +import static org.junit.jupiter.api.Assertions.*; + +import java.nio.file.Files; +import java.nio.file.Path; +import org.junit.jupiter.api.Test; + +class LogoDownloaderTest { + @Test + void testConvertBlobToPNG() throws Exception { + byte[] fakeImage = new byte[] {1, 2, 3, 4, 5}; + + LogoDownloader.convertBlobToPNG(fakeImage, "test-logo"); + + Path path = Path.of("target", "logo", "test-logo.png"); + + assertTrue(Files.exists(path)); + assertArrayEquals(fakeImage, Files.readAllBytes(path)); + } +}