diff --git a/helpmehelpapplication/App_startup_and_api_sync.puml b/helpmehelpapplication/App_startup_and_api_sync.puml new file mode 100644 index 00000000..fc1956dd --- /dev/null +++ b/helpmehelpapplication/App_startup_and_api_sync.puml @@ -0,0 +1,46 @@ +@startuml +!theme plain +skinparam backgroundColor white + +title Application startup and organisation data sync from API + +participant "HmHApplication" as App +participant "DatabaseSetup" as Setup +participant "DatabaseConnection" as DBConn +participant "APICharityScraper" as API +participant "APIToDatabaseService" as Sync +database "MySQL DB" as DB +collections "CharityRegistry" as Registry + +App -> Setup : testConnection() +Setup -> DBConn : getMySqlConnection() +DBConn --> Setup : connection +Setup --> App : OK + +App -> Setup : createTables() +Setup -> DB : CREATE TABLE IF NOT EXISTS ... +DB --> Setup : tables ready +Setup --> App : OK + +App -> API : checkConnection() +API --> App : true +App -> API : getJSONData() +API --> App : JSON payload +App -> API : parseJSON(json) +API --> App : CharityRegistry + +App -> Sync : addAPIDataToTable(Registry.getAllCharities()) +activate Sync +Sync -> DBConn : getMySqlConnection() +DBConn --> Sync : SQL connection +loop For each charity from API + Sync -> DB : INSERT/UPDATE Charities + Sync -> DB : INSERT/UPDATE CharityVanity +end +Sync -> DB : CREATE TEMP TABLE temp_api_charities +Sync -> DB : INSERT current org numbers into temp table +Sync -> DB : DELETE stale charities not referenced elsewhere +DB --> Sync : commit complete +Sync --> App : Sync finished +Deactivate Sync +@enduml diff --git a/helpmehelpapplication/Authentication_backend_flow.puml b/helpmehelpapplication/Authentication_backend_flow.puml new file mode 100644 index 00000000..6668108d --- /dev/null +++ b/helpmehelpapplication/Authentication_backend_flow.puml @@ -0,0 +1,77 @@ +@startuml +!theme plain +skinparam backgroundColor white + +title User registration, login and logout backend flow + +actor User +participant "UI / Controller" as UI +participant "AuthenticationService" as Auth +participant "UserSelect" as UserSelect +participant "UserDAO" as UserDAO +database "MySQL DB" as DB + +== Registration == +User -> UI : Submit displayName, username, email, password +UI -> Auth : register(displayName, username, email, password) +activate Auth +Auth -> UserSelect : isUsernameTaken(username) +activate UserSelect +UserSelect -> DB : SELECT UUID_User FROM User WHERE user_name = username +DB --> UserSelect : username exists / not found +UserSelect --> Auth : true / false +Deactivate UserSelect + +alt Username already taken + Auth --> UI : false + UI --> User : Show registration failed +else Username available + Auth -> UserDAO : registerUser(new User) + activate UserDAO + UserDAO -> DB : INSERT INTO User + UserDAO -> DB : INSERT INTO Settings + DB --> UserDAO : insert result + UserDAO --> Auth : success / failure + deactivate UserDAO + + alt Registration successful + Auth -> Auth : currentUser = newUser + Auth --> UI : true + UI --> User : Show registration successful + else Registration failed + Auth --> UI : false + UI --> User : Show registration failed + end +end +Deactivate Auth + +== Login == +User -> UI : Submit username and password +UI -> Auth : login(username, password) +activate Auth +Auth -> UserSelect : getUserFromDBUsernameAndPassword(username, password) +activate UserSelect +UserSelect -> DB : SELECT User + Settings + Messages +DB --> UserSelect : matching user rows +UserSelect --> Auth : User / null +Deactivate UserSelect + +alt Matching user found + Auth -> Auth : currentUser = user + Auth --> UI : true + UI --> User : Show logged-in state +else No match + Auth --> UI : false + UI --> User : Show login failed +end +Deactivate Auth + +== Logout == +User -> UI : Click logout +UI -> Auth : logout() +activate Auth +Auth -> Auth : currentUser = null +Auth --> UI : logged out +UI --> User : Return to logged-out state +Deactivate Auth +@enduml diff --git a/helpmehelpapplication/Class_diagram.puml b/helpmehelpapplication/Class_diagram.puml new file mode 100644 index 00000000..771ac72a --- /dev/null +++ b/helpmehelpapplication/Class_diagram.puml @@ -0,0 +1,127 @@ +@startuml +skinparam classAttributeIconSize 0 + +' ====================== +' CONTROLLERS +' ====================== +package "Controllers" { + class OrganisationController { + +getAll() + +getById(id) + +search(query) + } + + class DonationController { + +createDonation() + +confirmDonation() + } + + class AuthController { + +login() + +register() + +logout() + } +} + +' ====================== +' SERVICES +' ====================== +package "Services" { + class OrganisationService { + +getOrganisations() + +getOrganisationDetails() + +searchOrganisations() + } + + class DonationService { + +createDonationIntent() + +confirmDonation() + } + + class AuthService { + +authenticate() + +createUser() + } + + class ScraperService { + +fetchFromAPI() + +enrichData() + } +} + +' ====================== +' REPOSITORIES +' ====================== +package "Repositories" { + class OrganisationRepository { + +findAll() + +findById() + +search() + } + + class DonationRepository { + +save() + +findByUser() + } + + class UserRepository { + +findByEmail() + +save() + } +} + +' ====================== +' MODELS / ENTITIES +' ====================== +package "Models" { + class Organisation { + id + name + description + category + imageUrl + } + + class Donation { + id + amount + userId + organisationId + createdAt + } + + class User { + id + email + passwordHash + } +} + +' ====================== +' RELATIONSHIPS +' ====================== + +' Controller -> Service +OrganisationController --> OrganisationService +DonationController --> DonationService +AuthController --> AuthService + +' Service -> Repository +OrganisationService --> OrganisationRepository +DonationService --> DonationRepository +DonationService --> OrganisationRepository +AuthService --> UserRepository + +' Service -> External logic +OrganisationService --> ScraperService + +' Repository -> Model +OrganisationRepository --> Organisation +DonationRepository --> Donation +UserRepository --> User + +' Model relations +Donation --> User +Donation --> Organisation + +@enduml \ No newline at end of file diff --git a/helpmehelpapplication/Frontpage_load.puml b/helpmehelpapplication/Frontpage_load.puml new file mode 100644 index 00000000..0175096b --- /dev/null +++ b/helpmehelpapplication/Frontpage_load.puml @@ -0,0 +1,38 @@ +@startuml +!theme plain +skinparam backgroundColor white + +title User opens front page and the app loads featured organisations + +actor User +participant "FrontpageController" as Frontpage +participant "DatabaseConnection" as DBConn +participant "CharitySelect" as CharitySelect +participant "DonationSelect" as DonationSelect +database "MySQL DB" as DB + +User -> Frontpage : Open application / front page +activate Frontpage +Frontpage -> DBConn : create connection +DBConn --> Frontpage : connection helper + +Frontpage -> CharitySelect : getCharitiesFromDB() +activate CharitySelect +CharitySelect -> DB : SELECT charities + charity vanity + categories + feedback +DB --> CharitySelect : CharityRegistry +CharitySelect --> Frontpage : CharityRegistry +Deactivate CharitySelect + +Frontpage -> DonationSelect : getDonationFromDB() +activate DonationSelect +DonationSelect -> DB : SELECT donations + charity + user +DB --> DonationSelect : DonationRegistry +DonationSelect --> Frontpage : DonationRegistry +Deactivate DonationSelect + +Frontpage -> Frontpage : displayCharities(allCharities) +Frontpage -> Frontpage : choose random featured charity +Frontpage -> Frontpage : calculate totals and pre-approved percentage +Frontpage --> User : Show cards, featured charity and statistics +Deactivate Frontpage +@enduml diff --git a/helpmehelpapplication/Full_organisation_enrichment_scrape.puml b/helpmehelpapplication/Full_organisation_enrichment_scrape.puml new file mode 100644 index 00000000..087c774d --- /dev/null +++ b/helpmehelpapplication/Full_organisation_enrichment_scrape.puml @@ -0,0 +1,39 @@ +@startuml +!theme plain +skinparam backgroundColor white + +title Full organisation enrichment scrape + +actor "System / Developer" as Operator +participant "FullCharityScrape" as FullScrape +participant "APICharityScraper" as API +participant "URLCharityScraper" as URLScraper +participant "LogoDownloader" as Logo +collections "CharityRegistry" as Registry +collections "Charity" as Charity + +Operator -> FullScrape : getAPIAndURLCharityData() +activate FullScrape +FullScrape -> API : checkConnection() +API --> FullScrape : true +FullScrape -> API : getJSONData() +API --> FullScrape : JSON payload +FullScrape -> API : parseJSON(json) +API --> FullScrape : CharityRegistry + +loop For each charity in registry + FullScrape -> URLScraper : new URLCharityScraper(charity.getURL()) + FullScrape -> URLScraper : scrapeCharityPage() + URLScraper --> FullScrape : description, categories, logoURL, keyValues + FullScrape -> Logo : downloadImageAsBlob(logoURL) + Logo --> FullScrape : logo blob + FullScrape -> Charity : setDescription(...) + FullScrape -> Charity : setCategory(...) + FullScrape -> Charity : setLogoURL(...) + FullScrape -> Charity : setKeyValues(...) + FullScrape -> Charity : setLogoBlob(...) +end + +FullScrape --> Operator : Fully enriched CharityRegistry +Deactivate FullScrape +@enduml diff --git a/helpmehelpapplication/Organization_message.puml b/helpmehelpapplication/Organization_message.puml deleted file mode 100644 index 81ef5b9a..00000000 --- a/helpmehelpapplication/Organization_message.puml +++ /dev/null @@ -1,31 +0,0 @@ - -'https://plantuml.com/sequence-diagram - -@startuml -title Messaging between Organisation and User - -actor Organisation as Org -actor User -participant "Organization Portal (UI)" as OrgUI -participant "User App (UI)" as UserUI -participant "Messaging Service" as MsgSvc -database "Messages DB" as MsgDB -participant "Notification Service" as Notify - -Org -> OrgUI: Write message to user -OrgUI -> MsgSvc: sendMessage(orgId, userId, content) -MsgSvc -> MsgDB: store message -MsgDB --> MsgSvc: stored -MsgSvc -> Notify: notifyUser(userId, "New message") -Notify --> MsgSvc: queued -MsgSvc --> OrgUI: sent OK -OrgUI --> Org: Message sent - -User -> UserUI: Open Inbox -UserUI -> MsgSvc: getInbox(userId) -MsgSvc -> MsgDB: fetch messages for userId -MsgDB --> MsgSvc: messages -MsgSvc --> UserUI: messages -UserUI --> User: Display inbox - -@enduml \ No newline at end of file diff --git a/helpmehelpapplication/Organization_onboard.puml b/helpmehelpapplication/Organization_onboard.puml deleted file mode 100644 index fe48d6f3..00000000 --- a/helpmehelpapplication/Organization_onboard.puml +++ /dev/null @@ -1,46 +0,0 @@ - -'https://plantuml.com/sequence-diagram - -@startuml -title Organisation onboarding - -actor Organisation as Org -participant "Organization Portal (UI)" as UI -participant "Authentication Service" as Auth -participant "Email Service" as Mail -participant "Organisation Service" as OrgSvc -database "Organisation DB" as OrgDB -participant "Payment Provider" as Pay - -Org -> UI: Open Organisation Portal Dashboard -UI --> Org: Show portal dashboard - -Org -> UI: Register organisation account -UI -> Auth: register(orgEmail, password) -Auth --> UI: accountCreated + verificationToken - -Auth -> Mail: sendVerificationEmail(orgEmail, token) -Mail --> Auth: sent - -Org -> UI: Click verification link (token) -UI -> Auth: verifyEmail(token) -Auth --> UI: verified OK - -Org -> UI: Create organisation profile (name, desc, category, media) -UI -> OrgSvc: createProfile(profileData) -OrgSvc -> OrgDB: insert profile -OrgDB --> OrgSvc: created -OrgSvc --> UI: profileCreated -UI --> Org: Profile created - -Org -> UI: Set up payout account -UI -> Pay: createConnectedAccount(orgData) -Pay --> UI: connectedAccountId + status - -UI -> OrgSvc: savePayoutAccount(orgId, connectedAccountId) -OrgSvc -> OrgDB: update payout info -OrgDB --> OrgSvc: saved -OrgSvc --> UI: payoutSetupSaved -UI --> Org: Payout setup complete - -@enduml diff --git a/helpmehelpapplication/Organization_update_profile.puml b/helpmehelpapplication/Organization_update_profile.puml deleted file mode 100644 index e6ef6f06..00000000 --- a/helpmehelpapplication/Organization_update_profile.puml +++ /dev/null @@ -1,26 +0,0 @@ - -'https://plantuml.com/sequence-diagram - -@startuml -title Organisation updates profile - -actor Organisation as Org -participant "Organization Portal (UI)" as UI -participant "Organisation Service" as OrgSvc -database "Organisation DB" as OrgDB - -Org -> UI: Open My Organisation Profile -UI -> OrgSvc: getProfile -OrgSvc -> OrgDB: load profile -OrgDB --> OrgSvc: profile data -OrgSvc --> UI: profile data -UI --> Org: Show profile - -Org -> UI: Edit profile fields + upload media -UI -> OrgSvc: updateProfile -OrgSvc -> OrgDB: update profile -OrgDB --> OrgSvc: updated -OrgSvc --> UI: update OK -UI --> Org: Show updated profile - -@enduml diff --git a/helpmehelpapplication/Search_and_browse_organisation.puml b/helpmehelpapplication/Search_and_browse_organisation.puml new file mode 100644 index 00000000..6d297588 --- /dev/null +++ b/helpmehelpapplication/Search_and_browse_organisation.puml @@ -0,0 +1,82 @@ +@startuml +!theme plain +skinparam backgroundColor white + +title User searches and browses organisations + +actor User +participant "FrontpageController / CharityPageController / DonationPageController" as SourceUI +participant "LoaderScene" as Loader +participant "AvailableOrganizationController" as Available +participant "DatabaseConnection" as DBConn +participant "CharitySelect" as CharitySelect +database "MySQL DB" as DB + +User -> SourceUI : Enter search text and press search +SourceUI -> Loader : LoadScene("availableOrganization", query) +Loader -> Available : initialize() +activate Available +Available -> DBConn : create connection +DBConn --> Available : connection helper +Available -> CharitySelect : getCharitiesFromDB() +activate CharitySelect +CharitySelect -> DB : SELECT charities + charity vanity + categories + feedback +DB --> CharitySelect : CharityRegistry +CharitySelect --> Available : CharityRegistry +Deactivate CharitySelect + +Available -> Available : setInitialSearch(query) +Available -> Available : filterCharities(query) +Available -> Available : displayCharities(matches) +Available --> User : Show matching organisation cards + +loop While user edits the search field + User -> Available : Update search text + Available -> Available : filterCharities(newValue) + Available -> Available : displayCharities(filtered list) + Available --> User : Refresh result list +end + +deactivate Available +@enduml +@startuml +!theme plain +skinparam backgroundColor white + +title User searches and browses organisations + +actor User +participant "FrontpageController / CharityPageController / DonationPageController" as SourceUI +participant "LoaderScene" as Loader +participant "AvailableOrganizationController" as Available +participant "DatabaseConnection" as DBConn +participant "CharitySelect" as CharitySelect +database "MySQL DB" as DB + +User -> SourceUI : Enter search text and press search +SourceUI -> Loader : LoadScene("availableOrganization", query) +Loader -> Available : initialize() +activate Available +Available -> DBConn : create connection +DBConn --> Available : connection helper +Available -> CharitySelect : getCharitiesFromDB() +activate CharitySelect +CharitySelect -> DB : SELECT charities + charity vanity + categories + feedback +DB --> CharitySelect : CharityRegistry +CharitySelect --> Available : CharityRegistry +Deactivate CharitySelect + +Available -> Available : setInitialSearch(query) +Available -> Available : filterCharities(query) +Available -> Available : displayCharities(matches) +Available --> User : Show matching organisation cards + +loop While user edits the search field + User -> Available : Update search text + Available -> Available : filterCharities(newValue) + Available -> Available : displayCharities(filtered list) + Available --> User : Refresh result list +end + +deactivate Available +@enduml diff --git a/helpmehelpapplication/User_authenticator.puml b/helpmehelpapplication/User_authenticator.puml deleted file mode 100644 index 403814fd..00000000 --- a/helpmehelpapplication/User_authenticator.puml +++ /dev/null @@ -1,36 +0,0 @@ - -'https://plantuml.com/sequence-diagram - -@startuml -title User authentication for for signin and Login - -actor User -participant "Desktop App (UI)" as UI -participant "Authenticator Service" as Auth -database "User DB" as UDB - -User -> UI: Open Dashboard -UI --> User: Show dashboard - -User -> UI: Click Login -UI --> User: Show Welcome/Login page - -User -> UI: Go to Sign In -UI --> User: Show Sign In form - -User -> UI: Submit credentials -UI -> Auth: signIn(email, password) -Auth -> UDB: validate credentials -UDB --> Auth: valid -Auth --> UI: session/token -UI --> User: Logged in (Dashboard updated) - -User -> UI: Click "My Profile" (from Dashboard) -UI --> User: Show Profile - -User -> UI: Click Logout -UI -> Auth: logout(session) -Auth --> UI: session terminated -UI --> User: Redirect to Dashboard - -@enduml diff --git a/helpmehelpapplication/User_browser.puml b/helpmehelpapplication/User_browser.puml deleted file mode 100644 index d279b7e8..00000000 --- a/helpmehelpapplication/User_browser.puml +++ /dev/null @@ -1,56 +0,0 @@ - -'https://plantuml.com/sequence-diagram - -@startuml -title User explores organisations - -actor User -participant "Desktop App (UI)" as UI -participant "Organisation Service" as OrgSvc -database "Organisation DB" as OrgDB - -User -> UI: Open Dashboard -UI --> User: Show dashboard - -User -> UI: Click "Browse Organisations" -UI -> OrgSvc: getFeaturedOrgs() -OrgSvc -> OrgDB: query featured orgs -OrgDB --> OrgSvc: org list -OrgSvc --> UI: org list -UI --> User: Show organisations list - -alt User chooses Category - User -> UI: Select category - UI -> OrgSvc: getOrgsByCategory(category) - OrgSvc -> OrgDB: query orgs by category - OrgDB --> OrgSvc: results - OrgSvc --> UI: results - UI --> User: Show filtered list -end - -alt User uses Filter/Sort - User -> UI: Set filters/sort - UI -> OrgSvc: getOrgsFiltered(filters, sort) - OrgSvc -> OrgDB: query with filters/sort - OrgDB --> OrgSvc: results - OrgSvc --> UI: results - UI --> User: Show filtered list -end - -alt User searches from Dashboard or list - User -> UI: Search organisations (query) - UI -> OrgSvc: searchOrgs(query) - OrgSvc -> OrgDB: fulltext/search query - OrgDB --> OrgSvc: results - OrgSvc --> UI: results - UI --> User: Show search results -end - -User -> UI: Open organisation page -UI -> OrgSvc: getOrganisationDetails(orgId) -OrgSvc -> OrgDB: load org details -OrgDB --> OrgSvc: org details -OrgSvc --> UI: org details -UI --> User: Show organisation page - -@enduml \ No newline at end of file diff --git a/helpmehelpapplication/User_donate.puml b/helpmehelpapplication/User_donate.puml deleted file mode 100644 index f856d182..00000000 --- a/helpmehelpapplication/User_donate.puml +++ /dev/null @@ -1,41 +0,0 @@ - -'https://plantuml.com/sequence-diagram - -@startuml -title User donation - -actor User -participant "Desktop App (UI)" as UI -participant "Donation Service" as DonSvc -participant "Payment Provider" as Pay -database "Donation DB" as DonDB -participant "Organisation Service" as OrgSvc - -User -> UI: Click "Donate" -UI --> User: Show donation form - -User -> UI: Enter amount + confirm -UI -> DonSvc: createDonationIntent(userId, orgId, amount) - -DonSvc -> OrgSvc: validateOrganisation(orgId) -OrgSvc --> DonSvc: OK - -DonSvc -> Pay: createPaymentIntent(amount) -Pay --> DonSvc: paymentIntentId + clientSecret - -DonSvc --> UI: clientSecret -UI -> Pay: Complete payment (clientSecret) -Pay --> UI: Payment success - -UI -> DonSvc: confirmDonation(paymentIntentId) -DonSvc -> Pay: verifyPayment(paymentIntentId) -Pay --> DonSvc: verified OK - -DonSvc -> DonDB: store donation record -DonDB --> DonSvc: stored - -DonSvc --> UI: Donation confirmation -UI --> User: Show "Thank you" + receipt - -@enduml - diff --git a/helpmehelpapplication/User_donation.puml b/helpmehelpapplication/User_donation.puml new file mode 100644 index 00000000..61b56cf6 --- /dev/null +++ b/helpmehelpapplication/User_donation.puml @@ -0,0 +1,40 @@ +@startuml +!theme plain +skinparam backgroundColor white + +title User donation flow in the current application + +actor User +participant "DonationPageController" as DonationPage +participant "JavaFX Alert" as Alert +participant "LoaderScene" as Loader + +User -> DonationPage : Open donation page for selected charity +DonationPage -> DonationPage : setCharity(charity) +DonationPage --> User : Show charity name and amount field + +User -> DonationPage : Enter amount and click Donate +DonationPage -> DonationPage : Validate input + +alt Invalid amount / empty / not numeric / <= 0 / > 100000 + DonationPage -> Alert : showAlert(ERROR or WARNING) + Alert --> User : Show validation message +else Valid amount + DonationPage -> Alert : Show confirmation dialog + Alert --> User : Confirm donation? + + alt User cancels + Alert --> DonationPage : Cancel + DonationPage --> User : Stay on donation page + else User confirms + Alert --> DonationPage : OK + note right of DonationPage + end note + DonationPage -> Alert : showAlert(INFORMATION, "Thank you!") + Alert --> User : Show success message + DonationPage -> DonationPage : clear amount field + DonationPage -> Loader : LoadScene("FrontPage") + Loader --> User : Show front page + end +end +@enduml diff --git a/helpmehelpapplication/View_organisation_details.puml b/helpmehelpapplication/View_organisation_details.puml new file mode 100644 index 00000000..d833aa73 --- /dev/null +++ b/helpmehelpapplication/View_organisation_details.puml @@ -0,0 +1,29 @@ +@startuml +!theme plain +skinparam backgroundColor white + +title User opens an organisation page + +actor User +participant "OrganizationCardController" as Card +participant "LoaderScene" as Loader +participant "CharityPageController" as CharityPage +collections "Selected Charity object" as Charity + +User -> Card : Click organisation card +Card -> Loader : LoadScene("CharityPage", charity) +Loader -> CharityPage : setCharity(charity) +activate CharityPage +CharityPage -> Charity : getName() +Charity --> CharityPage : charity name +CharityPage -> Charity : getDescription() +Charity --> CharityPage : charity description +CharityPage --> User : Show organisation page + +opt User chooses to donate + User -> CharityPage : Click donate + CharityPage -> Loader : LoadScene("donationPage", charity) +end + +deactivate CharityPage +@enduml diff --git a/helpmehelpapplication/src/main/java/ntnu/systemutvikling/team6/HmHApplication.java b/helpmehelpapplication/src/main/java/ntnu/systemutvikling/team6/HmHApplication.java index 0ca2bef0..1026c202 100644 --- a/helpmehelpapplication/src/main/java/ntnu/systemutvikling/team6/HmHApplication.java +++ b/helpmehelpapplication/src/main/java/ntnu/systemutvikling/team6/HmHApplication.java @@ -4,7 +4,6 @@ import java.net.http.HttpClient; import java.util.Objects; - import javafx.application.Application; import javafx.fxml.FXMLLoader; import javafx.scene.Parent; @@ -18,7 +17,7 @@ import ntnu.systemutvikling.team6.database.Readers.UserSelect; 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.FullCharityScrape; import ntnu.systemutvikling.team6.service.APIToDatabaseService; import ntnu.systemutvikling.team6.service.AuthenticationService; @@ -61,19 +60,26 @@ public void init() { e.printStackTrace(); } /* Test and get data from Innsamlingkontrollen API */ - /* try { HttpClient https = HttpClient.newHttpClient(); - APICharityScraper scraper = new APICharityScraper(https); + // APICharityScraper scraper = new APICharityScraper(https); + FullCharityScrape scraper = new FullCharityScrape(); DatabaseConnection conn = new DatabaseConnection(); APIToDatabaseService db = new APIToDatabaseService(conn); - if (scraper.checkConnection()) { + if (scraper.getAPIScraper().checkConnection()) { + /* + if (scraper.checkConnection()) { CharityRegistry charityRegistry = scraper.parseJSON(scraper.getJSONData()); for (Charity charity : charityRegistry.getAllCharities()) { System.out.println(charity.getName()); } + */ + + // Comment out the two below to use already generated database. + CharityRegistry charityRegistry = scraper.getAPIAndURLCharityData(); + db.addAPIDataToTable(charityRegistry.getAllCharities()); } } catch (Exception e) { @@ -81,7 +87,7 @@ public void init() { } System.out.println("-- \n Init complete \n --"); - */ + } public static void main(String[] args) { 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 9838b1c7..44a66b6c 100644 --- a/helpmehelpapplication/src/main/java/ntnu/systemutvikling/team6/controller/CharityPageController.java +++ b/helpmehelpapplication/src/main/java/ntnu/systemutvikling/team6/controller/CharityPageController.java @@ -1,10 +1,20 @@ package ntnu.systemutvikling.team6.controller; -import com.google.errorprone.annotations.FormatMethod; import javafx.event.ActionEvent; +import java.io.ByteArrayInputStream; +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; import javafx.fxml.FXML; +import javafx.scene.control.Hyperlink; import javafx.scene.control.Label; import javafx.scene.control.TextField; +import javafx.scene.image.Image; +import javafx.scene.image.ImageView; +import javafx.scene.layout.StackPane; +import javafx.scene.layout.VBox; +import javafx.scene.shape.Arc; +import javafx.scene.shape.Rectangle; import ntnu.systemutvikling.team6.controller.components.BaseController; import ntnu.systemutvikling.team6.controller.components.FooterController; import ntnu.systemutvikling.team6.controller.components.LoaderScene; @@ -22,6 +32,24 @@ public class CharityPageController extends BaseController { @FXML private Label CharityName; + @FXML private ImageView CharityLogo; + + @FXML private Hyperlink CharityURL; + + @FXML private Arc keyValueInnsamlingArc; + + @FXML private Label keyValueInnsamlingLabel; + + @FXML private Arc keyValueAdminArc; + + @FXML private Label keyValueAdminLabel; + + @FXML private Arc keyValueFormaalArc; + + @FXML private Label keyValueFormaalLabel; + + @FXML private VBox categoriesContainer; + @FXML private NavbarController navbarController; @FXML private FooterController footerController; @@ -45,7 +73,7 @@ protected void authTokenisSet(){ * front page when the user clicks on a charity, to set the charity that is being displayed on the * page. * - * @param charity + * @param charity the charity to be displayed */ @FXML public void setCharity(Charity charity) { @@ -53,9 +81,43 @@ public void setCharity(Charity charity) { CharityDescription.setText(charity.getDescription()); CharityName.setText(charity.getName()); - } + if (this.charity.getLogoBlob() != null) { + ByteArrayInputStream logoByteStream = new ByteArrayInputStream(this.charity.getLogoBlob()); + Image CharityLogoImage = new Image(logoByteStream); + this.CharityLogo.setImage(CharityLogoImage); + } else { + String placeholderImagePath = + Objects.requireNonNull(getClass().getResource("/images/leggTilBilde.jpg")) + .toExternalForm(); + Image placeholderImage = new Image(placeholderImagePath); + this.CharityLogo.setImage(placeholderImage); + } + + // Sets key values to a List + String input = charity.getKeyValues(); + + String[] parts = input.split(":"); + List numbers = new ArrayList<>(); + + for (String part : parts) { + part = part.replace(",", "."); + numbers.add(Double.parseDouble(part)); + } + + // Sets the value of each arc and label + setArc(keyValueInnsamlingArc, numbers.getFirst()); + keyValueInnsamlingLabel.setText(String.format("%.1f%%", numbers.getFirst())); + setArc(keyValueAdminArc, numbers.get(1)); + keyValueAdminLabel.setText(String.format("%.1f%%", numbers.get(1))); + setArc(keyValueFormaalArc, numbers.getLast()); + keyValueFormaalLabel.setText(String.format("%.1f%%", numbers.getLast())); + + // Sets the categories + setCategories(charity.getCategory()); + } + /** * This method is used to switch to the donation page. * @@ -67,10 +129,89 @@ public void switchToDonationPage(ActionEvent event) { LoaderScene.LoadScene("donationPage", event, charity, null, authToken); } + @FXML + public void switchToFrontPage(ActionEvent event) { + System.out.println("Click"); + LoaderScene.LoadScene("FrontPage", event, charity, null, authToken); + } + + /** + * 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 private void switchToFeedbackPage(ActionEvent event){ LoaderScene.LoadScene("giveFeedback", event, charity, null, authToken); } + /** + * Opens OS default webbrowser and loads the url of the charity on click. + * + * @param event the onclick event + */ + @FXML + public void handleHomepageClick(ActionEvent event) { + try { + String url = this.charity.getURL(); + java.awt.Desktop.getDesktop().browse(java.net.URI.create(url)); + } catch (Exception e) { + System.out.println("Something went wrong when opening URL."); + e.printStackTrace(); + } + } + + /** + * Creates the labels (and the rectangle surrounding it) for the categories + * + * @param category the String for the category + * @return a fxml object used to populate the category scroll pane + */ + private StackPane createCategoryChip(String category) { + + Rectangle rect = new Rectangle(); + rect.setArcWidth(30); + rect.setArcHeight(30); + rect.setHeight(40); + rect.setWidth(200); + rect.setFill(javafx.scene.paint.Color.web("#F5F5F5")); + rect.setStroke(javafx.scene.paint.Color.web("#4F4F4F")); + + Label label = new Label(category); + label.setStyle("-fx-font-weight: bold; -fx-font-size: 14px;"); + + StackPane stack = new StackPane(rect, label); + stack.setPadding(new javafx.geometry.Insets(5)); + + return stack; + } + + /** + * Takes a list of categories for the charities and populates a scroll pane with labels containing + * the charities. + * + * @param categories the list of categories for the charity + */ + public void setCategories(List categories) { + categoriesContainer.getChildren().clear(); + + for (String category : categories) { + if (category == null || category.isEmpty()) continue; + + StackPane chip = createCategoryChip(category); + categoriesContainer.getChildren().add(chip); + } + } + + /** + * Sets the fill of the arc for the different key values. + * + * @param arc the arc for one of the 3 key values + * @param percent the percentage of the key value + */ + private void setArc(Arc arc, double percent) { + double angle = -360 * (percent / 100.0); + arc.setLength(angle); + } } 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 660a9d30..a4bd5993 100644 --- a/helpmehelpapplication/src/main/java/ntnu/systemutvikling/team6/controller/FrontpageController.java +++ b/helpmehelpapplication/src/main/java/ntnu/systemutvikling/team6/controller/FrontpageController.java @@ -3,6 +3,7 @@ import java.io.IOException; import java.util.ArrayList; import java.util.List; +import java.util.Objects; import java.util.Random; import javafx.application.Platform; @@ -17,6 +18,7 @@ import javafx.scene.layout.FlowPane; import ntnu.systemutvikling.team6.controller.components.*; import ntnu.systemutvikling.team6.database.DatabaseConnection; +import ntnu.systemutvikling.team6.database.Readers.CategorySelect; import ntnu.systemutvikling.team6.database.Readers.CharitySelect; import ntnu.systemutvikling.team6.database.Readers.DonationSelect; import ntnu.systemutvikling.team6.models.Charity; @@ -41,9 +43,8 @@ public class FrontpageController extends BaseController{ @FXML private Label PreApproved_Percentage; @FXML private CheckBox verifiedFilter; - @FXML private CheckBox childrenFilter; - @FXML private CheckBox healthFilter; - @FXML private CheckBox emergencyAidFilter; + @FXML private javafx.scene.layout.VBox categoryList; + private final List selectedCategories = new ArrayList<>(); @FXML private NavbarController navbarController; @FXML private FooterController footerController; @@ -77,8 +78,27 @@ private void loadPage(){ DatabaseConnection conn = new DatabaseConnection(); CharitySelect cdb = new CharitySelect(conn); DonationSelect ddb = new DonationSelect(conn); + CategorySelect categoryselect = new CategorySelect(conn); CharityRegistry charities = cdb.getCharitiesFromDB(); DonationRegistry donations = ddb.getDonationFromDB(); + List categories = categoryselect.getCategoriesFromDB(); + categories.sort(String::compareToIgnoreCase); + + for (String category : categories) { + CheckBox cb = new CheckBox(category); + + cb.setOnAction( + e -> { + if (cb.isSelected()) { + selectedCategories.add(category); + } else { + selectedCategories.remove(category); + } + displayCharities(getFilteredCharities()); + }); + + categoryList.getChildren().add(cb); + } allCharities = new ArrayList<>(charities.getAllCharities()); displayCharities(allCharities); @@ -108,7 +128,6 @@ private void loadPage(){ } } - /** * This method is used to switch to the charity page for the selected charity * @@ -140,69 +159,42 @@ public void handleCategoryFilterChange(ActionEvent event) { /** * This method is used to filter the charities based on the selected filters. * + *

The filters are whether the charity was pre-verified, or if it falls under one (or more) of + * the given categories.

+ * + *

The check checks whether the given charity has ALL filters. If one does not apply, + * the charity is rejected.

+ * * @return a list of filtered charities. */ private List getFilteredCharities() { - if (!verifiedFilter.isSelected() - && !childrenFilter.isSelected() - && !healthFilter.isSelected() - && !emergencyAidFilter.isSelected()) { - return allCharities; - } - List filteredCharities = new ArrayList<>(); + List filtered = new ArrayList<>(); + for (Charity charity : allCharities) { - if (matchesSelectedFilters(charity)) { - filteredCharities.add(charity); + + if (verifiedFilter.isSelected() && !charity.getPreApproved()) { + continue; } - } - 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")); - } + if (!selectedCategories.isEmpty()) { - /** - * 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; - }; + boolean hasAll = + charity.getCategory().stream() + .filter(Objects::nonNull) + .map(c -> c.toLowerCase().trim()) + .collect(java.util.stream.Collectors.toSet()) + .containsAll(selectedCategories); + + if (!hasAll) { + continue; + } + } + + filtered.add(charity); + } + + return filtered; } /** @@ -215,7 +207,8 @@ private void displayCharities(List charities) { for (Charity charity : charities) { try { - FXMLLoader loader = new FXMLLoader(getClass().getResource("/fxml/components/organizationCard.fxml")); + FXMLLoader loader = + new FXMLLoader(getClass().getResource("/fxml/components/organizationCard.fxml")); Parent card = loader.load(); OrganizationCardController cardController = loader.getController(); cardController.setOrganization(charity); diff --git a/helpmehelpapplication/src/main/java/ntnu/systemutvikling/team6/database/DAO/DonationDAO.java b/helpmehelpapplication/src/main/java/ntnu/systemutvikling/team6/database/DAO/DonationDAO.java index e40641ae..dbcdf387 100644 --- a/helpmehelpapplication/src/main/java/ntnu/systemutvikling/team6/database/DAO/DonationDAO.java +++ b/helpmehelpapplication/src/main/java/ntnu/systemutvikling/team6/database/DAO/DonationDAO.java @@ -14,11 +14,11 @@ public class DonationDAO { private final DatabaseConnection connection; - public DonationDAO(DatabaseConnection connection) { - this.connection = 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. * @@ -42,7 +42,6 @@ INSERT INTO Donations (UUID_Donations, amount, isAnonymous, date, charity_id, us ps.setString(5, donation.getCharity().getUUID().toString()); ps.setString(6, donation.getDonor().getId().toString()); - ps.executeUpdate(); conn.commit(); } catch (SQLException e) { 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 index 5fc0171f..23578946 100644 --- a/helpmehelpapplication/src/main/java/ntnu/systemutvikling/team6/database/DAO/UserDAO.java +++ b/helpmehelpapplication/src/main/java/ntnu/systemutvikling/team6/database/DAO/UserDAO.java @@ -17,14 +17,13 @@ public class UserDAO { private final DatabaseConnection connection; - public UserDAO(DatabaseConnection connection) { - this.connection = 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 @@ -79,7 +78,6 @@ INSERT INTO Settings ( psSettings.setBoolean(4, user.getSettings().isLightMode()); psSettingsRows = psSettings.executeUpdate(); - } conn.commit(); diff --git a/helpmehelpapplication/src/main/java/ntnu/systemutvikling/team6/database/DatabaseSetup.java b/helpmehelpapplication/src/main/java/ntnu/systemutvikling/team6/database/DatabaseSetup.java index 97c43913..4b138de5 100644 --- a/helpmehelpapplication/src/main/java/ntnu/systemutvikling/team6/database/DatabaseSetup.java +++ b/helpmehelpapplication/src/main/java/ntnu/systemutvikling/team6/database/DatabaseSetup.java @@ -197,7 +197,9 @@ FOREIGN KEY (`user_id`) CREATE TABLE IF NOT EXISTS `apbaluna`.`Categories` ( `category_id` INT NOT NULL AUTO_INCREMENT, `category` VARCHAR(255) NOT NULL, - PRIMARY KEY (`category_id`)) + PRIMARY KEY (`category_id`), + UNIQUE (`category`) + ) ENGINE = InnoDB; """; @@ -220,8 +222,8 @@ FOREIGN KEY (`Categories_category_id`) CONSTRAINT `fk_Categories_has_Charities_Charities1` FOREIGN KEY (`Charities_UUID_charities`) REFERENCES `apbaluna`.`Charities` (`UUID_charities`) - ON DELETE NO ACTION - ON UPDATE NO ACTION) + ON DELETE CASCADE + ON UPDATE CASCADE) ENGINE = InnoDB; """; @@ -258,6 +260,7 @@ FOREIGN KEY (`CharityUserId`) `logoURL` TEXT NULL, `key_values` TEXT NULL, `logoBLOB` MEDIUMBLOB NULL, + CONSTRAINT `unique_UUID_charity` UNIQUE (`UUID_charity`), INDEX `fk_CharityVanity_Charities1_idx` (`UUID_charity` ASC) VISIBLE, CONSTRAINT `fk_CharityVanity_Charities1` FOREIGN KEY (`UUID_charity`) diff --git a/helpmehelpapplication/src/main/java/ntnu/systemutvikling/team6/database/Readers/CategorySelect.java b/helpmehelpapplication/src/main/java/ntnu/systemutvikling/team6/database/Readers/CategorySelect.java new file mode 100644 index 00000000..5ce735e4 --- /dev/null +++ b/helpmehelpapplication/src/main/java/ntnu/systemutvikling/team6/database/Readers/CategorySelect.java @@ -0,0 +1,53 @@ +package ntnu.systemutvikling.team6.database.Readers; + +import java.sql.Connection; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.Statement; +import java.util.ArrayList; +import java.util.List; +import ntnu.systemutvikling.team6.database.DatabaseConnection; + +/** + * Data access class responsible for getting all the distinct categories from the database. + * + *

All queries are executed against a MySQL database via a {@link DatabaseConnection}. + */ +public class CategorySelect { + 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 CategorySelect(DatabaseConnection connection) { + this.connection = connection; + } + + /** + * Retrieves all the categories listed in the Category table of the database. + * + * @return a list of strings containing the name of the categories + */ + public List getCategoriesFromDB() { + List categories = new ArrayList<>(); + + try (Connection conn = connection.getMySqlConnection(); + Statement stmt = conn.createStatement(); + ResultSet rs = stmt.executeQuery("SELECT category FROM Categories")) { + + while (rs.next()) { + categories.add(rs.getString("category")); + } + + } catch (SQLException e) { + e.printStackTrace(); + throw new RuntimeException( + "ERROR: Something went wrong during fetching categories from database."); + } + + return categories; + } +} 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 10efe2d7..640d90c0 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 @@ -52,8 +52,10 @@ public CharitySelect(DatabaseConnection connection) { public CharityRegistry getCharitiesFromDB() { CharityRegistry registry = null; Connection conn = null; + try { conn = connection.getMySqlConnection(); + String sql_query = """ SELECT @@ -68,6 +70,7 @@ public CharityRegistry getCharitiesFromDB() { LEFT JOIN Charity_Categories cc ON cc.Charities_UUID_charities = c.UUID_charities LEFT JOIN Categories cat ON cat.category_id = cc.Categories_category_id INNER JOIN CharityVanity cv ON cv.UUID_charity = c.UUID_charities; + ORDER BY c.UUID_charities; """; Statement stmt = conn.createStatement(); ResultSet rs = stmt.executeQuery(sql_query); @@ -78,10 +81,12 @@ public CharityRegistry getCharitiesFromDB() { 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"), @@ -94,6 +99,7 @@ public CharityRegistry getCharitiesFromDB() { rs.getString("logoURL"), rs.getString("key_values"), rs.getBytes("logoBLOB")); + registry.addCharity(currentCharity); lastCharity = currentId; seenFeedbackIds.clear(); @@ -105,8 +111,10 @@ public CharityRegistry getCharitiesFromDB() { } String feedbackId = rs.getString("UUID_feedback"); + if (feedbackId != null && !seenFeedbackIds.contains(feedbackId)) { seenFeedbackIds.add(feedbackId); + User userWithMinimalSettingsAndInbox = new User( rs.getString("UUID_User"), @@ -114,7 +122,8 @@ public CharityRegistry getCharitiesFromDB() { rs.getString("user_email"), rs.getString("user_password"), rs.getString("role")); - userWithMinimalSettingsAndInbox.setSettings(new Settings(false, Language.ENGLISH, false)); + + userWithMinimalSettingsAndInbox.setSettings(new Settings(false, Language.ENGLISH, false)); Feedback feedback = new Feedback( @@ -131,6 +140,7 @@ public CharityRegistry getCharitiesFromDB() { e.printStackTrace(); throw new RuntimeException("ERROR: Something went wrong during updating charities table."); } + return registry; } @@ -185,7 +195,7 @@ public ArrayList getFeedbackforCharityUUID(String charity_uuid) { Feedback feedback = new Feedback( rs.getString("UUID_feedback"), - userWithSettingsAndNoInbox, + userWithSettingsAndNoInbox, rs.getString("feedback_comment"), LocalDate.parse(rs.getString("feedback_date"))); Feedbacks.add(feedback); 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 09d6ad0e..4516abba 100644 --- a/helpmehelpapplication/src/main/java/ntnu/systemutvikling/team6/models/Charity.java +++ b/helpmehelpapplication/src/main/java/ntnu/systemutvikling/team6/models/Charity.java @@ -214,8 +214,7 @@ public void setFeedbacks(ArrayList feedbacks) { this.feedbacks = feedbacks; } - public void setUUIDFromString(String uuid){ + public void setUUIDFromString(String uuid) { this.UUID = java.util.UUID.fromString(uuid); } } - 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 05d86558..20e1663a 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 @@ -67,7 +67,7 @@ public User(String username, String email, String password, Role role, Settings } /** - * Creates a new user taylored for interaction with the DATABASE. Settings and inbox can be set on a + * 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 diff --git a/helpmehelpapplication/src/main/java/ntnu/systemutvikling/team6/scraper/FullCharityScrape.java b/helpmehelpapplication/src/main/java/ntnu/systemutvikling/team6/scraper/FullCharityScrape.java index a69e3a9b..e881d424 100644 --- a/helpmehelpapplication/src/main/java/ntnu/systemutvikling/team6/scraper/FullCharityScrape.java +++ b/helpmehelpapplication/src/main/java/ntnu/systemutvikling/team6/scraper/FullCharityScrape.java @@ -103,4 +103,8 @@ public CharityRegistry getAPIAndURLCharityData() throws IOException, Interrupted } return charityRegistry; } + + public APICharityScraper getAPIScraper() { + return this.apiScraper; + } } 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 index f9e98b37..33b3c529 100644 --- a/helpmehelpapplication/src/main/java/ntnu/systemutvikling/team6/scraper/scraperComponents/URLCharityScraper.java +++ b/helpmehelpapplication/src/main/java/ntnu/systemutvikling/team6/scraper/scraperComponents/URLCharityScraper.java @@ -70,7 +70,7 @@ public URLCharityScraper(String url, WebDriver driver) { * @return the {@code WebDriverWait} object to be used in the methods */ protected WebDriverWait createWait() { - return new WebDriverWait(driver, Duration.ofSeconds(30)); + return new WebDriverWait(driver, Duration.ofSeconds(10)); } /** @@ -118,7 +118,7 @@ protected void updateDescription() { wait.until( ExpectedConditions.numberOfElementsToBeMoreThan(By.cssSelector(".information"), 0)); - Thread.sleep(5000); + // Thread.sleep(5000); List firstDescription = findElements(By.cssSelector(".information")); for (WebElement element : firstDescription) { @@ -139,7 +139,7 @@ void updateLogo() { try { WebDriverWait wait = createWait(); wait.until(ExpectedConditions.visibilityOfElementLocated(By.cssSelector(".logo > img"))); - Thread.sleep(5000); + // Thread.sleep(5000); WebElement logo = findElement(By.cssSelector(".logo > img")); this.logoURL = logo.getAttribute("src"); @@ -155,7 +155,7 @@ void updateCategories() { WebDriverWait wait = createWait(); wait.until(ExpectedConditions.presenceOfElementLocated(By.cssSelector(".tag-label"))); - Thread.sleep(5000); + // Thread.sleep(5000); List elements = findElements(By.cssSelector(".tag-label")); @@ -183,7 +183,7 @@ void updateKeyValues() { ExpectedConditions.visibilityOfElementLocated( By.xpath( "//li[.//h2[normalize-space()='Innsamlingsprosent']]//div[@class='graph']"))); - Thread.sleep(5000); + // Thread.sleep(5000); element = findElement( By.xpath("//li[.//h2[normalize-space()='Innsamlingsprosent']]//div[@class='graph']")); @@ -224,9 +224,9 @@ public void scrapeCharityPage() { updateLogo(); updateCategories(); updateKeyValues(); - Thread.sleep(1000); + // Thread.sleep(1000); - } catch (InterruptedException e) { + } catch (Exception e) { throw new RuntimeException(e); } finally { closeDriver(); diff --git a/helpmehelpapplication/src/main/java/ntnu/systemutvikling/team6/service/APIToDatabaseService.java b/helpmehelpapplication/src/main/java/ntnu/systemutvikling/team6/service/APIToDatabaseService.java index 7c2a7fab..9e73c5c1 100644 --- a/helpmehelpapplication/src/main/java/ntnu/systemutvikling/team6/service/APIToDatabaseService.java +++ b/helpmehelpapplication/src/main/java/ntnu/systemutvikling/team6/service/APIToDatabaseService.java @@ -37,39 +37,58 @@ public APIToDatabaseService(DatabaseConnection connection) { */ 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); - """; + + 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); + """; + + String sql3 = "INSERT IGNORE INTO Categories (category) VALUES (?)"; + + String sql4 = "SELECT category_id FROM Categories WHERE category = ?"; + + String sql5 = + """ + INSERT IGNORE INTO Charity_Categories (Categories_category_id, Charities_UUID_charities) + VALUES (?, ?) + """; try (PreparedStatement ps1 = conn.prepareStatement(sql1); - PreparedStatement ps2 = conn.prepareStatement(sql2)) { + PreparedStatement ps2 = conn.prepareStatement(sql2); + PreparedStatement ps3 = conn.prepareStatement(sql3); + PreparedStatement ps4 = conn.prepareStatement(sql4); + PreparedStatement ps5 = conn.prepareStatement(sql5)) { for (Charity charity : charities) { String uuid; if (charity.getUUID() == null) { - uuid = UUID.randomUUID().toString(); + + uuid = + UUID.nameUUIDFromBytes( + (charity.getOrg_number() + charity.getURL() + charity.getName()).getBytes()) + .toString(); charity.setUUIDFromString(uuid); - System.out.println("API object doesnt have UUID, assigning"); + System.out.println("API object doesn't have UUID, assigning stable UUID"); } else { uuid = charity.getUUID().toString(); } @@ -89,6 +108,30 @@ INSERT INTO CharityVanity (UUID_charity, charity_name, charity_link, description ps2.setBytes(7, charity.getLogoBlob()); ps2.executeUpdate(); + if (charity.getCategory() != null) { + for (String categoryRaw : charity.getCategory()) { + if (categoryRaw == null) continue; + + String category = categoryRaw.toLowerCase().trim(); + + if (category.isEmpty()) continue; + + ps3.setString(1, category); + ps3.executeUpdate(); + + ps4.setString(1, category); + + try (ResultSet rs = ps4.executeQuery()) { + if (rs.next()) { + int categoryId = rs.getInt("category_id"); + + ps5.setInt(1, categoryId); + ps5.setString(2, uuid); + ps5.executeUpdate(); + } + } + } + } } } catch (Exception e) { @@ -96,53 +139,63 @@ INSERT INTO CharityVanity (UUID_charity, charity_name, charity_link, description throw new RuntimeException(e); } - // -- Intergerty Check: + // Integrity Check String createTemp = """ - CREATE TEMPORARY TABLE temp_api_charities ( - org_number VARCHAR(255) PRIMARY KEY - ) - """; - + 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(); + 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 + ); + """; + + String deleteUnusedCategoriesSql = + """ + + DELETE FROM Categories c + WHERE NOT EXISTS ( + SELECT 1 + FROM Charity_Categories cc + WHERE cc.Categories_category_id = c.category_id + ); + """; + + try (PreparedStatement ps1 = conn.prepareStatement(deleteSql); + PreparedStatement ps2 = conn.prepareStatement(deleteUnusedCategoriesSql)) { + + ps1.executeUpdate(); + ps2.executeUpdate(); } conn.commit(); @@ -156,7 +209,6 @@ AND NOT EXISTS ( } } e.printStackTrace(); - throw new RuntimeException("ERROR: Something went wrong during updating charities table."); } finally { if (conn != null) { diff --git a/helpmehelpapplication/src/main/resources/fxml/available_organizations.fxml b/helpmehelpapplication/src/main/resources/fxml/available_organizations.fxml index 17a10bd9..51e05252 100644 --- a/helpmehelpapplication/src/main/resources/fxml/available_organizations.fxml +++ b/helpmehelpapplication/src/main/resources/fxml/available_organizations.fxml @@ -29,7 +29,6 @@ - diff --git a/helpmehelpapplication/src/main/resources/fxml/components/organizationCard.fxml b/helpmehelpapplication/src/main/resources/fxml/components/organizationCard.fxml index 45380d8b..9076186d 100644 --- a/helpmehelpapplication/src/main/resources/fxml/components/organizationCard.fxml +++ b/helpmehelpapplication/src/main/resources/fxml/components/organizationCard.fxml @@ -5,6 +5,7 @@ + @@ -27,8 +28,16 @@ - + diff --git a/helpmehelpapplication/src/main/resources/fxml/donationPage.fxml b/helpmehelpapplication/src/main/resources/fxml/donationPage.fxml index e30e7854..df065b4f 100644 --- a/helpmehelpapplication/src/main/resources/fxml/donationPage.fxml +++ b/helpmehelpapplication/src/main/resources/fxml/donationPage.fxml @@ -87,11 +87,18 @@