diff --git a/src/main/java/edu/group5/app/model/organization/Organization.java b/src/main/java/edu/group5/app/model/organization/Organization.java
index 42844e1..7016567 100644
--- a/src/main/java/edu/group5/app/model/organization/Organization.java
+++ b/src/main/java/edu/group5/app/model/organization/Organization.java
@@ -7,7 +7,8 @@
*
*
* An organization is identified by an organization number, a name,
- * trust status, website Url, pre-approval status, and a textual description.
+ * trust status, website Url, pre-approval status, and a textual description,
+ * and a logo URL.
*
*
* Instances are validated on creation:
@@ -15,6 +16,7 @@
*
orgNumber must be non-negative
* name and websiteUrl must not be null or blank
* description must not be null
+ * logoUrl may be null if no logo is available
*
*/
public record Organization(
@@ -23,7 +25,8 @@ public record Organization(
boolean trusted,
String websiteUrl,
boolean isPreApproved,
- String description) {
+ String description,
+ String logoUrl) {
/**
* Creates a new organization.
*
@@ -35,12 +38,13 @@ public record Organization(
* @param isPreApproved whether the organization is pre-approved
* @param description a textual description of the organization; must not be
* null
+ * @param logoUrl the URL to the organization's logo image; may be null
* @throws NullPointerException if name, websiteUrl or description is null
* @throws IllegalArgumentException if orgNumber is negative, or if name or
* websiteUrl is blank
*/
public Organization(int orgNumber, String name, boolean trusted, String websiteUrl, boolean isPreApproved,
- String description) {
+ String description, String logoUrl) {
if (orgNumber < 0) {
throw new IllegalArgumentException("orgNumber cannot be negative");
}
@@ -50,6 +54,7 @@ public Organization(int orgNumber, String name, boolean trusted, String websiteU
this.websiteUrl = Objects.requireNonNull(websiteUrl, "websiteUrl cannot be null");
this.isPreApproved = isPreApproved;
this.description = Objects.requireNonNull(description, "description cannot be null");
+ this.logoUrl = logoUrl;
if (name.isBlank()) {
throw new IllegalArgumentException("name cannot be blank");
diff --git a/src/main/java/edu/group5/app/model/organization/OrganizationRepository.java b/src/main/java/edu/group5/app/model/organization/OrganizationRepository.java
index a47b3d5..cc0a6b1 100644
--- a/src/main/java/edu/group5/app/model/organization/OrganizationRepository.java
+++ b/src/main/java/edu/group5/app/model/organization/OrganizationRepository.java
@@ -43,7 +43,7 @@ public OrganizationRepository(Object[] input) {
String websiteURL = (String) contentMap.get("url");
boolean isPreApproved = Boolean.TRUE.equals(contentMap.get("is_pre_approved"));
String description = "Information about " + name;
- Organization org = new Organization(orgNumber, name, trusted, websiteURL, isPreApproved, description);
+ Organization org = new Organization(orgNumber, name, trusted, websiteURL, isPreApproved, description, null);
grandMap.put(org.orgNumber(), org);
}
diff --git a/src/main/java/edu/group5/app/model/organization/OrganizationService.java b/src/main/java/edu/group5/app/model/organization/OrganizationService.java
index c5979f5..9785040 100644
--- a/src/main/java/edu/group5/app/model/organization/OrganizationService.java
+++ b/src/main/java/edu/group5/app/model/organization/OrganizationService.java
@@ -1,15 +1,28 @@
package edu.group5.app.model.organization;
+import org.jsoup.Jsoup;
+import org.jsoup.nodes.Document;
+import org.jsoup.nodes.Element;
+
+import java.util.HashMap;
import java.util.Map;
+import java.util.concurrent.CompletableFuture;
/**
* Service class for managing organization-related operations.
* It interacts with the OrganizationRepository to retrieve organization information
* and contains business logic associated with organization management.
+ *
+ * It provides fetching logo URLs by web scraping each organization's page on
+ * Innsamlingskontrollen.
+ *
+ * Fetched logo URLs are cached to avoid redundant network requests.
*/
public class OrganizationService {
private OrganizationRepository organizationRepository;
+ private final Map logoCache = new HashMap<>();
+
/**
* Constructs an OrganizationService with the given OrganizationRepository.
* @param organizationRepository the OrganizationRepository to use for managing organization data; must not be null
@@ -55,4 +68,82 @@ public Organization findByOrgNumber(int orgNumber) {
public Organization findByOrgName(String name) {
return organizationRepository.findByOrgName(name);
}
+
+ /**
+ * Fetches the logo URL for the given page by scraping the {@code div.logo img}
+ * element. Results are cached so each URL is only fetched once.
+ *
+ *
+ * Using Jsoup to web scrape through the URLs in the API.
+ *
+ * @param pageUrl the URL for the organization's page; may be null or blank
+ * @return the absolute logo URL, or null if not found or pageUrl is invalid
+ */
+
+ public String fetchLogoUrl(String pageUrl) {
+ if (pageUrl == null || pageUrl.isBlank()) {
+ return null;
+ }
+
+ if (logoCache.containsKey(pageUrl)) {
+ return logoCache.get(pageUrl);
+ }
+
+ try {
+ Document doc = Jsoup.connect(pageUrl).get();
+ Element img = doc.selectFirst("div.logo img");
+
+ if (img != null) {
+ String logoUrl = img.absUrl("src");
+ logoCache.put(pageUrl, logoUrl);
+ return logoUrl;
+ }
+ } catch (Exception e) {
+ System.out.println("Could not get logo for: " + pageUrl);
+ }
+ return null;
+ }
+
+ /**
+ * Fetches all trusted organizations with their logo URLs.
+ *
+ *
+ * For each trusted organization, attempts to get its logo using
+ * {@link #fetchLogoUrl(String)}. Creates a new Organization
+ * object including the logo URL.
+ *
+ * @return a map of trusted organizations keyed by organization number, with logos included
+ */
+ public Map getTrustedOrganizationsWithLogos() {
+ Map original = getTrustedOrganizations();
+ Map trustedOrgsWithLogos = new HashMap<>();
+
+ for (Organization org : original.values()) {
+ String logoUrl = fetchLogoUrl(org.websiteUrl());
+
+ Organization newOrg = new Organization(
+ org.orgNumber(),
+ org.name(),
+ org.trusted(),
+ org.websiteUrl(),
+ org.isPreApproved(),
+ org.description(),
+ logoUrl
+ );
+ trustedOrgsWithLogos.put(newOrg.orgNumber(), newOrg);
+ }
+ return trustedOrgsWithLogos;
+ }
+
+ /**
+ * Asynchronously fetches trusted organizations with logos.
+ *
+ * Runs in the background so the UI thread is no blocked.
+ * Returns a CompletableFuture that completes when all logos are loaded.
+ *
+ * @return a CompletableFuture containing a map of organizations with logos
+ */
+ public CompletableFuture