Orchestrates a full charity data scrape by combining two data sources:
+
+
+ The external charity API (via APICharityScraper), which provides structured data
+ such as organisation numbers, approval status, and charity URLs.
+Starting from the Overview page, you can browse the documentation using the links in each page, and in the navigation bar at the top of each page. The Index and Search box allow you to navigate to specific declarations and summary pages, including: All Packages, All Classes and Interfaces
+
+
Search
+
You can search for definitions of modules, packages, types, fields, methods, system properties and other terms defined in the API. These items can be searched using part or all of the name, optionally using "camelCase" abbreviations, or multiple search terms separated by whitespace. Some examples:
Documentation pages provide keyboard shortcuts to facilitate access to common navigation tasks.
+
+
Type
+/
+ to access the search input field in any page.
+
Type
+.
+ to access the filter input field in the sidebar of class pages.
+
Type
+Esc
+ to clear the input and release keyboard focus in any input field.
+
Type
+Tab
+/
+↓
+/
+↑
+ to select list items after entering a search term in a search or filter input field.
+
Type
+←
+/
+→
+ to switch between tabs in tabbed summary tables.
+
+
+
+
+
+
Kinds of Pages
+The following sections describe the different kinds of pages in this collection.
+
+
Overview
+
The Overview page is the front page of this API document and provides a list of all packages with a summary for each. This page can also contain an overall description of the set of packages.
+
+
+
Package
+
Each package has a page that contains a list of its classes and interfaces, with a summary for each. These pages may contain the following categories:
+
+
Interfaces
+
Classes
+
Enum Classes
+
Exception Classes
+
Annotation Interfaces
+
+
+
+
Class or Interface
+
Each class, interface, nested class and nested interface has its own separate page. Each of these pages has three sections consisting of a declaration and description, member summary tables, and detailed member descriptions. Entries in each of these sections are omitted if they are empty or not applicable.
+
+
Class Inheritance Diagram
+
Direct Subclasses
+
All Known Subinterfaces
+
All Known Implementing Classes
+
Class or Interface Declaration
+
Class or Interface Description
+
+
+
+
Nested Class Summary
+
Enum Constant Summary
+
Field Summary
+
Property Summary
+
Constructor Summary
+
Method Summary
+
Required Element Summary
+
Optional Element Summary
+
+
+
+
Enum Constant Details
+
Field Details
+
Property Details
+
Constructor Details
+
Method Details
+
Element Details
+
+
Note: Annotation interfaces have required and optional elements, but not methods. Only enum classes have enum constants. The components of a record class are displayed as part of the declaration of the record class. Properties are a feature of JavaFX.
+
The summary entries are alphabetical, while the detailed descriptions are in the order they appear in the source code. This preserves the logical groupings established by the programmer.
+
+
+
Other Files
+
Packages and modules may contain pages with additional information related to the declarations nearby.
+
+
+
Use
+
Each documented package, class or interface has its own Use page, which lists packages, classes, interfaces, methods, constructors and fields that use any part of that package, class or interface. Given a class or interface A, its Use page includes subclasses or subinterfaces of A, fields declared as A, methods that return A, methods and constructors with parameters of type A, and subclasses or subinterfaces with parameters of type A. You can access this page by first going to the package, class or interface, then clicking on the USE link in the navigation bar.
+
+
+
Tree (Class Hierarchy)
+
There is a Class Hierarchy page for all packages, plus a hierarchy for each package. Each hierarchy page contains a list of classes and a list of interfaces. Classes are organized by inheritance structure starting with java.lang.Object. Interfaces do not inherit from java.lang.Object.
+
+
When viewing the Overview page, clicking on TREE displays the hierarchy for all packages.
+
When viewing a particular package, class or interface page, clicking on TREE displays the hierarchy for only that package.
+
+
+
+
Serialized Form
+
Each serializable or externalizable class has a description of its serialization fields and methods. This information is of interest to those who implement rather than use the API. While there is no link in the navigation bar, you can get to this information by going to any serialized class and clicking "Serialized Form" in the "See Also" section of the class description.
+
+
+
All Packages
+
The All Packages page contains an alphabetic index of all packages contained in the documentation.
+
+
+
All Classes and Interfaces
+
The All Classes and Interfaces page contains an alphabetic index of all classes and interfaces contained in the documentation, including annotation interfaces, enum classes, and record classes.
+
+
+
Index
+
The Index contains an alphabetic index of all classes, interfaces, constructors, methods, and fields in the documentation, as well as summary pages such as All Packages, All Classes and Interfaces.
+
+
+
+
+
Release Details
+
The details for each module, package, class or interface normally include the release in which the declaration was introduced.
+
When a member is added after the initial introduction of the enclosing class or interface, the details of the member include the release in which it was introduced.
+
+
+This help file applies to API documentation generated by the standard doclet.
+
+
+
This method is used to verify the integrity of the data in the charities table and to
+update it based on the data retrieved from the IK API and the charity's URL.
Orchestrates a full charity data scrape by combining two data sources:
+
+
+ The external charity API (via APICharityScraper), which provides structured data
+ such as organisation numbers, approval status, and charity URLs.
+Fonts are (c) Bitstream (see below). DejaVu changes are in public domain.
+Glyphs imported from Arev fonts are (c) Tavmjong Bah (see below)
+
+
+Bitstream Vera Fonts Copyright
+------------------------------
+
+Copyright (c) 2003 by Bitstream, Inc. All Rights Reserved. Bitstream Vera is
+a trademark of Bitstream, Inc.
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of the fonts accompanying this license ("Fonts") and associated
+documentation files (the "Font Software"), to reproduce and distribute the
+Font Software, including without limitation the rights to use, copy, merge,
+publish, distribute, and/or sell copies of the Font Software, and to permit
+persons to whom the Font Software is furnished to do so, subject to the
+following conditions:
+
+The above copyright and trademark notices and this permission notice shall
+be included in all copies of one or more of the Font Software typefaces.
+
+The Font Software may be modified, altered, or added to, and in particular
+the designs of glyphs or characters in the Fonts may be modified and
+additional glyphs or characters may be added to the Fonts, only if the fonts
+are renamed to names not containing either the words "Bitstream" or the word
+"Vera".
+
+This License becomes null and void to the extent applicable to Fonts or Font
+Software that has been modified and is distributed under the "Bitstream
+Vera" names.
+
+The Font Software may be sold as part of a larger software package but no
+copy of one or more of the Font Software typefaces may be sold by itself.
+
+THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF COPYRIGHT, PATENT,
+TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL BITSTREAM OR THE GNOME
+FOUNDATION BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, INCLUDING
+ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL DAMAGES,
+WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF
+THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM OTHER DEALINGS IN THE
+FONT SOFTWARE.
+
+Except as contained in this notice, the names of Gnome, the Gnome
+Foundation, and Bitstream Inc., shall not be used in advertising or
+otherwise to promote the sale, use or other dealings in this Font Software
+without prior written authorization from the Gnome Foundation or Bitstream
+Inc., respectively. For further information, contact: fonts at gnome dot
+org.
+
+Arev Fonts Copyright
+------------------------------
+
+Copyright (c) 2006 by Tavmjong Bah. All Rights Reserved.
+
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of the fonts accompanying this license ("Fonts") and
+associated documentation files (the "Font Software"), to reproduce
+and distribute the modifications to the Bitstream Vera Font Software,
+including without limitation the rights to use, copy, merge, publish,
+distribute, and/or sell copies of the Font Software, and to permit
+persons to whom the Font Software is furnished to do so, subject to
+the following conditions:
+
+The above copyright and trademark notices and this permission notice
+shall be included in all copies of one or more of the Font Software
+typefaces.
+
+The Font Software may be modified, altered, or added to, and in
+particular the designs of glyphs or characters in the Fonts may be
+modified and additional glyphs or characters may be added to the
+Fonts, only if the fonts are renamed to names not containing either
+the words "Tavmjong Bah" or the word "Arev".
+
+This License becomes null and void to the extent applicable to Fonts
+or Font Software that has been modified and is distributed under the
+"Tavmjong Bah Arev" names.
+
+The Font Software may be sold as part of a larger software package but
+no copy of one or more of the Font Software typefaces may be sold by
+itself.
+
+THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
+OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL
+TAVMJONG BAH BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
+DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
+OTHER DEALINGS IN THE FONT SOFTWARE.
+
+Except as contained in this notice, the name of Tavmjong Bah shall not
+be used in advertising or otherwise to promote the sale, use or other
+dealings in this Font Software without prior written authorization
+from Tavmjong Bah. For further information, contact: tavmjong @ free
+. fr.
+
+TeX Gyre DJV Math
+-----------------
+Fonts are (c) Bitstream (see below). DejaVu changes are in public domain.
+
+Math extensions done by B. Jackowski, P. Strzelczyk and P. Pianowski
+(on behalf of TeX users groups) are in public domain.
+
+Letters imported from Euler Fraktur from AMSfonts are (c) American
+Mathematical Society (see below).
+Bitstream Vera Fonts Copyright
+Copyright (c) 2003 by Bitstream, Inc. All Rights Reserved. Bitstream Vera
+is a trademark of Bitstream, Inc.
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of the fonts accompanying this license ("Fonts") and associated
+documentation
+files (the "Font Software"), to reproduce and distribute the Font Software,
+including without limitation the rights to use, copy, merge, publish,
+distribute,
+and/or sell copies of the Font Software, and to permit persons to whom
+the Font Software is furnished to do so, subject to the following
+conditions:
+
+The above copyright and trademark notices and this permission notice
+shall be
+included in all copies of one or more of the Font Software typefaces.
+
+The Font Software may be modified, altered, or added to, and in particular
+the designs of glyphs or characters in the Fonts may be modified and
+additional
+glyphs or characters may be added to the Fonts, only if the fonts are
+renamed
+to names not containing either the words "Bitstream" or the word "Vera".
+
+This License becomes null and void to the extent applicable to Fonts or
+Font Software
+that has been modified and is distributed under the "Bitstream Vera"
+names.
+
+The Font Software may be sold as part of a larger software package but
+no copy
+of one or more of the Font Software typefaces may be sold by itself.
+
+THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF COPYRIGHT, PATENT,
+TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL BITSTREAM OR THE GNOME
+FOUNDATION
+BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, INCLUDING ANY GENERAL,
+SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, WHETHER IN AN
+ACTION
+OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF THE USE OR
+INABILITY TO USE
+THE FONT SOFTWARE OR FROM OTHER DEALINGS IN THE FONT SOFTWARE.
+Except as contained in this notice, the names of GNOME, the GNOME
+Foundation,
+and Bitstream Inc., shall not be used in advertising or otherwise to promote
+the sale, use or other dealings in this Font Software without prior written
+authorization from the GNOME Foundation or Bitstream Inc., respectively.
+For further information, contact: fonts at gnome dot org.
+
+AMSFonts (v. 2.2) copyright
+
+The PostScript Type 1 implementation of the AMSFonts produced by and
+previously distributed by Blue Sky Research and Y&Y, Inc. are now freely
+available for general use. This has been accomplished through the
+cooperation
+of a consortium of scientific publishers with Blue Sky Research and Y&Y.
+Members of this consortium include:
+
+Elsevier Science IBM Corporation Society for Industrial and Applied
+Mathematics (SIAM) Springer-Verlag American Mathematical Society (AMS)
+
+In order to assure the authenticity of these fonts, copyright will be
+held by
+the American Mathematical Society. This is not meant to restrict in any way
+the legitimate use of the fonts, such as (but not limited to) electronic
+distribution of documents containing these fonts, inclusion of these fonts
+into other public domain or commercial font collections or computer
+applications, use of the outline data to create derivative fonts and/or
+faces, etc. However, the AMS does require that the AMS copyright notice be
+removed from any derivative versions of the fonts which have been altered in
+any way. In addition, to ensure the fidelity of TeX documents using Computer
+Modern fonts, Professor Donald Knuth, creator of the Computer Modern faces,
+has requested that any alterations which yield different font metrics be
+given a different name.
+
+
diff --git a/docs/Javadoc/apidocs/legal/jquery.md b/docs/Javadoc/apidocs/legal/jquery.md
new file mode 100644
index 00000000..a763ec6f
--- /dev/null
+++ b/docs/Javadoc/apidocs/legal/jquery.md
@@ -0,0 +1,26 @@
+## jQuery v3.7.1
+
+### jQuery License
+```
+jQuery v 3.7.1
+Copyright OpenJS Foundation and other contributors, https://openjsf.org/
+
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of this software and associated documentation files (the
+"Software"), to deal in the Software without restriction, including
+without limitation the rights to use, copy, modify, merge, publish,
+distribute, sublicense, and/or sell copies of the Software, and to
+permit persons to whom the Software is furnished to do so, subject to
+the following conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+```
diff --git a/docs/Javadoc/apidocs/legal/jqueryUI.md b/docs/Javadoc/apidocs/legal/jqueryUI.md
new file mode 100644
index 00000000..46bfbaa5
--- /dev/null
+++ b/docs/Javadoc/apidocs/legal/jqueryUI.md
@@ -0,0 +1,49 @@
+## jQuery UI v1.14.1
+
+### jQuery UI License
+```
+Copyright OpenJS Foundation and other contributors, https://openjsf.org/
+
+This software consists of voluntary contributions made by many
+individuals. For exact contribution history, see the revision history
+available at https://github.com/jquery/jquery-ui
+
+The following license applies to all parts of this software except as
+documented below:
+
+====
+
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of this software and associated documentation files (the
+"Software"), to deal in the Software without restriction, including
+without limitation the rights to use, copy, modify, merge, publish,
+distribute, sublicense, and/or sell copies of the Software, and to
+permit persons to whom the Software is furnished to do so, subject to
+the following conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+====
+
+Copyright and related rights for sample code are waived via CC0. Sample
+code is defined as all source code contained within the demos directory.
+
+CC0: http://creativecommons.org/publicdomain/zero/1.0/
+
+====
+
+All files located in the node_modules and external directories are
+externally maintained libraries used by this software which have their
+own licenses; we recommend you read them, as their terms may differ from
+the terms above.
+
+```
diff --git a/docs/Javadoc/apidocs/member-search-index.js b/docs/Javadoc/apidocs/member-search-index.js
new file mode 100644
index 00000000..ebd9a0f1
--- /dev/null
+++ b/docs/Javadoc/apidocs/member-search-index.js
@@ -0,0 +1 @@
+memberSearchIndex = [{"p":"ntnu.systemutvikling.team6.service","c":"APIToDatabaseService","l":"addAPIDataToTable(List)","u":"addAPIDataToTable(java.util.List)"},{"p":"ntnu.systemutvikling.team6.models.registry","c":"CharityRegistry","l":"addCharity(Charity)","u":"addCharity(ntnu.systemutvikling.team6.models.Charity)"},{"p":"ntnu.systemutvikling.team6.database.DAO","c":"DonationDAO","l":"addDonation(Charity, User, double)","u":"addDonation(ntnu.systemutvikling.team6.models.Charity,ntnu.systemutvikling.team6.models.user.User,double)"},{"p":"ntnu.systemutvikling.team6.models.registry","c":"DonationRegistry","l":"addDonation(Donation)","u":"addDonation(ntnu.systemutvikling.team6.models.Donation)"},{"p":"ntnu.systemutvikling.team6.models.user","c":"Inbox","l":"addMessage(Message)","u":"addMessage(ntnu.systemutvikling.team6.models.user.Message)"},{"p":"ntnu.systemutvikling.team6.models.registry","c":"UserRegistry","l":"addUser(User)","u":"addUser(ntnu.systemutvikling.team6.models.user.User)"},{"p":"ntnu.systemutvikling.team6.scraper.scraperComponents","c":"APICharityData","l":"APICharityData(String, String, String, String, boolean)","u":"%3Cinit%3E(java.lang.String,java.lang.String,java.lang.String,java.lang.String,boolean)","k":"3"},{"p":"ntnu.systemutvikling.team6.scraper.scraperComponents","c":"APICharityScraper","l":"APICharityScraper(HttpClient)","u":"%3Cinit%3E(java.net.http.HttpClient)","k":"3"},{"p":"ntnu.systemutvikling.team6.service","c":"APIToDatabaseService","l":"APIToDatabaseService(DatabaseConnection)","u":"%3Cinit%3E(ntnu.systemutvikling.team6.database.DatabaseConnection)","k":"3"},{"p":"ntnu.systemutvikling.team6.service","c":"AuthenticationService","l":"AuthenticationService(UserSelect, UserDAO)","u":"%3Cinit%3E(ntnu.systemutvikling.team6.database.Readers.UserSelect,ntnu.systemutvikling.team6.database.DAO.UserDAO)","k":"3"},{"p":"ntnu.systemutvikling.team6.controller.components","c":"BaseController","l":"authToken","k":"1"},{"p":"ntnu.systemutvikling.team6.controller.components","c":"BaseController","l":"authTokenisSet()"},{"p":"ntnu.systemutvikling.team6.controller","c":"FrontpageController","l":"authTokenisSet()"},{"p":"ntnu.systemutvikling.team6.controller","c":"AvailableOrganizationController","l":"AvailableOrganizationController()","u":"%3Cinit%3E()","k":"3"},{"p":"ntnu.systemutvikling.team6.controller.components","c":"BaseController","l":"BaseController()","u":"%3Cinit%3E()","k":"3"},{"p":"ntnu.systemutvikling.team6.database.Readers","c":"CategorySelect","l":"CategorySelect(DatabaseConnection)","u":"%3Cinit%3E(ntnu.systemutvikling.team6.database.DatabaseConnection)","k":"3"},{"p":"ntnu.systemutvikling.team6.models.user","c":"Settings","l":"changeLanguage(Language)","u":"changeLanguage(ntnu.systemutvikling.team6.models.user.Language)"},{"p":"ntnu.systemutvikling.team6.models.user","c":"Role","l":"CHARITY_USER","k":"0"},{"p":"ntnu.systemutvikling.team6.models","c":"Charity","l":"Charity(String, String, Boolean, String)","u":"%3Cinit%3E(java.lang.String,java.lang.String,java.lang.Boolean,java.lang.String)","k":"3"},{"p":"ntnu.systemutvikling.team6.models","c":"Charity","l":"Charity(String, String, String, boolean, String)","u":"%3Cinit%3E(java.lang.String,java.lang.String,java.lang.String,boolean,java.lang.String)","k":"3"},{"p":"ntnu.systemutvikling.team6.models","c":"Charity","l":"Charity(String, String, String, String, String, boolean, String, String, String, byte[])","u":"%3Cinit%3E(java.lang.String,java.lang.String,java.lang.String,java.lang.String,java.lang.String,boolean,java.lang.String,java.lang.String,java.lang.String,byte[])","k":"3"},{"p":"ntnu.systemutvikling.team6.controller","c":"CharityPageController","l":"CharityPageController()","u":"%3Cinit%3E()","k":"3"},{"p":"ntnu.systemutvikling.team6.models.registry","c":"CharityRegistry","l":"CharityRegistry()","u":"%3Cinit%3E()","k":"3"},{"p":"ntnu.systemutvikling.team6.database.Readers","c":"CharitySelect","l":"CharitySelect(DatabaseConnection)","u":"%3Cinit%3E(ntnu.systemutvikling.team6.database.DatabaseConnection)","k":"3"},{"p":"ntnu.systemutvikling.team6.service","c":"CharityService","l":"CharityService()","u":"%3Cinit%3E()","k":"3"},{"p":"ntnu.systemutvikling.team6.scraper.scraperComponents","c":"APICharityScraper","l":"checkConnection()"},{"p":"ntnu.systemutvikling.team6.models.user","c":"User","l":"checkPassword(String)","u":"checkPassword(java.lang.String)"},{"p":"ntnu.systemutvikling.team6.scraper.scraperComponents","c":"URLCharityScraper","l":"closeDriver()"},{"p":"ntnu.systemutvikling.team6.scraper","c":"LogoDownloader","l":"convertBlobToPNG(byte[], String)","u":"convertBlobToPNG(byte[],java.lang.String)","k":"6"},{"p":"ntnu.systemutvikling.team6.database","c":"DatabaseSetup","l":"createTables()"},{"p":"ntnu.systemutvikling.team6.scraper.scraperComponents","c":"URLCharityScraper","l":"createWait()"},{"p":"ntnu.systemutvikling.team6.database","c":"DatabaseConnection","l":"DatabaseConnection()","u":"%3Cinit%3E()","k":"3"},{"p":"ntnu.systemutvikling.team6.database","c":"DatabaseConnection","l":"DatabaseConnection(String, String, String)","u":"%3Cinit%3E(java.lang.String,java.lang.String,java.lang.String)","k":"3"},{"p":"ntnu.systemutvikling.team6.database","c":"DatabaseSetup","l":"DatabaseSetup(DatabaseConnection)","u":"%3Cinit%3E(ntnu.systemutvikling.team6.database.DatabaseConnection)","k":"3"},{"p":"ntnu.systemutvikling.team6.controller","c":"DonationPageController","l":"Donate(ActionEvent)","u":"Donate(javafx.event.ActionEvent)"},{"p":"ntnu.systemutvikling.team6.models","c":"Donation","l":"Donation(double, LocalDate, Charity, User)","u":"%3Cinit%3E(double,java.time.LocalDate,ntnu.systemutvikling.team6.models.Charity,ntnu.systemutvikling.team6.models.user.User)","k":"3"},{"p":"ntnu.systemutvikling.team6.models","c":"Donation","l":"Donation(String, double, LocalDate, Charity, User, boolean)","u":"%3Cinit%3E(java.lang.String,double,java.time.LocalDate,ntnu.systemutvikling.team6.models.Charity,ntnu.systemutvikling.team6.models.user.User,boolean)","k":"3"},{"p":"ntnu.systemutvikling.team6.database.DAO","c":"DonationDAO","l":"DonationDAO(DatabaseConnection)","u":"%3Cinit%3E(ntnu.systemutvikling.team6.database.DatabaseConnection)","k":"3"},{"p":"ntnu.systemutvikling.team6.controller","c":"DonationPageController","l":"DonationPageController()","u":"%3Cinit%3E()","k":"3"},{"p":"ntnu.systemutvikling.team6.models.registry","c":"DonationRegistry","l":"DonationRegistry()","u":"%3Cinit%3E()","k":"3"},{"p":"ntnu.systemutvikling.team6.database.Readers","c":"DonationSelect","l":"DonationSelect(DatabaseConnection)","u":"%3Cinit%3E(ntnu.systemutvikling.team6.database.DatabaseConnection)","k":"3"},{"p":"ntnu.systemutvikling.team6.service","c":"DonationService","l":"DonationService()","u":"%3Cinit%3E()","k":"3"},{"p":"ntnu.systemutvikling.team6.scraper","c":"LogoDownloader","l":"downloadImageAsBlob(String)","u":"downloadImageAsBlob(java.lang.String)","k":"6"},{"p":"ntnu.systemutvikling.team6.models.user","c":"Language","l":"ENGLISH","k":"0"},{"p":"ntnu.systemutvikling.team6.models","c":"Feedback","l":"Feedback(String, User, String, LocalDate)","u":"%3Cinit%3E(java.lang.String,ntnu.systemutvikling.team6.models.user.User,java.lang.String,java.time.LocalDate)","k":"3"},{"p":"ntnu.systemutvikling.team6.models","c":"Feedback","l":"Feedback(User, String)","u":"%3Cinit%3E(ntnu.systemutvikling.team6.models.user.User,java.lang.String)","k":"3"},{"p":"ntnu.systemutvikling.team6.service","c":"FeedbackService","l":"FeedbackService()","u":"%3Cinit%3E()","k":"3"},{"p":"ntnu.systemutvikling.team6.models.registry","c":"CharityRegistry","l":"findCharityByOrgnumber(String)","u":"findCharityByOrgnumber(java.lang.String)"},{"p":"ntnu.systemutvikling.team6.models.registry","c":"CharityRegistry","l":"findCharityByUUID(UUID)","u":"findCharityByUUID(java.util.UUID)"},{"p":"ntnu.systemutvikling.team6.models.registry","c":"DonationRegistry","l":"findDonationById(UUID)","u":"findDonationById(java.util.UUID)"},{"p":"ntnu.systemutvikling.team6.scraper.scraperComponents","c":"URLCharityScraper","l":"findElement(By)","u":"findElement(org.openqa.selenium.By)"},{"p":"ntnu.systemutvikling.team6.scraper.scraperComponents","c":"URLCharityScraper","l":"findElements(By)","u":"findElements(org.openqa.selenium.By)"},{"p":"ntnu.systemutvikling.team6.models.user","c":"Inbox","l":"findMessageById(UUID)","u":"findMessageById(java.util.UUID)"},{"p":"ntnu.systemutvikling.team6.models.registry","c":"UserRegistry","l":"findUserById(UUID)","u":"findUserById(java.util.UUID)"},{"p":"ntnu.systemutvikling.team6.controller","c":"FrontpageController","l":"FrontpageController()","u":"%3Cinit%3E()","k":"3"},{"p":"ntnu.systemutvikling.team6.scraper","c":"FullCharityScrape","l":"FullCharityScrape()","u":"%3Cinit%3E()","k":"3"},{"p":"ntnu.systemutvikling.team6.models.registry","c":"CharityRegistry","l":"getAllCharities()"},{"p":"ntnu.systemutvikling.team6.models.registry","c":"DonationRegistry","l":"getAllDonations()"},{"p":"ntnu.systemutvikling.team6.models.registry","c":"UserRegistry","l":"getAllUsers()"},{"p":"ntnu.systemutvikling.team6.models","c":"Donation","l":"getAmount()"},{"p":"ntnu.systemutvikling.team6.scraper","c":"FullCharityScrape","l":"getAPIAndURLCharityData()"},{"p":"ntnu.systemutvikling.team6.scraper","c":"FullCharityScrape","l":"getAPIScraper()"},{"p":"ntnu.systemutvikling.team6.scraper.scraperComponents","c":"URLCharityScraper","l":"getCategories()"},{"p":"ntnu.systemutvikling.team6.database.Readers","c":"CategorySelect","l":"getCategoriesFromDB()"},{"p":"ntnu.systemutvikling.team6.models","c":"Charity","l":"getCategory()"},{"p":"ntnu.systemutvikling.team6.database.Readers","c":"CharitySelect","l":"getCharitiesFromDB()"},{"p":"ntnu.systemutvikling.team6.models","c":"Donation","l":"getCharity()"},{"p":"ntnu.systemutvikling.team6.models","c":"Donation","l":"getCharityId()"},{"p":"ntnu.systemutvikling.team6.models","c":"Feedback","l":"getComment()"},{"p":"ntnu.systemutvikling.team6.models.user","c":"Message","l":"getContent()"},{"p":"ntnu.systemutvikling.team6.service","c":"AuthenticationService","l":"getCurrentUser()"},{"p":"ntnu.systemutvikling.team6.models","c":"Donation","l":"getDate()"},{"p":"ntnu.systemutvikling.team6.models","c":"Feedback","l":"getDate()"},{"p":"ntnu.systemutvikling.team6.models","c":"Charity","l":"getDescription()"},{"p":"ntnu.systemutvikling.team6.scraper.scraperComponents","c":"URLCharityScraper","l":"getDescription()"},{"p":"ntnu.systemutvikling.team6.models.user","c":"User","l":"getDisplayName()"},{"p":"ntnu.systemutvikling.team6.database.Readers","c":"DonationSelect","l":"getDonationFromDB()"},{"p":"ntnu.systemutvikling.team6.models","c":"Donation","l":"getDonationID()"},{"p":"ntnu.systemutvikling.team6.models","c":"Donation","l":"getDonor()"},{"p":"ntnu.systemutvikling.team6.models.user","c":"User","l":"getEmail()"},{"p":"ntnu.systemutvikling.team6.database.Readers","c":"CharitySelect","l":"getFeedbackforCharityUUID(String)","u":"getFeedbackforCharityUUID(java.lang.String)"},{"p":"ntnu.systemutvikling.team6.models","c":"Feedback","l":"getFeedbackId()"},{"p":"ntnu.systemutvikling.team6.models","c":"Charity","l":"getFeedbacks()"},{"p":"ntnu.systemutvikling.team6.models.user","c":"Message","l":"getFrom()"},{"p":"ntnu.systemutvikling.team6.security","c":"PasswordHasher","l":"getHashPassword(String)","u":"getHashPassword(java.lang.String)"},{"p":"ntnu.systemutvikling.team6.models.user","c":"Message","l":"getId()"},{"p":"ntnu.systemutvikling.team6.models.user","c":"User","l":"getId()"},{"p":"ntnu.systemutvikling.team6.models.user","c":"User","l":"getInbox()"},{"p":"ntnu.systemutvikling.team6.database.Readers","c":"UserSelect","l":"getInboxForUser(String)","u":"getInboxForUser(java.lang.String)"},{"p":"ntnu.systemutvikling.team6.scraper.scraperComponents","c":"APICharityData","l":"getIs_pre_approved()"},{"p":"ntnu.systemutvikling.team6.scraper.scraperComponents","c":"APICharityScraper","l":"getJSONData()"},{"p":"ntnu.systemutvikling.team6.models","c":"Charity","l":"getKeyValues()"},{"p":"ntnu.systemutvikling.team6.scraper.scraperComponents","c":"URLCharityScraper","l":"getKeyValues()"},{"p":"ntnu.systemutvikling.team6.models.user","c":"Settings","l":"getLanguage()"},{"p":"ntnu.systemutvikling.team6.models","c":"Charity","l":"getLogoBlob()"},{"p":"ntnu.systemutvikling.team6.models","c":"Charity","l":"getLogoURL()"},{"p":"ntnu.systemutvikling.team6.scraper.scraperComponents","c":"URLCharityScraper","l":"getLogoURL()"},{"p":"ntnu.systemutvikling.team6.models.user","c":"Inbox","l":"getMessages()"},{"p":"ntnu.systemutvikling.team6.database","c":"DatabaseConnection","l":"getMySqlConnection()"},{"p":"ntnu.systemutvikling.team6.models","c":"Charity","l":"getName()"},{"p":"ntnu.systemutvikling.team6.scraper.scraperComponents","c":"APICharityData","l":"getName()"},{"p":"ntnu.systemutvikling.team6.models","c":"Charity","l":"getOrg_number()"},{"p":"ntnu.systemutvikling.team6.scraper.scraperComponents","c":"APICharityData","l":"getOrg_number()"},{"p":"ntnu.systemutvikling.team6.models.user","c":"User","l":"getPasswordHash()"},{"p":"ntnu.systemutvikling.team6.models","c":"Charity","l":"getPreApproved()"},{"p":"ntnu.systemutvikling.team6.models.user","c":"User","l":"getRole()"},{"p":"ntnu.systemutvikling.team6.models.user","c":"User","l":"getSettings()"},{"p":"ntnu.systemutvikling.team6.database.Readers","c":"UserSelect","l":"getSettingsForUser(String)","u":"getSettingsForUser(java.lang.String)"},{"p":"ntnu.systemutvikling.team6.models","c":"Charity","l":"getStatus()"},{"p":"ntnu.systemutvikling.team6.scraper.scraperComponents","c":"APICharityData","l":"getStatus()"},{"p":"ntnu.systemutvikling.team6.models.user","c":"Message","l":"getTimeAndDate()"},{"p":"ntnu.systemutvikling.team6.models.user","c":"Message","l":"getTitle()"},{"p":"ntnu.systemutvikling.team6.scraper.scraperComponents","c":"APICharityData","l":"getUrl()"},{"p":"ntnu.systemutvikling.team6.models","c":"Charity","l":"getURL()"},{"p":"ntnu.systemutvikling.team6.models","c":"Feedback","l":"getUser()"},{"p":"ntnu.systemutvikling.team6.database.Readers","c":"UserSelect","l":"getUserFromDBUsernameAndPassword(String, String)","u":"getUserFromDBUsernameAndPassword(java.lang.String,java.lang.String)"},{"p":"ntnu.systemutvikling.team6.database.Readers","c":"UserSelect","l":"getUserFromDBUuid(String)","u":"getUserFromDBUuid(java.lang.String)"},{"p":"ntnu.systemutvikling.team6.models.user","c":"User","l":"getUsername()"},{"p":"ntnu.systemutvikling.team6.database.Readers","c":"UserSelect","l":"getUsersFromDB()"},{"p":"ntnu.systemutvikling.team6.models","c":"Charity","l":"getUUID()"},{"p":"ntnu.systemutvikling.team6.controller","c":"FrontpageController","l":"handleCategoryFilterChange(ActionEvent)","u":"handleCategoryFilterChange(javafx.event.ActionEvent)"},{"p":"ntnu.systemutvikling.team6.controller","c":"FrontpageController","l":"handleFrontSearch(ActionEvent)","u":"handleFrontSearch(javafx.event.ActionEvent)"},{"p":"ntnu.systemutvikling.team6.controller","c":"CharityPageController","l":"handleHomepageClick(ActionEvent)","u":"handleHomepageClick(javafx.event.ActionEvent)"},{"p":"ntnu.systemutvikling.team6.controller","c":"CharityPageController","l":"handleSearch(ActionEvent)","u":"handleSearch(javafx.event.ActionEvent)"},{"p":"ntnu.systemutvikling.team6.controller.components","c":"NavbarFooterController","l":"handleSearch(ActionEvent)","u":"handleSearch(javafx.event.ActionEvent)"},{"p":"ntnu.systemutvikling.team6.controller","c":"DonationPageController","l":"handleSearch(ActionEvent)","u":"handleSearch(javafx.event.ActionEvent)"},{"p":"ntnu.systemutvikling.team6","c":"HmHApplication","l":"HmHApplication()","u":"%3Cinit%3E()","k":"3"},{"p":"ntnu.systemutvikling.team6.models.user","c":"Inbox","l":"Inbox()","u":"%3Cinit%3E()","k":"3"},{"p":"ntnu.systemutvikling.team6","c":"HmHApplication","l":"init()"},{"p":"ntnu.systemutvikling.team6.controller","c":"AvailableOrganizationController","l":"initialize()"},{"p":"ntnu.systemutvikling.team6.controller","c":"CharityPageController","l":"initialize()"},{"p":"ntnu.systemutvikling.team6.controller","c":"FrontpageController","l":"initialize()"},{"p":"ntnu.systemutvikling.team6.models","c":"Donation","l":"isAnonymous()"},{"p":"ntnu.systemutvikling.team6.models","c":"Feedback","l":"isAnonymous()"},{"p":"ntnu.systemutvikling.team6.models.user","c":"Settings","l":"isAnonymous()"},{"p":"ntnu.systemutvikling.team6.models.user","c":"Settings","l":"isLightMode()"},{"p":"ntnu.systemutvikling.team6.controller.components","c":"BaseController","l":"isLoggedin()"},{"p":"ntnu.systemutvikling.team6.service","c":"AuthenticationService","l":"isLoggedin()"},{"p":"ntnu.systemutvikling.team6.database.Readers","c":"UserSelect","l":"isUsernameTaken(String)","u":"isUsernameTaken(java.lang.String)"},{"p":"ntnu.systemutvikling.team6.security","c":"PasswordHasher","l":"isValidPassword(String, String)","u":"isValidPassword(java.lang.String,java.lang.String)"},{"p":"ntnu.systemutvikling.team6.controller.components","c":"LoaderScene","l":"LoaderScene()","u":"%3Cinit%3E()","k":"3"},{"p":"ntnu.systemutvikling.team6.controller.components","c":"LoaderScene","l":"LoadScene(String, ActionEvent, Charity, String)","u":"LoadScene(java.lang.String,javafx.event.ActionEvent,ntnu.systemutvikling.team6.models.Charity,java.lang.String)","k":"6"},{"p":"ntnu.systemutvikling.team6.service","c":"AuthenticationService","l":"login(String, String)","u":"login(java.lang.String,java.lang.String)"},{"p":"ntnu.systemutvikling.team6.scraper","c":"LogoDownloader","l":"LogoDownloader()","u":"%3Cinit%3E()","k":"3"},{"p":"ntnu.systemutvikling.team6.service","c":"AuthenticationService","l":"logout()"},{"p":"ntnu.systemutvikling.team6","c":"Main","l":"Main()","u":"%3Cinit%3E()","k":"3"},{"p":"ntnu.systemutvikling.team6","c":"HmHApplication","l":"main(String[])","u":"main(java.lang.String[])","k":"6"},{"p":"ntnu.systemutvikling.team6","c":"Main","l":"main(String[])","u":"main(java.lang.String[])","k":"6"},{"p":"ntnu.systemutvikling.team6.models.user","c":"Message","l":"Message(String, UUID, String)","u":"%3Cinit%3E(java.lang.String,java.util.UUID,java.lang.String)","k":"3"},{"p":"ntnu.systemutvikling.team6.models.user","c":"Message","l":"Message(String, UUID, String, LocalDate)","u":"%3Cinit%3E(java.lang.String,java.util.UUID,java.lang.String,java.time.LocalDate)","k":"3"},{"p":"ntnu.systemutvikling.team6.models.user","c":"Role","l":"NORMAL_USER","k":"0"},{"p":"ntnu.systemutvikling.team6.models.user","c":"Language","l":"NORWEGIAN","k":"0"},{"p":"ntnu.systemutvikling.team6.controller.components","c":"OrganizationCardController","l":"OrganizationCardController()","u":"%3Cinit%3E()","k":"3"},{"p":"ntnu.systemutvikling.team6.scraper.scraperComponents","c":"APICharityScraper","l":"parseJSON(String)","u":"parseJSON(java.lang.String)"},{"p":"ntnu.systemutvikling.team6.security","c":"PasswordHasher","l":"PasswordHasher()","u":"%3Cinit%3E()","k":"3"},{"p":"ntnu.systemutvikling.team6.controller","c":"DonationPageController","l":"processDonation(Charity, User, double)","u":"processDonation(ntnu.systemutvikling.team6.models.Charity,ntnu.systemutvikling.team6.models.user.User,double)"},{"p":"ntnu.systemutvikling.team6.service","c":"AuthenticationService","l":"register(String, String, String, String)","u":"register(java.lang.String,java.lang.String,java.lang.String,java.lang.String)"},{"p":"ntnu.systemutvikling.team6.database.DAO","c":"UserDAO","l":"registerUser(User)","u":"registerUser(ntnu.systemutvikling.team6.models.user.User)"},{"p":"ntnu.systemutvikling.team6.models.registry","c":"CharityRegistry","l":"removeCharity(String)","u":"removeCharity(java.lang.String)"},{"p":"ntnu.systemutvikling.team6.models.registry","c":"CharityRegistry","l":"removeCharityUUID(UUID)","u":"removeCharityUUID(java.util.UUID)"},{"p":"ntnu.systemutvikling.team6.models.registry","c":"DonationRegistry","l":"removeDonation(UUID)","u":"removeDonation(java.util.UUID)"},{"p":"ntnu.systemutvikling.team6.models.user","c":"Inbox","l":"removeMessage(UUID)","u":"removeMessage(java.util.UUID)"},{"p":"ntnu.systemutvikling.team6.models.registry","c":"UserRegistry","l":"removeUserByUUID(UUID)","u":"removeUserByUUID(java.util.UUID)"},{"p":"ntnu.systemutvikling.team6.scraper.scraperComponents","c":"URLCharityScraper","l":"scrapeCharityPage()"},{"p":"ntnu.systemutvikling.team6.controller.components","c":"BaseController","l":"setAuthToken(AuthenticationService)","u":"setAuthToken(ntnu.systemutvikling.team6.service.AuthenticationService)"},{"p":"ntnu.systemutvikling.team6.controller","c":"CharityPageController","l":"setCategories(List)","u":"setCategories(java.util.List)"},{"p":"ntnu.systemutvikling.team6.models","c":"Charity","l":"setCategory(List)","u":"setCategory(java.util.List)"},{"p":"ntnu.systemutvikling.team6.controller","c":"CharityPageController","l":"setCharity(Charity)","u":"setCharity(ntnu.systemutvikling.team6.models.Charity)"},{"p":"ntnu.systemutvikling.team6.controller","c":"DonationPageController","l":"setCharity(Charity)","u":"setCharity(ntnu.systemutvikling.team6.models.Charity)"},{"p":"ntnu.systemutvikling.team6.models","c":"Charity","l":"setDescription(String)","u":"setDescription(java.lang.String)"},{"p":"ntnu.systemutvikling.team6.models.user","c":"User","l":"setEmail(String)","u":"setEmail(java.lang.String)"},{"p":"ntnu.systemutvikling.team6.models","c":"Charity","l":"setFeedbacks(ArrayList)","u":"setFeedbacks(java.util.ArrayList)"},{"p":"ntnu.systemutvikling.team6.models.user","c":"User","l":"setInbox(Inbox)","u":"setInbox(ntnu.systemutvikling.team6.models.user.Inbox)"},{"p":"ntnu.systemutvikling.team6.controller","c":"AvailableOrganizationController","l":"setInitialSearch(String)","u":"setInitialSearch(java.lang.String)"},{"p":"ntnu.systemutvikling.team6.models","c":"Charity","l":"setKeyValues(String)","u":"setKeyValues(java.lang.String)"},{"p":"ntnu.systemutvikling.team6.models","c":"Charity","l":"setLogoBlob(byte[])"},{"p":"ntnu.systemutvikling.team6.models","c":"Charity","l":"setLogoURL(String)","u":"setLogoURL(java.lang.String)"},{"p":"ntnu.systemutvikling.team6.controller.components","c":"OrganizationCardController","l":"setOrganization(Charity)","u":"setOrganization(ntnu.systemutvikling.team6.models.Charity)"},{"p":"ntnu.systemutvikling.team6.models.user","c":"User","l":"setPassword(String)","u":"setPassword(java.lang.String)"},{"p":"ntnu.systemutvikling.team6.models.user","c":"User","l":"setSettings(Settings)","u":"setSettings(ntnu.systemutvikling.team6.models.user.Settings)"},{"p":"ntnu.systemutvikling.team6.models.user","c":"Settings","l":"Settings()","u":"%3Cinit%3E()","k":"3"},{"p":"ntnu.systemutvikling.team6.models.user","c":"Settings","l":"Settings(boolean, Language, boolean)","u":"%3Cinit%3E(boolean,ntnu.systemutvikling.team6.models.user.Language,boolean)","k":"3"},{"p":"ntnu.systemutvikling.team6.models","c":"Charity","l":"setUnverified()"},{"p":"ntnu.systemutvikling.team6.models.user","c":"User","l":"setUsername(String)","u":"setUsername(java.lang.String)"},{"p":"ntnu.systemutvikling.team6.models","c":"Charity","l":"setUUIDFromString(String)","u":"setUUIDFromString(java.lang.String)"},{"p":"ntnu.systemutvikling.team6.models","c":"Charity","l":"setVerified()"},{"p":"ntnu.systemutvikling.team6","c":"HmHApplication","l":"start(Stage)","u":"start(javafx.stage.Stage)"},{"p":"ntnu.systemutvikling.team6.controller.components","c":"NavbarFooterController","l":"switchToAboutPage(ActionEvent)","u":"switchToAboutPage(javafx.event.ActionEvent)"},{"p":"ntnu.systemutvikling.team6.controller","c":"AvailableOrganizationController","l":"switchToCharityPage(ActionEvent)","u":"switchToCharityPage(javafx.event.ActionEvent)"},{"p":"ntnu.systemutvikling.team6.controller.components","c":"OrganizationCardController","l":"switchToCharityPage(ActionEvent)","u":"switchToCharityPage(javafx.event.ActionEvent)"},{"p":"ntnu.systemutvikling.team6.controller","c":"DonationPageController","l":"switchToCharityPage(ActionEvent)","u":"switchToCharityPage(javafx.event.ActionEvent)"},{"p":"ntnu.systemutvikling.team6.controller","c":"FrontpageController","l":"switchToCharityPage(ActionEvent)","u":"switchToCharityPage(javafx.event.ActionEvent)"},{"p":"ntnu.systemutvikling.team6.controller","c":"AvailableOrganizationController","l":"switchToDonationPage(ActionEvent)","u":"switchToDonationPage(javafx.event.ActionEvent)"},{"p":"ntnu.systemutvikling.team6.controller","c":"CharityPageController","l":"switchToDonationPage(ActionEvent)","u":"switchToDonationPage(javafx.event.ActionEvent)"},{"p":"ntnu.systemutvikling.team6.controller.components","c":"OrganizationCardController","l":"switchToDonationPage(ActionEvent)","u":"switchToDonationPage(javafx.event.ActionEvent)"},{"p":"ntnu.systemutvikling.team6.controller","c":"FrontpageController","l":"switchToDonationPage(ActionEvent)","u":"switchToDonationPage(javafx.event.ActionEvent)"},{"p":"ntnu.systemutvikling.team6.controller","c":"CharityPageController","l":"switchToFrontPage(ActionEvent)","u":"switchToFrontPage(javafx.event.ActionEvent)"},{"p":"ntnu.systemutvikling.team6.controller.components","c":"NavbarFooterController","l":"switchToFrontPage(ActionEvent)","u":"switchToFrontPage(javafx.event.ActionEvent)"},{"p":"ntnu.systemutvikling.team6.controller","c":"DonationPageController","l":"switchToFrontPage(ActionEvent)","u":"switchToFrontPage(javafx.event.ActionEvent)"},{"p":"ntnu.systemutvikling.team6.controller.components","c":"NavbarFooterController","l":"switchToLoginPage(ActionEvent)","u":"switchToLoginPage(javafx.event.ActionEvent)"},{"p":"ntnu.systemutvikling.team6.controller.components","c":"NavbarFooterController","l":"switchToProfilePage(ActionEvent)","u":"switchToProfilePage(javafx.event.ActionEvent)"},{"p":"ntnu.systemutvikling.team6.database","c":"DatabaseSetup","l":"testConnection()"},{"p":"ntnu.systemutvikling.team6.models.user","c":"Settings","l":"toggleAnonymousMode()"},{"p":"ntnu.systemutvikling.team6.models.user","c":"Settings","l":"toggleLightMode()"},{"p":"ntnu.systemutvikling.team6.scraper.scraperComponents","c":"URLCharityScraper","l":"updateDescription()"},{"p":"ntnu.systemutvikling.team6.scraper.scraperComponents","c":"URLCharityScraper","l":"URLCharityScraper(String)","u":"%3Cinit%3E(java.lang.String)","k":"3"},{"p":"ntnu.systemutvikling.team6.scraper.scraperComponents","c":"URLCharityScraper","l":"URLCharityScraper(String, WebDriver)","u":"%3Cinit%3E(java.lang.String,org.openqa.selenium.WebDriver)","k":"3"},{"p":"ntnu.systemutvikling.team6.models.user","c":"User","l":"User(String, String, String, String, Role, Settings, Inbox)","u":"%3Cinit%3E(java.lang.String,java.lang.String,java.lang.String,java.lang.String,ntnu.systemutvikling.team6.models.user.Role,ntnu.systemutvikling.team6.models.user.Settings,ntnu.systemutvikling.team6.models.user.Inbox)","k":"3"},{"p":"ntnu.systemutvikling.team6.models.user","c":"User","l":"User(String, String, String, String, String, String)","u":"%3Cinit%3E(java.lang.String,java.lang.String,java.lang.String,java.lang.String,java.lang.String,java.lang.String)","k":"3"},{"p":"ntnu.systemutvikling.team6.database.DAO","c":"UserDAO","l":"UserDAO(DatabaseConnection)","u":"%3Cinit%3E(ntnu.systemutvikling.team6.database.DatabaseConnection)","k":"3"},{"p":"ntnu.systemutvikling.team6.models.registry","c":"UserRegistry","l":"UserRegistry()","u":"%3Cinit%3E()","k":"3"},{"p":"ntnu.systemutvikling.team6.database.Readers","c":"UserSelect","l":"UserSelect(DatabaseConnection)","u":"%3Cinit%3E(ntnu.systemutvikling.team6.database.DatabaseConnection)","k":"3"},{"p":"ntnu.systemutvikling.team6.models.user","c":"Language","l":"valueOf(String)","u":"valueOf(java.lang.String)","k":"6"},{"p":"ntnu.systemutvikling.team6.models.user","c":"Role","l":"valueOf(String)","u":"valueOf(java.lang.String)","k":"6"},{"p":"ntnu.systemutvikling.team6.models.user","c":"Language","l":"values()","k":"6"},{"p":"ntnu.systemutvikling.team6.models.user","c":"Role","l":"values()","k":"6"}];updateSearchResults();
\ No newline at end of file
diff --git a/docs/Javadoc/apidocs/module-search-index.js b/docs/Javadoc/apidocs/module-search-index.js
new file mode 100644
index 00000000..0d59754f
--- /dev/null
+++ b/docs/Javadoc/apidocs/module-search-index.js
@@ -0,0 +1 @@
+moduleSearchIndex = [];updateSearchResults();
\ No newline at end of file
diff --git a/docs/Javadoc/apidocs/ntnu/systemutvikling/team6/HmHApplication.html b/docs/Javadoc/apidocs/ntnu/systemutvikling/team6/HmHApplication.html
new file mode 100644
index 00000000..42309199
--- /dev/null
+++ b/docs/Javadoc/apidocs/ntnu/systemutvikling/team6/HmHApplication.html
@@ -0,0 +1,230 @@
+
+
+
+
+HmHApplication (helpmehelpapplication 1.0-SNAPSHOT API)
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
This controller represents the available organization page, where the user can search for a
+charity and choose to donate to it. It also has a button to return to the front page. The user
+can search for a charity by typing in the search field, and the charities that match the search
+query will be displayed. The user can click on a charity to see more details about it, or click
+on the featured charity to see more details about it. The user can also switch to the charity
+page or donation page for the selected charity.
This method is used to initialize the available organization page. It retrieves all charities
+from the database and sets up a listener on the search field to filter the charities based on
+the user's input. It also clears the cards container to prepare for displaying the filtered
+charities. The method is called automatically when the page is loaded, and it sets up the
+initial state of the page.
public class CharityPageController
+extends Object
+
This controller represents the charity page, where the user can read about the charity and choose
+to donate to it. It also has a button to return to the front page.
This method is used to set the charity that is being displayed on the page. It also updates the
+labels with the charity's name and description. It acts like an initializer for the charity
+page, since the charity is not known until the user clicks on a charity from the front page.
+Param charity is the charity that is being displayed on the page, AND is called on from the
+front page when the user clicks on a charity, to set the charity that is being displayed on the
+page.
public class DonationPageController
+extends Object
+
This controller represents the donation page, where the user can enter a donation amount and
+confirm their donation to the charity. It also has a button to return to the front page.
Initialize method for the donation page. Sets the charity name label to the name of the charity
+that is being donated to. The charity is set from the original page it was called from when the
+user clicks on the donate button, and is passed as a parameter to this method.
This method is used to switch back to the Donation page when the user clicks the back button.
+
+
Parameters:
+
event -
+
+
+
+
+
+
+
Donate
+
+
publicvoidDonate(javafx.event.ActionEvent event)
+
This method is used to process the donation when the user clicks the donate button. Checks if
+the input is valid and displays appropriate error messages if it is not. If the input is valid,
+it shows a confirmation dialog to the user. Shows a confirmation dialog. If the user confirms,
+the donation is processed and the user is taken back to the front page.
+
+
Parameters:
+
event -
+
+
+
+
+
+
+
processDonation
+
+
publicvoidprocessDonation(Charity charity,
+ User user,
+ double amount)
+
Invoke DAO object to add the donation to the database. This method is called from the Donate
+method after the user confirms their donation.
Landing page's controller. This is the first page the user sees when they open the application.
+It displays a random featured charity, some statistics about the charities and donations, and a
+list of all charities in the database. The user can click on a charity to see more details about
+it, or click on the featured charity to see more details about it. It also has buttons to switch
+to the charity page and the donation page for the featured charity
Initialize method for the front page. This method is called when the front page is loaded. It
+retrieves the list of charities and donations from the database. The list of charities is
+displayed as a list of cards, where each card represents a charity from the
+Innsamlingskontrollen. A random charity is selected to be featured on the page, and its name
+and description are displayed in the carousel section. The total number of charities, total
+amount of donations, and percentage of pre-approved charities are also displayed on the page.
This class is a utility class that is used to load different scenes in the application. For now,
+It is used to switch between the front page, charity page, and donation page. It takes care of
+loading the FXML file, setting the controller, and switching the scene.
When going to a new scene, this method is called to load the new scene. It takes the name of
+the scene to load, the event that triggered the scene change, and the charity that is being
+passed to the new scene (if applicable). It loads the FXML file for the new scene, sets the
+controller, and switches the scene.
public class OrganizationCardController
+extends Object
+
This controller represents the organization card that is displayed on the front page and is
+looped upon in FronpageController. It is used to display the name and description of a charity,
+and to switch to the charity page or donation page when the user clicks on the card.
This class is responsible for sending concurrent information about the donation to the Donation
+Database. Usally called from the DonationPageController, where the user confirms their donation.
Retrieves all charities from the database, including their associated feedback and the users
+who submitted each piece of feedback.
+
+
The query performs a LEFT JOIN between the Charities, Feedback,
+User, CharityVanity, and category(s) tables. Each unique charity is added once
+to the registry; any feedback rows found for that charity are appended to its feedback list.
+
+
Note: charities with no feedback and categories are still included in the result due to the
+LEFT JOIN.
+
+
Returns:
+
a CharityRegistry containing all charities found in the database, each
+ populated with its associated Feedback objects (if any)
A helper function that retrieves all feedback entries associated with a specific charity,
+identified by its UUID. Currently, has no use.
+
+
Each Feedback object is populated with the associated User (without settings
+or inbox data). The query uses a LEFT JOIN between the Feedback and User
+tables, filtered by charity_id.
+
+
Parameters:
+
charity_uuid - the UUID of the charity whose feedback should be retrieved; must not be
+ null
+
Returns:
+
an ArrayList of Feedback objects for the given charity; returns an
+ empty list if no feedback exists for that charity
+
Throws:
+
RuntimeException - if any exception occurs while executing the query, wrapping the
+ original cause
Data access class responsible for reading donation data from the database.
+
+
Retrieves donations along with their associated Charity by performing an INNER JOIN
+between the Donations and Charities tables. Only donations with a matching
+charity record are included. Donor (User) and CharityVanity details are
+intentionally excluded to keep this query lightweight — join those separately if richer data is
+needed.
+
+
Note: CharityVanity fields (name, link, description, logo) are NOT fetched here since
+they live in a separate table. The Charity objects returned will only contain the core
+fields present in the Charities table.
Retrieves all donations from the database, each populated with its associated Charity.
+
+
The query performs an INNER JOIN between the Donations and Charities tables
+on the charity UUID foreign key. Donations without a matching charity are excluded from the
+result.
+
+
Returns:
+
a DonationRegistry containing all matched donations; never null, but
+ may be empty if no rows are returned
Data access class responsible for reading user-related data from the database.
+
+
Provides methods to retrieve individual users (by credentials or UUID), all users, a user's
+settings, and a user's inbox. Queries use LEFT JOINs across the User, Settings,
+and Messages tables to assemble fully populated User objects in a single round
+trip where possible.
Retrieves a single User from the database matching the given username and password.
+
+
The password is hashed via PasswordHasher before being compared against the stored
+value. If a matching user is found, their Settings (when present) and Inbox
+(including any Message objects) are also populated. Returns null if no matching
+user is found.
+
+
Note: the current SQL query compares both parameters against
+user_password; the user_name column is not yet included in the WHERE clause, which may
+be a bug.
+
+
Parameters:
+
username - the plain-text username to look up
+
password - the plain-text password; hashed internally before the query runs
+
Returns:
+
the matching User with settings and inbox populated, or null if no
+ match is found
Retrieves a single User from the database by their UUID.
+
+
The returned user is fully populated with Settings (when present) and an Inbox containing any associated Message objects. Returns null if no user with
+the given UUID exists.
+
+
Parameters:
+
user_id - the UUID string of the user to retrieve; must not be null
+
Returns:
+
the matching User with settings and inbox populated, or null if no user
+ is found
Retrieves all users from the database, each fully populated with their Settings and
+Inbox.
+
+
The query LEFT JOINs User, Settings, and Messages. Multiple rows for
+the same user UUID (due to multiple messages) are collapsed into a single User object
+with all messages appended to its inbox.
+
+
Returns:
+
a UserRegistry containing all users found in the database; never null,
+ but may be empty if no users exist
Minimal contructor JUST FOR DONATIONSSELECT. Just cause donation object needs to only contain
+information about receiver Chairty and donator User, and not necessarily Urls,
+logos, and etc.
+
+
Parameters:
+
uuid - from DonationSelect
+
org_number - matches from DonationSelect
+
is_pre_approved - name matches from DonationSelect
Contructor for creating a new charity. Taylored to match data given from DATABASE. Expects
+paramaters that will fill all attributes. EXECPT for feedbacks and categories (which is done
+right after).
+
+
Parameters:
+
org_number - matches from innsamlingkontrollen
+
name - matches from innsamlingkontrollen
+
status - name matches from innsamlingkontrollen
+
is_pre_approved - name matches from innsamlingkontrollen
Constructor for creating a new donation. The charityId is generated automatically using UUID.
+If the donation is made anonymously, the isAnonymous parameter is set to true.
This method is used to verify the integrity of the data in the charities table and to
+update it based on the data retrieved from the IK API and the charity's URL.
Returns the enum constant of this class with the specified name.
+The string must match exactly an identifier used to declare an
+enum constant in this class. (Extraneous whitespace characters are
+not permitted.)
+
+
Parameters:
+
name - the name of the enum constant to be returned.
Creates a message with a unique identifier. The message includes a title, a string who it's
+from, content and the time and date. This one creates a message that has been created before.
Returns the enum constant of this class with the specified name.
+The string must match exactly an identifier used to declare an
+enum constant in this class. (Extraneous whitespace characters are
+not permitted.)
+
+
Parameters:
+
name - the name of the enum constant to be returned.
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
+
+
Parameters:
+
uuid - gives the user a unique identifier with UUID
Orchestrates a full charity data scrape by combining two data sources:
+
+
+
The external charity API (via APICharityScraper), which provides structured data
+ such as organisation numbers, approval status, and charity URLs.
+
Individual charity web pages (via URLCharityScraper), which provide richer
+ presentation data such as descriptions, logos, categories, and key values.
+
Phase 2 — URL scrape: Iterates over every Charity in the registry and uses a
+URLCharityScraper to enrich each entry with its description, logo URL, logo blob,
+categories, and key values scraped from the charity's own web page.
+
+
If APICharityScraper.checkConnection() throws an exception, it propagates to the
+caller and null is returned. If the connection check passes but returns false,
+null is also returned.
+
+
Returns:
+
a fully populated CharityRegistry, or null if the API is unreachable
+
Throws:
+
IOException - if an I/O error occurs during the API request or URL scraping
Orchestrates a full charity data scrape by combining two data sources:
+
+
+ The external charity API (via APICharityScraper), which provides structured data
+ such as organisation numbers, approval status, and charity URLs.
Constructs a new APICharityData object. This class represents the data provided from the IK
+Api, and is used expand and provide data to our design of a charity.
+
+
Parameters:
+
org_number - a unique number that identifies the organization
+
name - the name of the organization
+
status - approved for approved organizations, obs for non-approved
+ organizations
+
url - the URL for more info about the organization on the IK domain
+
is_pre_approved - whether the organization was pre-approved
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.
This method is used to verify the integrity of the data in the charities table and to
+update it based on the data retrieved from the IK API and the charity's URL.
This method is used to verify the integrity of the data in the charities table and to
+update it based on the data retrieved from the IK API and the charity's URL. The param
+charities are retrieved from the IK API through the APICharityData class. Called in initialize
+method in HmHApplication.java, which is the main class of the application, to ensure that the
+data is up to date when the application starts. Uses a temp table to ensure that the data in
+the database is consistent with the data from the API.
+
+
Uses a URLScraper object to get data not contained in the API, and static methods from
+LogoDownloader to get the charity's logo as a blob.
+
+
Parameters:
+
charities - a list of Charity objects to add to the database
Registers a new user account with the provided details.
+
+
The new user is assigned the Role.NORMAL_USER role and default Settings and
+Inbox. Registration will fail if the username is already taken or if the database
+operation is unsuccessful. On success, the new user is set as the current user.
+
+
Parameters:
+
displayName - the display name shown on the user's profile
+
username - the unique username for the new account
+
email - the email address associated with the new account
+
password - the password for the new account
+
Returns:
+
true if registration was successful; false if the username is already
+ taken or the database operation failed
+
+
+
+
+
+
+
logout
+
+
publicvoidlogout()
+
Logs out the currently authenticated user by clearing the current user state.
All queries are executed against a MySQL database via a {@link DatabaseConnection}.
+ */
+public class CategoryDAO {
+ private final DatabaseConnection connection;
+
+ /**
+ * Constructs a new {@code CategoryDAO} with the given database connection.
+ *
+ * @param connection the {@link DatabaseConnection} to use for executing queries; must not be
+ * {@code null}
+ */
+ public CategoryDAO(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/DAO/CharityDAO.java b/helpmehelpapplication/src/main/java/ntnu/systemutvikling/team6/database/DAO/CharityDAO.java
new file mode 100644
index 00000000..85393a8a
--- /dev/null
+++ b/helpmehelpapplication/src/main/java/ntnu/systemutvikling/team6/database/DAO/CharityDAO.java
@@ -0,0 +1,214 @@
+package ntnu.systemutvikling.team6.database.DAO;
+
+import java.sql.*;
+import java.time.LocalDate;
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.Set;
+import ntnu.systemutvikling.team6.database.DatabaseConnection;
+import ntnu.systemutvikling.team6.models.Charity;
+import ntnu.systemutvikling.team6.models.Feedback;
+import ntnu.systemutvikling.team6.models.registry.CharityRegistry;
+import ntnu.systemutvikling.team6.models.user.Language;
+import ntnu.systemutvikling.team6.models.user.Settings;
+import ntnu.systemutvikling.team6.models.user.User;
+
+/**
+ * Data access class responsible for acsessing charity-related data from the database.
+ *
+ *
Primarily provides read methods to retrieve all charities (with their associated feedback and
+ * users) as well as feedback entries for a specific charity by UUID.
+ *
+ *
All queries are executed against a MySQL database via a {@link DatabaseConnection}.
+ */
+public class CharityDAO {
+ 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 CharityDAO(DatabaseConnection connection) {
+ this.connection = connection;
+ }
+
+ /**
+ * Retrieves all charities from the database, including their associated feedback and the users
+ * who submitted each piece of feedback.
+ *
+ *
The query performs a LEFT JOIN between the {@code Charities}, {@code Feedback}, {@code User}
+ * and {@code category(s)} tables and a INNER JOIN with {@code CharityVanity} table. Each unique
+ * charity is added once to the registry; any feedback rows found for that charity are appended to
+ * its feedback list.
+ *
+ *
Note: charities with no feedback and categories are still included in the result due to the
+ * LEFT JOIN.
+ *
+ * @return a {@link CharityRegistry} containing all charities found in the database, each
+ * populated with its associated {@link Feedback} objects (if any)
+ * @throws RuntimeException if a {@link SQLException} occurs while executing the query
+ */
+ public CharityRegistry getCharitiesFromDB() {
+ CharityRegistry registry = null;
+ Connection conn = null;
+
+ try {
+ conn = connection.getMySqlConnection();
+
+ String sql_query =
+ """
+ SELECT
+ c.UUID_charities, c.org_number, c.pre_approved, c.status,
+ f.UUID_feedback, f.feedback_comment, f.feedback_date, f.isAnonymous, f.charity_id, f.user_id,
+ cv.charity_name, cv.charity_link, cv.description, cv.logoURL, cv.key_values, cv.logoBLOB,
+ u.UUID_user, u.user_name, u.user_email, u.user_password, u.role,
+ cat.category
+ FROM Charities c
+ LEFT JOIN Feedback f ON f.charity_id = c.UUID_charities
+ LEFT JOIN User u ON f.user_id = u.UUID_user
+ LEFT JOIN Charity_Categories cc ON cc.Charities_UUID_charities = c.UUID_charities
+ LEFT JOIN 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);
+
+ Charity currentCharity = null;
+ String lastCharity = null;
+
+ Set seenFeedbackIds = new HashSet<>();
+
+ registry = new CharityRegistry();
+
+ while (rs.next()) {
+ String currentId = rs.getString("UUID_charities");
+
+ if (lastCharity == null || !currentId.equals(lastCharity)) {
+
+ currentCharity =
+ new Charity(
+ rs.getString("UUID_charities"),
+ rs.getString("org_number"),
+ rs.getString("charity_name"),
+ rs.getString("charity_link"),
+ rs.getString("status"),
+ rs.getBoolean("pre_approved"),
+ rs.getString("description"),
+ rs.getString("logoURL"),
+ rs.getString("key_values"),
+ rs.getBytes("logoBLOB"));
+
+ registry.addCharity(currentCharity);
+ lastCharity = currentId;
+ seenFeedbackIds.clear();
+ }
+
+ String categoryName = rs.getString("category");
+ if (categoryName != null & !currentCharity.getCategory().contains(categoryName)) {
+ currentCharity.getCategory().add(categoryName);
+ }
+
+ String feedbackId = rs.getString("UUID_feedback");
+
+ if (feedbackId != null && !seenFeedbackIds.contains(feedbackId)) {
+ seenFeedbackIds.add(feedbackId);
+
+ User userWithMinimalSettingsAndInbox =
+ new User(
+ rs.getString("UUID_User"),
+ rs.getString("user_name"),
+ rs.getString("user_email"),
+ rs.getString("user_password"),
+ rs.getString("role"));
+
+ userWithMinimalSettingsAndInbox.setSettings(new Settings(false, Language.ENGLISH, false));
+
+ Feedback feedback =
+ new Feedback(
+ rs.getString("UUID_feedback"),
+ userWithMinimalSettingsAndInbox,
+ rs.getString("feedback_comment"),
+ LocalDate.parse(rs.getString("feedback_date")));
+
+ currentCharity.getFeedbacks().add(feedback);
+ }
+ }
+
+ } catch (SQLException e) {
+ e.printStackTrace();
+ throw new RuntimeException("ERROR: Something went wrong during updating charities table.");
+ } finally {
+ conn = null;
+ }
+
+ return registry;
+ }
+
+ /**
+ * A helper function that retrieves all feedback entries associated with a specific charity,
+ * identified by its UUID. Currently, has no use.
+ *
+ *
Each {@link Feedback} object is populated with the associated {@link User} (without settings
+ * or inbox data). The query uses a LEFT JOIN between the {@code Feedback} and {@code User}
+ * tables, filtered by {@code charity_id}.
+ *
+ * @param charity_uuid the UUID of the charity whose feedback should be retrieved; must not be
+ * {@code null}
+ * @return an {@link ArrayList} of {@link Feedback} objects for the given charity; returns an
+ * empty list if no feedback exists for that charity
+ * @throws RuntimeException if any exception occurs while executing the query, wrapping the
+ * original cause
+ */
+ public ArrayList getFeedbackforCharityUUID(String charity_uuid) {
+ ArrayList Feedbacks = new ArrayList<>();
+ Connection conn = null;
+ try {
+ conn = connection.getMySqlConnection();
+ String sql_query =
+ """
+ SELECT
+ f.UUID_feedback, f.feedback_comment, f.feedback_date, f.isAnonymous, f.charity_id, f.user_id,
+ u.UUID_user, u.user_name, u.user_email, u.user_password, u.role,
+ s.language, s.lightmode
+ FROM Feedback f
+ LEFT JOIN User u ON f.user_id = u.UUID_user
+ RIGHT JOIN Settings s ON u.UUID_user = s.UUID_user
+ WHERE f.charity_id = ?;
+ """;
+ PreparedStatement stmt = conn.prepareStatement(sql_query);
+ stmt.setString(1, charity_uuid);
+ ResultSet rs = stmt.executeQuery();
+
+ while (rs.next()) {
+ User userWithSettingsAndNoInbox =
+ new User(
+ rs.getString("UUID_User"),
+ rs.getString("user_name"),
+ rs.getString("user_email"),
+ rs.getString("user_password"),
+ rs.getString("role"));
+ userWithSettingsAndNoInbox.setSettings(
+ new Settings(
+ rs.getBoolean("isAnonymous"),
+ Language.valueOf(rs.getString("language")),
+ rs.getBoolean("lightmode")));
+ Feedback feedback =
+ new Feedback(
+ rs.getString("UUID_feedback"),
+ userWithSettingsAndNoInbox,
+ rs.getString("feedback_comment"),
+ LocalDate.parse(rs.getString("feedback_date")));
+ Feedbacks.add(feedback);
+ }
+ } catch (Exception e) {
+ e.printStackTrace();
+ throw new RuntimeException(e);
+ } finally {
+ conn = null;
+ }
+ return Feedbacks;
+ }
+}
diff --git a/helpmehelpapplication/src/main/java/ntnu/systemutvikling/team6/database/DAO/CharityUserDAO.java b/helpmehelpapplication/src/main/java/ntnu/systemutvikling/team6/database/DAO/CharityUserDAO.java
new file mode 100644
index 00000000..8b7fe701
--- /dev/null
+++ b/helpmehelpapplication/src/main/java/ntnu/systemutvikling/team6/database/DAO/CharityUserDAO.java
@@ -0,0 +1,161 @@
+package ntnu.systemutvikling.team6.database.DAO;
+
+import java.sql.Connection;
+import java.sql.PreparedStatement;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import ntnu.systemutvikling.team6.database.DatabaseConnection;
+import ntnu.systemutvikling.team6.models.Charity;
+
+/**
+ * This Data Access Object is responsible for communication to the Database for a potensial user
+ * that is also a CharityUser.
+ *
+ *
CharityUsers have additional features and priviliges that regular users don't have. Methods
+ * specified provide the opportunity to save a new name or description.
+ *
+ *
All queries are executed against a MySQL database via a {@link DatabaseConnection}.
+ */
+public class CharityUserDAO {
+
+ private DatabaseConnection connection;
+
+ /**
+ * Constructs a new {@code CharityUserDAO} with the given database connection.
+ *
+ * @param connection the {@link DatabaseConnection} to use for executing queries; must not be
+ * {@code null}
+ */
+ public CharityUserDAO(DatabaseConnection connection) {
+ this.connection = connection;
+ }
+
+ /**
+ * Updates the Charity's name in the {@code CharityVanity} table in the database by getting the
+ * Charity object in question.
+ *
+ * @param charity Charity containing the new name for the database
+ * @return True or False based on if the update succeed or not
+ */
+ public boolean updateCharityVanityName(Charity charity) {
+ Connection conn = null;
+ String sql =
+ """
+ UPDATE CharityVanity SET
+ charity_name = ?
+ WHERE UUID_charity = ?;
+ """;
+ try {
+ conn = connection.getMySqlConnection();
+ PreparedStatement ps = conn.prepareStatement(sql);
+
+ System.out.println(charity.getUUID().toString());
+ ps.setString(1, charity.getName());
+ ps.setString(2, charity.getUUID().toString());
+
+ return ps.executeUpdate() > 0;
+
+ } catch (SQLException e) {
+ e.printStackTrace();
+ System.out.println("Something went wrong when updating ah");
+ return false;
+ }
+ }
+
+ /**
+ * Updates the Charity's description in the {@code CharityVanity} table in the database by getting
+ * the Charity object in question.
+ *
+ * @param charity Charity containing the new name for the database
+ * @return True or False based on if the update succeed or not
+ */
+ public boolean updateCharityVanityDescription(Charity charity) {
+ Connection conn = null;
+ String sql =
+ """
+ UPDATE CharityVanity SET
+ description = ?
+ WHERE UUID_charity = ?;
+ """;
+ try {
+ conn = connection.getMySqlConnection();
+ PreparedStatement ps = conn.prepareStatement(sql);
+
+ System.out.println(charity.getUUID().toString());
+ ps.setString(1, charity.getDescription());
+ ps.setString(2, charity.getUUID().toString());
+
+ return ps.executeUpdate() > 0;
+
+ } catch (SQLException e) {
+ e.printStackTrace();
+ System.out.println("Something went wrong when updating ah");
+ return false;
+ }
+ }
+
+ /**
+ * Gets the Charity that the User is connected to by using {@code CharityUsers} as an
+ * identifie/bridge.
+ *
+ * @param uuid Id of the User.
+ * @return The Charity the CharityUser is connected to.
+ */
+ public Charity getUserCharityUser(String uuid) {
+ if (uuid == null || uuid.isBlank()) {
+ throw new IllegalArgumentException("UUID cannot be null or blank");
+ }
+ Charity currentCharity = null;
+ Connection conn = null;
+ try {
+ conn = connection.getMySqlConnection();
+ String sql_query =
+ """
+ SELECT
+ c.UUID_charities, c.org_number, c.pre_approved, c.status,
+ cv.charity_name, cv.charity_link, cv.description, cv.logoURL, cv.key_values, cv.logoBLOB,
+ cat.category
+ FROM CharityUsers cu
+ INNER JOIN Charities c ON c.UUID_charities = cu.TheCharity
+ INNER JOIN CharityVanity cv ON cv.UUID_charity = c.UUID_charities
+ 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
+ WHERE CharityUserId = ?
+ """;
+ PreparedStatement stmt = conn.prepareStatement(sql_query);
+ stmt.setString(1, uuid);
+ ResultSet rs = stmt.executeQuery();
+
+ String lastCharity = null;
+
+ while (rs.next()) {
+ String currentId = rs.getString("UUID_charities");
+ if (lastCharity == null || !currentId.equals(lastCharity)) {
+ currentCharity =
+ new Charity(
+ rs.getString("UUID_charities"),
+ rs.getString("org_number"),
+ rs.getString("charity_name"),
+ rs.getString("charity_link"),
+ rs.getString("status"),
+ rs.getBoolean("pre_approved"),
+ rs.getString("description"),
+ rs.getString("logoURL"),
+ rs.getString("key_values"),
+ rs.getBytes("logoBLOB"));
+ lastCharity = currentId;
+ }
+
+ String categoryName = rs.getString("category");
+ if (categoryName != null && !currentCharity.getCategory().contains(categoryName)) {
+ currentCharity.getCategory().add(categoryName);
+ }
+ }
+
+ } catch (SQLException e) {
+ e.printStackTrace();
+ throw new RuntimeException("ERROR: Something went wrong during updating charities table.");
+ }
+ return currentCharity;
+ }
+}
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
new file mode 100644
index 00000000..1e6c1cc3
--- /dev/null
+++ b/helpmehelpapplication/src/main/java/ntnu/systemutvikling/team6/database/DAO/DonationDAO.java
@@ -0,0 +1,261 @@
+package ntnu.systemutvikling.team6.database.DAO;
+
+import java.sql.*;
+import ntnu.systemutvikling.team6.database.DatabaseConnection;
+import ntnu.systemutvikling.team6.models.Charity;
+import ntnu.systemutvikling.team6.models.Donation;
+import ntnu.systemutvikling.team6.models.registry.DonationRegistry;
+import ntnu.systemutvikling.team6.models.user.User;
+
+/**
+ * This class is responsible for sending and receiving concurrent information about the donation to
+ * and from the Donation Database. Usually called from the Donation related controller, where the
+ * user is able to view their donations.
+ */
+public class DonationDAO {
+ private final DatabaseConnection connection;
+
+ /**
+ * Constructs a new {@code CharityUserDAO} with the given database connection.
+ *
+ * @param connection the {@link DatabaseConnection} to use for executing queries; must not be
+ * {@code null}
+ */
+ public DonationDAO(DatabaseConnection connection) {
+ this.connection = connection;
+ }
+
+ /**
+ * Retrieves all donations from the database, each populated with its associated {@link Charity}.
+ *
+ *
The query performs an INNER JOIN between the {@code Donations}, {@code Charities} and {@code
+ * User} tables on the donations row Charity_id and user_id. Donations without a matching charity
+ * are excluded from the result.
+ *
+ * @return a {@link DonationRegistry} containing all matched donations; never {@code null}, but
+ * may be empty if no rows are returned
+ * @throws RuntimeException if a {@link SQLException} occurs while executing the query
+ */
+ public DonationRegistry getDonationFromDB() {
+ DonationRegistry registry = null;
+ Connection conn = null;
+ try {
+ conn = connection.getMySqlConnection();
+ String sql_query =
+ """
+ SELECT
+ d.UUID_Donations, d.amount, d.isAnonymous, d.date, d.charity_id, d.user_id,
+ c.UUID_charities, c.org_number, c.pre_approved, c.status,
+ u.UUID_User, u.user_name, u.user_email, u.user_password, u.role
+ FROM Donations d
+ INNER JOIN Charities c ON d.charity_id = c.UUID_charities
+ INNER JOIN User u ON d.user_id = u.UUID_user
+ """;
+ Statement stmt = conn.createStatement();
+ ResultSet rs = stmt.executeQuery(sql_query);
+
+ registry = new DonationRegistry();
+ while (rs.next()) {
+ Charity charity =
+ new Charity(
+ rs.getString("UUID_charities"),
+ rs.getString("org_number"),
+ rs.getBoolean("pre_approved"),
+ rs.getString("status"));
+
+ User user =
+ new User(
+ rs.getString("UUID_User"),
+ rs.getString("user_name"),
+ rs.getString("user_email"),
+ rs.getString("user_password"),
+ rs.getString("role"));
+ Donation donation =
+ new Donation(
+ rs.getString("UUID_Donations"),
+ rs.getDouble("amount"),
+ rs.getDate("date").toLocalDate(),
+ charity,
+ user,
+ rs.getBoolean("isAnonymous"));
+ registry.addDonation(donation);
+ }
+ } catch (SQLException e) {
+ e.printStackTrace();
+ throw new RuntimeException("ERROR: Something went wrong during updating charities table.");
+ }
+ return registry;
+ }
+
+ /**
+ * Retrieves all donation data from the {@code Donations} table for a spesific user using its ID.
+ *
+ *
Query selects from {@code Donations}, then INNER JOINS with {@code Charities} and {@code
+ * CharityVanity} to get names among other thing, and {@code User} table to get the donor. At the
+ * end uses a WHERE-clause to get the specified User.
+ *
+ * @param uuid the java.UUID for the user but in string.
+ * @return Returns a DOnationRegistry with all donations.
+ */
+ public DonationRegistry getDonationForUser(String uuid) {
+ DonationRegistry registry = null;
+ Connection conn = null;
+ try {
+ conn = connection.getMySqlConnection();
+ String sql_query =
+ """
+ SELECT
+ d.UUID_Donations, d.amount, d.isAnonymous, d.date, d.charity_id, d.user_id,
+ c.UUID_charities, c.org_number, c.pre_approved, c.status,
+ cv.charity_name, cv.charity_link, cv.description, cv.logoURL, cv.key_values, cv.logoBLOB,
+ u.UUID_User, u.user_name, u.user_email, u.user_password, u.role
+ FROM Donations d
+ INNER JOIN Charities c ON d.charity_id = c.UUID_charities
+ INNER JOIN CharityVanity cv ON cv.UUID_charity = c.UUID_charities
+ INNER JOIN User u ON d.user_id = u.UUID_user
+ WHERE d.user_id = ?
+ """;
+ PreparedStatement stmt = conn.prepareStatement(sql_query);
+ stmt.setString(1, uuid);
+ ResultSet rs = stmt.executeQuery();
+
+ registry = new DonationRegistry();
+ while (rs.next()) {
+ Charity charity =
+ new Charity(
+ rs.getString("UUID_charities"),
+ rs.getString("org_number"),
+ rs.getString("charity_name"),
+ rs.getString("charity_link"),
+ rs.getString("status"),
+ rs.getBoolean("pre_approved"),
+ rs.getString("description"),
+ rs.getString("logoURL"),
+ rs.getString("key_values"),
+ rs.getBytes("logoBLOB"));
+ User user =
+ new User(
+ rs.getString("UUID_User"),
+ rs.getString("user_name"),
+ rs.getString("user_email"),
+ rs.getString("user_password"),
+ rs.getString("role"));
+ Donation donation =
+ new Donation(
+ rs.getString("UUID_Donations"),
+ rs.getDouble("amount"),
+ rs.getDate("date").toLocalDate(),
+ charity,
+ user,
+ rs.getBoolean("isAnonymous"));
+ registry.addDonation(donation);
+ }
+ } catch (SQLException e) {
+ e.printStackTrace();
+ throw new RuntimeException("ERROR: Something went wrong during updating charities table.");
+ }
+ return registry;
+ }
+
+ /**
+ * Retrieves all donation data from the {@code Donations} table for a specific Charity using its
+ * ID.
+ *
+ *
Mostly used by Charity Users to view how much money they have. Query selects from {@code
+ * Donations}, then INNER JOINS with {@code Charities} and {@code CharityVanity} to get names
+ * among other thing, and {@code User} table to get the donor. At the end uses a WHERE - clause to
+ * get the Charity in question.
+ *
+ * @param uuid the java.UUID for the user but in string.
+ * @return Returns a DOnationRegistry with all donations.
+ */
+ public DonationRegistry getDonationForCharity(String uuid) {
+ DonationRegistry registry = null;
+ Connection conn = null;
+ try {
+ conn = connection.getMySqlConnection();
+ String sql_query =
+ """
+ SELECT
+ d.UUID_Donations, d.amount, d.isAnonymous, d.date, d.charity_id, d.user_id,
+ c.UUID_charities, c.org_number, c.pre_approved, c.status,
+ cv.charity_name, cv.charity_link, cv.description, cv.logoURL, cv.key_values, cv.logoBLOB,
+ u.UUID_User, u.user_name, u.user_email, u.user_password, u.role
+ FROM Donations d
+ INNER JOIN Charities c ON d.charity_id = c.UUID_charities
+ INNER JOIN CharityVanity cv ON cv.UUID_charity = c.UUID_charities
+ INNER JOIN User u ON d.user_id = u.UUID_user
+ WHERE c.UUID_charities = ?
+ """;
+ PreparedStatement stmt = conn.prepareStatement(sql_query);
+ stmt.setString(1, uuid);
+ ResultSet rs = stmt.executeQuery();
+
+ registry = new DonationRegistry();
+ while (rs.next()) {
+ Charity charity =
+ new Charity(
+ rs.getString("UUID_charities"),
+ rs.getString("org_number"),
+ rs.getString("charity_name"),
+ rs.getString("charity_link"),
+ rs.getString("status"),
+ rs.getBoolean("pre_approved"),
+ rs.getString("description"),
+ rs.getString("logoURL"),
+ rs.getString("key_values"),
+ rs.getBytes("logoBLOB"));
+ User user =
+ new User(
+ rs.getString("UUID_User"),
+ rs.getString("user_name"),
+ rs.getString("user_email"),
+ rs.getString("user_password"),
+ rs.getString("role"));
+ Donation donation =
+ new Donation(
+ rs.getString("UUID_Donations"),
+ rs.getDouble("amount"),
+ rs.getDate("date").toLocalDate(),
+ charity,
+ user,
+ rs.getBoolean("isAnonymous"));
+ registry.addDonation(donation);
+ }
+ } catch (SQLException e) {
+ e.printStackTrace();
+ throw new RuntimeException("ERROR: Something went wrong during updating charities table.");
+ }
+ return registry;
+ }
+
+ /**
+ * Gets a Donation object for a given charity and by a User, and sends it to the database through
+ * MySQL.
+ *
+ * @param donation Donation object containing all relevant information to send to database.
+ */
+ public void addDonation(Donation donation) {
+ String sql_query =
+ """
+ INSERT INTO Donations (UUID_Donations, amount, isAnonymous, date, charity_id, user_id)
+ VALUES (?, ?, ?, ?, ?, ?)
+ """;
+ try (Connection conn = connection.getMySqlConnection();
+ PreparedStatement ps = conn.prepareStatement(sql_query)) {
+ conn.setAutoCommit(false);
+
+ ps.setString(1, donation.getDonationID().toString());
+ ps.setDouble(2, donation.getAmount());
+ ps.setBoolean(3, donation.getDonor().getSettings().isAnonymous());
+ ps.setDate(4, Date.valueOf(donation.getDate()));
+ ps.setString(5, donation.getCharity().getUUID().toString());
+ ps.setString(6, donation.getDonor().getId().toString());
+
+ ps.executeUpdate();
+ conn.commit();
+ } catch (SQLException e) {
+ throw new RuntimeException(e);
+ }
+ }
+}
diff --git a/helpmehelpapplication/src/main/java/ntnu/systemutvikling/team6/database/DAO/FavouritesDAO.java b/helpmehelpapplication/src/main/java/ntnu/systemutvikling/team6/database/DAO/FavouritesDAO.java
new file mode 100644
index 00000000..f9450288
--- /dev/null
+++ b/helpmehelpapplication/src/main/java/ntnu/systemutvikling/team6/database/DAO/FavouritesDAO.java
@@ -0,0 +1,184 @@
+package ntnu.systemutvikling.team6.database.DAO;
+
+import java.sql.Connection;
+import java.sql.PreparedStatement;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.util.List;
+import ntnu.systemutvikling.team6.database.DatabaseConnection;
+import ntnu.systemutvikling.team6.models.Charity;
+import ntnu.systemutvikling.team6.models.registry.CharityRegistry;
+import ntnu.systemutvikling.team6.models.user.User;
+
+/**
+ * Data access class responsible for getting managing favourites for a logged inn user in the
+ * application.
+ *
+ *
Methods include gettng users favourites, adding a favourite and checking if the charity was
+ * favourited.
+ *
+ *
All queries are executed against a MySQL database via a {@link DatabaseConnection}.
+ */
+public class FavouritesDAO {
+
+ private final DatabaseConnection connection;
+
+ /**
+ * Constructs a new {@code FavouritesDAO} with the given database connection.
+ *
+ * @param connection the {@link DatabaseConnection} to use for executing queries; must not be
+ * {@code null}
+ */
+ public FavouritesDAO(DatabaseConnection connection) {
+ this.connection = connection;
+ }
+
+ /**
+ * Checks if both {@code User} and {@code Charity} are in the same row in the {@code
+ * User_has_favourites } table. If it is, the charity is considered a favourite by the User
+ *
+ *
Uses a Select query with a WHERE-clause that searches for a row with both ids, which
+ * considered a favourite.
+ *
+ * @param user User in question
+ * @param charity Charity in question of being favourite by User.
+ * @return
+ */
+ public boolean isFavourite(User user, Charity charity) {
+ String sql = "SELECT * FROM User_has_favourites WHERE Favourer = ? AND Favourite_Charity = ?";
+
+ try (Connection conn = connection.getMySqlConnection();
+ PreparedStatement ps = conn.prepareStatement(sql)) {
+
+ ps.setString(1, user.getId().toString());
+ ps.setString(2, charity.getUUID().toString());
+
+ ResultSet rs = ps.executeQuery();
+ return rs.next();
+
+ } catch (SQLException e) {
+ e.printStackTrace();
+ return false;
+ }
+ }
+
+ /**
+ * Send a quick insert query to the {@code User_has_favourites} table, adding a row with favourer
+ * and the favourite Charity
+ *
+ * @param user User or the Favourer in the database
+ * @param charity Favourite charity in question.
+ * @return True or False based on if it succeeded or not
+ */
+ public boolean addFavourite(User user, Charity charity) {
+ String sql = "INSERT INTO User_has_favourites (Favourer, Favourite_charity) VALUES (?, ?)";
+
+ try (Connection conn = connection.getMySqlConnection();
+ PreparedStatement ps = conn.prepareStatement(sql)) {
+
+ ps.setString(1, user.getId().toString());
+ ps.setString(2, charity.getUUID().toString());
+ return ps.executeUpdate() > 0;
+
+ } catch (SQLException e) {
+ e.printStackTrace();
+ return false;
+ }
+ }
+
+ /**
+ * Does a quick remove query to delete a previous favourite charity.
+ *
+ *
This will not invoke and Argument-Exception because the remove query is avabile only after
+ * being added throught {@code addFavourite}
+ *
+ * @param user user in question of having a favourite
+ * @param charity Charity about to be removed a favourite
+ * @return
+ */
+ public boolean removeFavourite(User user, Charity charity) {
+ String sql = "DELETE FROM User_has_favourites WHERE Favourer = ? AND Favourite_charity = ?";
+
+ try (Connection conn = connection.getMySqlConnection();
+ PreparedStatement ps = conn.prepareStatement(sql)) {
+
+ ps.setString(1, user.getId().toString());
+ ps.setString(2, charity.getUUID().toString());
+ return ps.executeUpdate() > 0;
+
+ } catch (SQLException e) {
+ e.printStackTrace();
+ return false;
+ }
+ }
+
+ /**
+ * Get all detailts about the users favourites.
+ *
+ *
Uses a Select-query, INNER JOIN between {@code Charities} and {@code CharityVanity} tables,
+ * and LEFT JOIN {@code Charity_Categories} and {@code Categries} tables to get fill all
+ * attributes in the {@code Charity} object. At the end, uses a WHERE-clause to grab the right
+ * favourer.
+ *
+ * @param user_id UUID in String for the Favourer
+ * @return An empty or non-empty List of Charities that have been favourited by User
+ */
+ public List getFavouritesForUser(String user_id) {
+ CharityRegistry registry = null;
+ Connection conn = null;
+ try {
+ conn = connection.getMySqlConnection();
+ String sql_query =
+ """
+ SELECT
+ uf.Favourite_Charity,
+ c.UUID_charities, c.org_number, c.pre_approved, c.status,
+ cv.charity_name, cv.charity_link, cv.description, cv.logoURL, cv.key_values, cv.logoBLOB,
+ cat.category
+ FROM User_has_favourites uf
+ INNER JOIN Charities c ON uf.Favourite_Charity = c.UUID_charities
+ INNER JOIN CharityVanity cv ON uf.Favourite_Charity = cv.UUID_charity
+ 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
+ WHERE Favourer = ?;
+ """;
+ PreparedStatement stmt = conn.prepareStatement(sql_query);
+ stmt.setString(1, user_id);
+ ResultSet rs = stmt.executeQuery();
+
+ Charity currentCharity = null;
+
+ registry = new CharityRegistry();
+ while (rs.next()) {
+ String currentId = rs.getString("UUID_charities");
+ if (currentCharity == null || !currentId.equals(currentCharity.getUUID().toString())) {
+ System.out.println("Adding Charities");
+ currentCharity =
+ new Charity(
+ rs.getString("UUID_charities"),
+ rs.getString("org_number"),
+ rs.getString("charity_name"),
+ rs.getString("charity_link"),
+ rs.getString("status"),
+ rs.getBoolean("pre_approved"),
+ rs.getString("description"),
+ rs.getString("logoURL"),
+ rs.getString("key_values"),
+ rs.getBytes("logoBLOB"));
+ registry.addCharity(currentCharity);
+ }
+
+ String categoryName = rs.getString("category");
+ if (categoryName != null & !currentCharity.getCategory().contains(categoryName)) {
+ currentCharity.getCategory().add(categoryName);
+ }
+ }
+ } catch (SQLException e) {
+ e.printStackTrace();
+ throw new RuntimeException("ERROR: Something went wrong during updating.");
+ } finally {
+ conn = null;
+ }
+ return registry.getAllCharities();
+ }
+}
diff --git a/helpmehelpapplication/src/main/java/ntnu/systemutvikling/team6/database/DAO/FeedbackDAO.java b/helpmehelpapplication/src/main/java/ntnu/systemutvikling/team6/database/DAO/FeedbackDAO.java
new file mode 100644
index 00000000..e546e87b
--- /dev/null
+++ b/helpmehelpapplication/src/main/java/ntnu/systemutvikling/team6/database/DAO/FeedbackDAO.java
@@ -0,0 +1,128 @@
+package ntnu.systemutvikling.team6.database.DAO;
+
+import java.sql.*;
+import java.time.LocalDate;
+import java.util.ArrayList;
+import ntnu.systemutvikling.team6.database.DatabaseConnection;
+import ntnu.systemutvikling.team6.models.Charity;
+import ntnu.systemutvikling.team6.models.Feedback;
+import ntnu.systemutvikling.team6.models.user.Language;
+import ntnu.systemutvikling.team6.models.user.Settings;
+import ntnu.systemutvikling.team6.models.user.User;
+
+/**
+ * This Data Access Object is mainly responsible for handling Feedbacks and communitcating with the
+ * DataBase. Primarily interacting with the {@code Feedback} table.
+ */
+public class FeedbackDAO {
+ private final DatabaseConnection connection;
+
+ /**
+ * Constructs a new {@code FeedbackDAO} with the given database connection.
+ *
+ * @param connection the {@link DatabaseConnection} to use for executing queries; must not be
+ * {@code null}
+ */
+ public FeedbackDAO(DatabaseConnection connection) {
+ this.connection = connection;
+ }
+
+ /**
+ * Send a simple insert query to the {@code Feedback} table. Containing the comment, date, if its
+ * an Anonymous Feedback and recipients.
+ *
+ * @param feedback Feedback object contain all relevant info, as said above.
+ * @param toCharity Dedicated Charity object, the recipient.
+ * @return
+ */
+ public boolean addFeedbackToCharity(Feedback feedback, Charity toCharity) {
+ String sql =
+ """
+ INSERT INTO Feedback
+ (UUID_feedback, feedback_comment, feedback_date, isAnonymous, charity_id, user_id)
+ VALUES (?, ?, ?, ?, ?, ?);
+
+ """;
+
+ try (Connection conn = connection.getMySqlConnection();
+ PreparedStatement ps = conn.prepareStatement(sql)) {
+
+ ps.setString(1, feedback.getFeedbackId().toString());
+ ps.setString(2, feedback.getComment());
+ ps.setDate(3, Date.valueOf(feedback.getDate()));
+ ps.setBoolean(4, feedback.getUser().getSettings().isAnonymous());
+ ps.setString(5, toCharity.getUUID().toString());
+ ps.setString(6, feedback.getUser().getId().toString());
+ return ps.executeUpdate() > 0;
+
+ } catch (SQLException e) {
+ e.printStackTrace();
+ return false;
+ }
+ }
+
+ /**
+ * A helper function that retrieves all feedback entries associated with a specific charity,
+ * identified by its UUID. Currently, has no use.
+ *
+ *
Each {@link Feedback} object is populated with the associated {@link User} (without settings
+ * or inbox data). The query uses a LEFT JOIN between the {@code Feedback} and {@code User}
+ * tables, filtered by {@code charity_id}.
+ *
+ * @param charity_uuid the UUID of the charity whose feedback should be retrieved; must not be
+ * {@code null}
+ * @return an {@link ArrayList} of {@link Feedback} objects for the given charity; returns an
+ * empty list if no feedback exists for that charity
+ * @throws RuntimeException if any exception occurs while executing the query, wrapping the
+ * original cause
+ */
+ public ArrayList getFeedbackforCharityUUID(String charity_uuid) {
+ ArrayList Feedbacks = new ArrayList<>();
+ Connection conn = null;
+ try {
+ conn = connection.getMySqlConnection();
+ String sql_query =
+ """
+ SELECT
+ f.UUID_feedback, f.feedback_comment, f.feedback_date, f.isAnonymous, f.charity_id, f.user_id,
+ u.UUID_user, u.user_name, u.user_email, u.user_password, u.role,
+ s.language, s.lightmode
+ FROM Feedback f
+ LEFT JOIN User u ON f.user_id = u.UUID_user
+ RIGHT JOIN Settings s ON u.UUID_user = s.UUID_user
+ WHERE f.charity_id = ?;
+ """;
+ PreparedStatement stmt = conn.prepareStatement(sql_query);
+ stmt.setString(1, charity_uuid);
+ ResultSet rs = stmt.executeQuery();
+
+ while (rs.next()) {
+ User userWithSettingsAndNoInbox =
+ new User(
+ rs.getString("UUID_User"),
+ rs.getString("user_name"),
+ rs.getString("user_email"),
+ rs.getString("user_password"),
+ rs.getString("role"));
+ userWithSettingsAndNoInbox.setSettings(
+ new Settings(
+ rs.getBoolean("isAnonymous"),
+ Language.valueOf(rs.getString("language")),
+ rs.getBoolean("lightmode")));
+ Feedback feedback =
+ new Feedback(
+ rs.getString("UUID_feedback"),
+ userWithSettingsAndNoInbox,
+ rs.getString("feedback_comment"),
+ LocalDate.parse(rs.getString("feedback_date")));
+ Feedbacks.add(feedback);
+ }
+ } catch (Exception e) {
+ e.printStackTrace();
+ throw new RuntimeException(e);
+ } finally {
+ conn = null;
+ }
+ return Feedbacks;
+ }
+}
diff --git a/helpmehelpapplication/src/main/java/ntnu/systemutvikling/team6/database/DAO/MessageDAO.java b/helpmehelpapplication/src/main/java/ntnu/systemutvikling/team6/database/DAO/MessageDAO.java
new file mode 100644
index 00000000..3d141d84
--- /dev/null
+++ b/helpmehelpapplication/src/main/java/ntnu/systemutvikling/team6/database/DAO/MessageDAO.java
@@ -0,0 +1,108 @@
+package ntnu.systemutvikling.team6.database.DAO;
+
+import java.sql.Connection;
+import java.sql.Date;
+import java.sql.PreparedStatement;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.UUID;
+import ntnu.systemutvikling.team6.database.DatabaseConnection;
+import ntnu.systemutvikling.team6.models.user.Message;
+
+/**
+ * This Data Access Object is mainly responsible for handling Messages and Message object to
+ * communitcate correctly with the DataBase. Primarily interacting with the {@code Messages} table.
+ */
+public class MessageDAO {
+ private final DatabaseConnection connection;
+
+ /**
+ * Constructs a new {@code FeedbackDAO} with the given database connection.
+ *
+ * @param connection the {@link DatabaseConnection} to use for executing queries; must not be
+ * {@code null}
+ */
+ public MessageDAO(DatabaseConnection connection) {
+ this.connection = connection;
+ }
+
+ /**
+ * Uses a repeated INSERT-query to send a message to all donor (as in Users that have donated).
+ *
+ *
First grabs all donors (all Users that have donated to the charity) ids by getting the
+ * Charity_id throught the attribute.
+ *
+ * @param message Message object contain all relevant info, including the Charity id.
+ * @return True or False based on if it succeeded or not
+ */
+ public boolean addMessage(Message message) {
+ // First fetch all unique donors for this charity
+ List donorIds = getDonorIdsForCharity(message.getFrom().getUUID().toString());
+
+ if (donorIds.isEmpty()) return false;
+ String sql =
+ """
+ INSERT INTO Messages
+ (UUID_message, message_title, message_content, message_date, sender_charity_id, user_id)
+ VALUES (?, ?, ?, ?, ?, ?)
+ """;
+
+ try (Connection conn = connection.getMySqlConnection();
+ PreparedStatement stmt = conn.prepareStatement(sql)) {
+
+ for (String donorId : donorIds) {
+ stmt.setString(1, UUID.randomUUID().toString());
+ stmt.setString(2, message.getTitle());
+ stmt.setString(3, message.getContent());
+ stmt.setDate(4, Date.valueOf(message.getTimeAndDate()));
+ stmt.setString(5, message.getFrom().getUUID().toString());
+ stmt.setString(6, donorId);
+ stmt.addBatch();
+ }
+
+ int[] results = stmt.executeBatch();
+ return results.length > 0;
+
+ } catch (SQLException e) {
+ e.printStackTrace();
+ throw new RuntimeException("ERROR: Failed to send messages to donors.");
+ }
+ }
+
+ /**
+ * Private helper function used in {@code addMessage} method, which finds all distinct User Ids
+ * that have donated to given Charity.
+ *
+ *
Uses a Select query with a distinct modifier to not get duplicate user_id. At the end uses
+ * an WHERE-clause to get the spesified charity.
+ *
+ * @param charityId Charity's I'd to search for.
+ * @return An empty or non-empty list of Ids as String.
+ */
+ private List getDonorIdsForCharity(String charityId) {
+ List donorIds = new ArrayList<>();
+ String sql =
+ """
+ SELECT DISTINCT user_id
+ FROM Donations
+ WHERE charity_id = ?
+ """;
+
+ try (Connection conn = connection.getMySqlConnection();
+ PreparedStatement stmt = conn.prepareStatement(sql)) {
+
+ stmt.setString(1, charityId);
+ try (ResultSet rs = stmt.executeQuery()) {
+ while (rs.next()) {
+ donorIds.add(rs.getString("user_id"));
+ }
+ }
+ } catch (SQLException e) {
+ e.printStackTrace();
+ throw new RuntimeException("ERROR: Failed to fetch donor IDs.");
+ }
+ return donorIds;
+ }
+}
diff --git a/helpmehelpapplication/src/main/java/ntnu/systemutvikling/team6/database/DAO/UserDAO.java b/helpmehelpapplication/src/main/java/ntnu/systemutvikling/team6/database/DAO/UserDAO.java
new file mode 100644
index 00000000..c3627149
--- /dev/null
+++ b/helpmehelpapplication/src/main/java/ntnu/systemutvikling/team6/database/DAO/UserDAO.java
@@ -0,0 +1,632 @@
+package ntnu.systemutvikling.team6.database.DAO;
+
+import java.sql.*;
+import java.time.LocalDate;
+import java.util.HashSet;
+import ntnu.systemutvikling.team6.database.DatabaseConnection;
+import ntnu.systemutvikling.team6.models.Charity;
+import ntnu.systemutvikling.team6.models.registry.UserRegistry;
+import ntnu.systemutvikling.team6.models.user.*;
+import ntnu.systemutvikling.team6.security.PasswordHasher;
+
+/**
+ * This class is responsible for sending concurrent information about the user to the User database,
+ * and user settings to the settings database.
+ *
+ * @author Robin Strand Prestmo
+ * @author Adrian Paul Limpiado Balunan
+ */
+public class UserDAO {
+
+ private final DatabaseConnection connection;
+
+ /**
+ * Constructs a new {@code UserDAO} with the given database connection.
+ *
+ * @param connection the {@link DatabaseConnection} to use for executing queries; must not be
+ * {@code null}
+ */
+ public UserDAO(DatabaseConnection connection) {
+ this.connection = connection;
+ }
+
+ /**
+ * Uses a select query to check if the provided Email already exists in {@code User} table. Return
+ * value is based on if finds a row or not.
+ *
+ *
Check if email is a valid email. Uses a Select query to get a row containing the provided
+ * email. If it exists, return true. If not, return false Used in tandem with {@code
+ * LoginPageController} prevent duplicate Emails appearing on database.
+ *
+ * @param email Email in question. Check
+ * @return
+ */
+ public boolean isEmailTaken(String email) {
+ if (email == null || email.isBlank() || !email.contains("@") || !email.contains(".")) {
+ throw new IllegalArgumentException(
+ "Email cannot be null or blank," + " and must contain '@' and '.'");
+ }
+ try (Connection conn = connection.getMySqlConnection()) {
+
+ String mysql =
+ """
+ SELECT UUID_User FROM User WHERE user_email = ?
+ """;
+ PreparedStatement statement = conn.prepareStatement(mysql);
+ statement.setString(1, email);
+ ResultSet rs = statement.executeQuery();
+
+ if (rs.next()) {
+ System.out.println("Email Taken already");
+ return true;
+ }
+
+ } catch (SQLException e) {
+ e.printStackTrace();
+ }
+ return false;
+ }
+
+ /**
+ * Retrieves a single {@link User} from the database by their UUID.
+ *
+ *
The returned user is fully populated with {@link Settings} (when present) and an {@link
+ * Inbox} containing any associated {@link Message} objects. Returns {@code null} if no user with
+ * the given UUID exists.
+ *
+ * @param user_id the UUID string of the user to retrieve; must not be {@code null}
+ * @return the matching {@link User} with settings and inbox populated, or {@code null} if no user
+ * is found
+ * @throws RuntimeException if a {@link SQLException} occurs while executing the query
+ */
+ public User getUserFromDBUuid(String user_id) {
+ User user = null;
+ Connection conn = null;
+ try {
+ conn = connection.getMySqlConnection();
+ String sql_query =
+ """
+ SELECT
+ u.UUID_User, u.user_name, u.user_email, u.user_password, u.role,
+ s.UUID_user, s.isAnonymous, s.language, s.lightmode,
+ m.UUID_message, m.message_title, m.message_content, m.message_date, m.sender_user_id, m.sender_charity_id, m.user_id,
+ cv.charity_name, cv.charity_link, cv.description, cv.logoURL, cv.key_values, cv.logoBLOB,
+ c.UUID_charities, c.org_number, c.pre_approved, c.status
+ FROM User u
+ LEFT JOIN Settings s ON u.UUID_User = s.UUID_user
+ LEFT JOIN Messages m ON u.UUID_User = m.user_id
+ LEFT JOIN Charities c ON m.sender_charity_id = c.UUID_charities
+ LEFT JOIN CharityVanity cv ON c.UUID_charities = cv.UUID_charity
+ WHERE u.UUID_User = ?;
+ """;
+ PreparedStatement stmt = conn.prepareStatement(sql_query);
+ stmt.setString(1, user_id);
+ ResultSet rs = stmt.executeQuery();
+
+ String lastUserid = null;
+ HashSet addedMessageIds = new HashSet<>();
+ while (rs.next()) {
+ String userId = rs.getString("UUID_User");
+ if (lastUserid == null || !userId.equals(lastUserid)) {
+ user =
+ new User(
+ userId,
+ rs.getString("user_name"),
+ rs.getString("user_email"),
+ rs.getString("user_password"),
+ rs.getString("role"));
+ if (rs.getString("isAnonymous") != null) {
+ Settings settings =
+ new Settings(
+ rs.getBoolean("isAnonymous"),
+ Language.valueOf(rs.getString("language").toUpperCase()),
+ rs.getBoolean("lightmode"));
+ user.setSettings(settings);
+ }
+ user.setInbox(new Inbox());
+ lastUserid = userId;
+ }
+ String messageId = rs.getString("UUID_message");
+ if (messageId != null && !addedMessageIds.contains(messageId)) {
+ addedMessageIds.add(messageId);
+ Charity fromCharity = null;
+ String charityId = rs.getString("UUID_charities");
+ if (charityId != null) {
+ fromCharity =
+ new Charity(
+ charityId,
+ rs.getString("org_number"),
+ rs.getString("charity_name"),
+ rs.getString("charity_link"),
+ rs.getString("status"),
+ rs.getBoolean("pre_approved"),
+ rs.getString("description"),
+ rs.getString("logoURL"),
+ rs.getString("key_values"),
+ rs.getBytes("logoBLOB"));
+ }
+
+ if (fromCharity != null) {
+ Message message =
+ new Message(
+ rs.getString("message_title"),
+ fromCharity,
+ rs.getString("message_content"),
+ LocalDate.parse(rs.getString("message_date")));
+ user.getInbox().addMessage(message);
+ }
+ }
+ }
+ } catch (SQLException e) {
+ e.printStackTrace();
+ throw new RuntimeException("ERROR: Something went wrong during updating charities table.");
+ } finally {
+ conn = null;
+ }
+ return user;
+ }
+
+ /**
+ * Retrieves a single {@link User} from the database matching the given username and password.
+ *
+ *
The password is hashed via {@link PasswordHasher} before being compared against the stored
+ * value. If a matching user is found, their {@link Settings} (when present) and {@link Inbox}
+ * (including any {@link Message} objects) are also populated. Returns {@code null} if no matching
+ * user is found.
+ *
+ *
Note: the current SQL query compares both parameters against {@code
+ * user_password}; the {@code user_name} column is not yet included in the WHERE clause, which may
+ * be a bug.
+ *
+ * @param email the email to look up
+ * @param password the plain-text password; hashed internally before the query runs
+ * @return the matching {@link User} with settings and inbox populated, or {@code null} if no
+ * match is found
+ * @throws RuntimeException if a {@link SQLException} occurs while executing the query
+ */
+ public User getUserFromDBEmailAndPassword(String email, String password) {
+ PasswordHasher hasher = new PasswordHasher();
+ String hashedpassword = hasher.getHashPassword(password);
+
+ User user = null;
+ Connection conn = null;
+ try {
+ conn = connection.getMySqlConnection();
+ String sql_query =
+ """
+ SELECT
+ u.UUID_User, u.user_name, u.user_email, u.user_password, u.role,
+ s.UUID_user, s.isAnonymous, s.language, s.lightmode,
+ m.UUID_message, m.message_title, m.message_content, m.message_date, m.sender_user_id, m.sender_charity_id, m.user_id,
+ cv.charity_name, cv.charity_link, cv.description, cv.logoURL, cv.key_values, cv.logoBLOB,
+ c.UUID_charities, c.org_number, c.pre_approved, c.status
+ FROM User u
+ LEFT JOIN Settings s ON u.UUID_User = s.UUID_user
+ LEFT JOIN Messages m ON u.UUID_User = m.user_id
+ LEFT JOIN Charities c ON m.sender_charity_id = c.UUID_charities
+ LEFT JOIN CharityVanity cv ON c.UUID_charities = cv.UUID_charity
+ WHERE u.user_email = ?;
+ """;
+ PreparedStatement stmt = conn.prepareStatement(sql_query);
+ stmt.setString(1, email);
+
+ ResultSet rs = stmt.executeQuery();
+ HashSet addedMessageIds = new HashSet<>();
+ while (rs.next()) {
+ String userId = rs.getString("UUID_User");
+ System.out.println(rs.getString("user_name"));
+
+ if (user == null) {
+ String storedHash = rs.getString("user_password");
+
+ if (!new PasswordHasher().isValidPassword(password, storedHash)) {
+ return null;
+ }
+ user =
+ new User(
+ userId,
+ rs.getString("user_name"),
+ rs.getString("user_email"),
+ rs.getString("user_password"),
+ rs.getString("role"));
+ if (rs.getString("isAnonymous") != null) {
+ Settings settings =
+ new Settings(
+ rs.getBoolean("isAnonymous"),
+ Language.valueOf(rs.getString("language").toUpperCase()),
+ rs.getBoolean("lightmode"));
+ user.setSettings(settings);
+ }
+ user.setInbox(new Inbox());
+ }
+ String messageId = rs.getString("UUID_message");
+ if (messageId != null && !addedMessageIds.contains(messageId)) {
+ addedMessageIds.add(messageId);
+ Charity fromCharity = null;
+ String charityId = rs.getString("UUID_charities");
+ if (charityId != null) {
+ fromCharity =
+ new Charity(
+ charityId,
+ rs.getString("org_number"),
+ rs.getString("charity_name"),
+ rs.getString("charity_link"),
+ rs.getString("status"),
+ rs.getBoolean("pre_approved"),
+ rs.getString("description"),
+ rs.getString("logoURL"),
+ rs.getString("key_values"),
+ rs.getBytes("logoBLOB"));
+ }
+
+ if (fromCharity != null) {
+ Message message =
+ new Message(
+ rs.getString("message_title"),
+ fromCharity,
+ rs.getString("message_content"),
+ LocalDate.parse(rs.getString("message_date")));
+ user.getInbox().addMessage(message);
+ }
+ }
+ }
+ } catch (SQLException e) {
+ e.printStackTrace();
+ throw new RuntimeException("ERROR: Something went wrong during updating charities table.");
+ } finally {
+ conn = null;
+ }
+ return user;
+ }
+
+ /**
+ * Retrieves all users from the database, each fully populated with their {@link Settings} and
+ * {@link Inbox}.
+ *
+ *
The query LEFT JOINs {@code User}, {@code Settings}, and {@code Messages}. Multiple rows for
+ * the same user UUID (due to multiple messages) are collapsed into a single {@link User} object
+ * with all messages appended to its inbox.
+ *
+ * @return a {@link UserRegistry} containing all users found in the database; never {@code null},
+ * but may be empty if no users exist
+ * @throws RuntimeException if a {@link SQLException} occurs while executing the query
+ */
+ public UserRegistry getUsersFromDB() {
+ UserRegistry registry = new UserRegistry();
+ Connection conn = null;
+ try {
+ conn = connection.getMySqlConnection();
+ String sql_query =
+ """
+ SELECT
+ u.UUID_User, u.user_name, u.user_email, u.user_password, u.role,
+ s.UUID_user, s.isAnonymous, s.language, s.lightmode,
+ m.UUID_message, m.message_title, m.message_content, m.message_date, m.sender_user_id, m.sender_charity_id, m.user_id,
+ cv.charity_name, cv.charity_link, cv.description, cv.logoURL, cv.key_values, cv.logoBLOB,
+ c.UUID_charities, c.org_number, c.pre_approved, c.status
+ FROM User u
+ LEFT JOIN Settings s ON u.UUID_User = s.UUID_user
+ LEFT JOIN Messages m ON u.UUID_User = m.user_id
+ LEFT JOIN Charities c ON m.sender_charity_id = c.UUID_charities
+ LEFT JOIN CharityVanity cv ON c.UUID_charities = cv.UUID_charity
+ """;
+ Statement stmt = conn.createStatement();
+ ResultSet rs = stmt.executeQuery(sql_query);
+
+ User currentUser = null;
+ String lastUserid = null;
+ HashSet addedMessageIds = new HashSet<>();
+
+ while (rs.next()) {
+ String userId = rs.getString("UUID_User");
+
+ if (lastUserid == null || !userId.equals(lastUserid)) {
+ currentUser =
+ new User(
+ userId,
+ rs.getString("user_name"),
+ rs.getString("user_email"),
+ rs.getString("user_password"),
+ rs.getString("role"));
+ if (rs.getString("isAnonymous") != null) {
+ Settings settings =
+ new Settings(
+ rs.getBoolean("isAnonymous"),
+ Language.valueOf(rs.getString("language").toUpperCase()),
+ rs.getBoolean("lightmode"));
+ currentUser.setSettings(settings);
+ }
+ currentUser.setInbox(new Inbox());
+ registry.addUser(currentUser);
+ lastUserid = userId;
+ }
+ String messageId = rs.getString("UUID_message");
+ if (messageId != null && !addedMessageIds.contains(messageId)) {
+ addedMessageIds.add(messageId);
+ Charity fromCharity = null;
+ String charityId = rs.getString("UUID_charities");
+ if (charityId != null) {
+ fromCharity =
+ new Charity(
+ charityId,
+ rs.getString("org_number"),
+ rs.getString("charity_name"),
+ rs.getString("charity_link"),
+ rs.getString("status"),
+ rs.getBoolean("pre_approved"),
+ rs.getString("description"),
+ rs.getString("logoURL"),
+ rs.getString("key_values"),
+ rs.getBytes("logoBLOB"));
+ }
+
+ if (fromCharity != null) {
+ Message message =
+ new Message(
+ rs.getString("message_title"),
+ fromCharity,
+ rs.getString("message_content"),
+ LocalDate.parse(rs.getString("message_date")));
+ currentUser.getInbox().addMessage(message);
+ }
+ }
+ }
+ } catch (SQLException e) {
+ e.printStackTrace();
+ throw new RuntimeException("ERROR: Something went wrong during updating charities table.");
+ } finally {
+ conn = null;
+ }
+ return registry;
+ }
+
+ /**
+ * Retrieves the {@link Inbox} for a specific user by their UUID, populated with all of their
+ * {@link Message} objects.
+ *
+ *
Returns an empty {@link Inbox} (never {@code null}) if no messages exist for the given user.
+ *
+ * @param user_id the UUID string of the user whose inbox should be retrieved; must not be {@code
+ * null}
+ * @return an {@link Inbox} containing all messages for the user; empty if no messages are found
+ * @throws RuntimeException if a {@link SQLException} occurs while executing the query
+ */
+ public Inbox getInboxForUser(String user_id) {
+ Inbox inbox = new Inbox();
+ Connection conn = null;
+ try {
+ conn = connection.getMySqlConnection();
+ String sql_query =
+ """
+ SELECT
+ m.UUID_message, m.message_title, m.message_content, m.message_date, m.sender_user_id, m.sender_charity_id, m.user_id,
+ cv.charity_name, cv.charity_link, cv.description, cv.logoURL, cv.key_values, cv.logoBLOB,
+ c.UUID_charities, c.org_number, c.pre_approved, c.status
+ FROM Messages m
+ LEFT JOIN Charities c ON m.sender_charity_id = c.UUID_charities
+ LEFT JOIN CharityVanity cv ON c.UUID_charities = cv.UUID_charity
+ WHERE user_id = ?;
+ """;
+ PreparedStatement stmt = conn.prepareStatement(sql_query);
+ stmt.setString(1, user_id);
+ ResultSet rs = stmt.executeQuery();
+
+ while (rs.next()) {
+ Charity fromCharity = null;
+ String charityId = rs.getString("UUID_charities");
+ if (charityId != null) {
+ fromCharity =
+ new Charity(
+ charityId,
+ rs.getString("org_number"),
+ rs.getString("charity_name"),
+ rs.getString("charity_link"),
+ rs.getString("status"),
+ rs.getBoolean("pre_approved"),
+ rs.getString("description"),
+ rs.getString("logoURL"),
+ rs.getString("key_values"),
+ rs.getBytes("logoBLOB"));
+ }
+
+ if (fromCharity != null) {
+ Message message =
+ new Message(
+ rs.getString("message_title"),
+ fromCharity,
+ rs.getString("message_content"),
+ LocalDate.parse(rs.getString("message_date")));
+ inbox.addMessage(message);
+ }
+ }
+ } catch (SQLException e) {
+ e.printStackTrace();
+ throw new RuntimeException("ERROR: Something went wrong during updating charities table.");
+ } finally {
+ conn = null;
+ }
+ return inbox;
+ }
+
+ /**
+ * Retrieves the {@link Settings} for a specific user by their UUID.
+ *
+ *
At most one row is fetched (via {@code setMaxRows(1)}). Returns {@code null} if no settings
+ * row exists for the given user.
+ *
+ * @param user_id the UUID string of the user whose settings should be retrieved; must not be
+ * {@code null}
+ * @return the user's {@link Settings}, or {@code null} if none are found
+ * @throws RuntimeException if a {@link SQLException} occurs while executing the query
+ */
+ public Settings getSettingsForUser(String user_id) {
+ Settings settings = null;
+ Connection conn = null;
+ try {
+ conn = connection.getMySqlConnection();
+ String sql_query =
+ """
+ SELECT UUID_user, isAnonymous, language, lightmode FROM Settings
+ WHERE UUID_user = ?;
+ """;
+ PreparedStatement stmt = conn.prepareStatement(sql_query);
+ stmt.setString(1, user_id);
+ stmt.setMaxRows(1);
+ ResultSet rs = stmt.executeQuery();
+
+ while (rs.next()) {
+ settings =
+ new Settings(
+ rs.getBoolean("isAnonymous"),
+ Language.valueOf(rs.getString("language").toUpperCase()),
+ rs.getBoolean("lightmode"));
+ }
+ } catch (SQLException e) {
+ e.printStackTrace();
+ throw new RuntimeException("ERROR: Something went wrong during updating charities table.");
+ } finally {
+ conn = null;
+ }
+
+ return settings;
+ }
+
+ /**
+ * Gets the user and settings information and sends it to the database through MySQL.
+ *
+ * @param user the user to be saved in the database.
+ * @throws RuntimeException if a database error occurs during the operation
+ * @return true or false based on if the register is a success or not
+ */
+ public boolean registerUser(User user) {
+ String userSql =
+ """
+ INSERT INTO User (
+ UUID_User,
+ user_name,
+ user_email,
+ user_password,
+ role
+ )
+ VALUES (?, ?, ?, ?, ?)
+ """;
+
+ String settingsSql =
+ """
+ INSERT INTO Settings (
+ UUID_user,
+ isAnonymous,
+ language,
+ lightmode
+ )
+ VALUES (?, ?, ?, ?)
+ """;
+
+ int psUserRows = 0;
+ int psSettingsRows = 0;
+
+ try (Connection conn = connection.getMySqlConnection()) {
+
+ conn.setAutoCommit(false);
+
+ try (PreparedStatement psUser = conn.prepareStatement(userSql)) {
+
+ psUser.setString(1, user.getId().toString());
+ psUser.setString(2, user.getUsername());
+ psUser.setString(3, user.getEmail());
+ psUser.setString(4, user.getPasswordHash());
+ psUser.setString(5, user.getRole().name());
+
+ psUserRows = psUser.executeUpdate();
+ }
+
+ try (PreparedStatement psSettings = conn.prepareStatement(settingsSql)) {
+
+ psSettings.setString(1, user.getId().toString());
+ psSettings.setBoolean(2, user.getSettings().isAnonymous());
+ psSettings.setString(3, user.getSettings().getLanguage().name());
+ psSettings.setBoolean(4, user.getSettings().isLightMode());
+
+ psSettingsRows = psSettings.executeUpdate();
+ }
+
+ conn.commit();
+
+ return psUserRows > 0 && psSettingsRows > 0;
+ } catch (SQLException e) {
+ e.printStackTrace();
+ return false;
+ }
+ }
+
+ /**
+ * Updates the Users settings in the {@code Settings} table. New values are stored in the Settings
+ * object.
+ *
+ * @param user User that is going to recieve changes
+ * @param settings Settings object containing new attributes
+ * @return True or False based on if the update succeed or not
+ */
+ public boolean updateUserSettings(User user, Settings settings) {
+ Connection conn = null;
+ String sql =
+ """
+ UPDATE Settings SET
+ isAnonymous = ?,
+ language = ?,
+ lightmode = ?
+ WHERE UUID_user = ?;
+ """;
+ try {
+ conn = connection.getMySqlConnection();
+ PreparedStatement ps = conn.prepareStatement(sql);
+
+ ps.setBoolean(1, settings.isAnonymous());
+ ps.setString(2, settings.getLanguage().name());
+ ps.setBoolean(3, settings.isLightMode());
+ ps.setString(4, user.getId().toString());
+
+ return ps.executeUpdate() > 0;
+
+ } catch (SQLException e) {
+ e.printStackTrace();
+ System.out.println("Something went wrong when updating Settings");
+ return false;
+ }
+ }
+
+ /**
+ * Updates the Users name, email and password in the {@code User} table. New values are stored in
+ * oncoming User param.
+ *
+ * @param user User that is going to recieve changes
+ * @return True or False based on if the update succeed or not
+ */
+ public boolean updateUserDetails(User user) {
+ Connection conn = null;
+ String sql =
+ """
+ UPDATE User SET
+ user_name = ?,
+ user_email = ?,
+ user_password = ?
+ WHERE UUID_User = ?;
+ """;
+ try {
+ conn = connection.getMySqlConnection();
+ PreparedStatement ps = conn.prepareStatement(sql);
+
+ ps.setString(1, user.getUsername());
+ ps.setString(2, user.getEmail());
+ ps.setString(3, user.getPasswordHash());
+ ps.setString(4, user.getId().toString());
+
+ return ps.executeUpdate() > 0;
+
+ } catch (SQLException e) {
+ e.printStackTrace();
+ System.out.println("Something went wrong when updating Settings");
+ return false;
+ }
+ }
+}
diff --git a/helpmehelpapplication/src/main/java/ntnu/systemutvikling/team6/database/DatabaseManager.java b/helpmehelpapplication/src/main/java/ntnu/systemutvikling/team6/database/DatabaseManager.java
deleted file mode 100644
index a4007a05..00000000
--- a/helpmehelpapplication/src/main/java/ntnu/systemutvikling/team6/database/DatabaseManager.java
+++ /dev/null
@@ -1,299 +0,0 @@
-package ntnu.systemutvikling.team6.database;
-
-import java.sql.*;
-import java.util.List;
-import java.util.UUID;
-import ntnu.systemutvikling.team6.models.Charity;
-import ntnu.systemutvikling.team6.models.CharityRegistry;
-import ntnu.systemutvikling.team6.models.Donation;
-import ntnu.systemutvikling.team6.models.DonationRegistry;
-import ntnu.systemutvikling.team6.scraper.APICharityData;
-
-/**
- * Manages the Database with MySQL tables and JDBC.
- *
- *
This class is responsible for creating the tables needed for the application, if not done
- * already and maintaining the {@code charities} table based on data retrieved from the IK API. It
- * is also responsible for retrieving the data from the database and sending it to the application
- * through the CharityRegistry and DonationRegistry. It is used by the FrontpageController to
- * retrieve the data needed to display the charities
- */
-public class DatabaseManager {
- private final DatabaseConnection connection;
-
- /**
- * Contractor for DatabaseManager. It uses a DatabaseConnection object that contains a connection
- * credentials.
- */
- public DatabaseManager() {
- this.connection = new DatabaseConnection();
- }
-
- /**
- * Connection test for the Database. Does a simple SELECT SQL query and returns either true og and
- * Exception if failed
- *
- * @return true if Sucsedd or SQLExepction if failed
- */
- public boolean testConnection() {
- try (Connection conn = connection.getMySqlConnection();
- Statement stmt = conn.createStatement()) {
-
- ResultSet rs = stmt.executeQuery("SELECT 1");
-
- if (rs.next()) {
- System.out.println("Database connection verified.");
- return true;
- }
-
- } catch (SQLException e) {
- System.out.println("Database connection failed.");
- e.printStackTrace();
- }
-
- return false;
- }
-
- /**
- * Creates the {@code Charities} and {@code Donations} tables if they do not already exist.
- *
- *
The table structure for Charities is based on fields from {@link APICharityData}.
- *
- * @throws RuntimeException if a {@link SQLException} occurs while creating the table
- */
- public void createTables() {
- String sql_query1 =
- """
- -- -----------------------------------------------------
- -- Table `HelpMeHelp`.`Charities`
- -- -----------------------------------------------------
- CREATE TABLE IF NOT EXISTS Charities (
- UUID_charities CHAR(36) PRIMARY KEY,
- org_number VARCHAR(255) NOT NULL,
- charity_name VARCHAR(255) NOT NULL,
- charity_link VARCHAR(255) NOT NULL,
- pre_approved TINYINT NOT NULL,
- status VARCHAR(255) NOT NULL,
- UNIQUE KEY unique_org_number (org_number)
- ) ENGINE=InnoDB;
-
-
- """;
- String sql_query2 =
- """
- -- -----------------------------------------------------
- -- Table `HelpMeHelp`.`Donations`
- -- -----------------------------------------------------
- CREATE TABLE IF NOT EXISTS Donations (
- `UUID_Donations` CHAR(36) NOT NULL,
- `amount` DECIMAL NOT NULL,
- `date` DATE NOT NULL,
- `Charities_UUID_charities` CHAR(36) NOT NULL,
- PRIMARY KEY (`UUID_Donations`),
- INDEX `fk_Donations_Charities_idx` (`Charities_UUID_charities` ASC) VISIBLE,
- CONSTRAINT `fk_Donations_Charities`
- FOREIGN KEY (`Charities_UUID_charities`)
- REFERENCES Charities (`UUID_charities`)
- ON DELETE CASCADE
- ON UPDATE CASCADE)
- ENGINE = InnoDB;
- """;
-
- try (Connection conn = connection.getMySqlConnection();
- Statement s = conn.createStatement()) {
-
- s.execute(sql_query1);
- s.execute(sql_query2);
- } catch (SQLException e) {
- e.printStackTrace();
- throw new RuntimeException("Error creating table.");
- }
- }
-
- /**
- * This method is used to verify the integrity of the data in the {@code charities} table and to
- * update it based on the data retrieved from the IK API. The param charities are retrieved from
- * the IK API through the APICharityData class. Called in initialize method in
- * HmHApplication.java, which is the main class of the application, to ensure that the data is up
- * to date when the application starts. Uses a a temp table to ensure that the data in the
- * database is consistent with the data from the API.
- *
- * @param charities
- */
- public void addAPIDataToTable(List charities) {
- Connection conn = null;
- try {
- conn = connection.getMySqlConnection();
- conn.setAutoCommit(false);
- String sql_query =
- """
- INSERT INTO Charities (UUID_charities, org_number, charity_name, charity_link, pre_approved, status)
- VALUES (?, ?, ?, ?, ?, ?)
- ON DUPLICATE KEY UPDATE
- charity_name = VALUES(charity_name),
- charity_link = VALUES(charity_link),
- pre_approved = VALUES(pre_approved),
- status = VALUES(status)
- """;
-
- try (PreparedStatement ps = conn.prepareStatement(sql_query)) {
- for (Charity charity : charities) {
- if (charity.getUUID() == null) {
- ps.setString(1, UUID.randomUUID().toString());
- } else {
- ps.setString(1, charity.getUUID().toString());
- }
-
- ps.setString(2, charity.getOrg_number().replaceAll("\\s", ""));
- ps.setString(3, charity.getName());
- ps.setString(4, charity.getDescription());
- ps.setBoolean(5, charity.getPreApproved()); // Description is the link
- ps.setString(6, charity.getStatus());
-
- ps.addBatch();
- }
- ps.executeBatch();
- }
-
- // -- Intergerty Check:
- String createTemp =
- """
- CREATE TEMPORARY TABLE temp_api_charities (
- org_number VARCHAR(20) PRIMARY KEY
- )
- """;
-
- try (PreparedStatement ps = conn.prepareStatement(createTemp)) {
- ps.execute();
- }
-
- String insertTemp = "INSERT IGNORE INTO temp_api_charities (org_number) VALUES (?)";
-
- try (PreparedStatement ps = conn.prepareStatement(insertTemp)) {
-
- for (Charity charity : charities) {
- ps.setString(1, charity.getOrg_number().replaceAll("\\s", ""));
- ps.addBatch();
- }
-
- ps.executeBatch();
- }
-
- String deleteSql =
- """
- DELETE FROM Charities c
- WHERE NOT EXISTS (
- SELECT 1
- FROM temp_api_charities t
- WHERE t.org_number = c.org_number
- )
- """;
-
- try (PreparedStatement ps = conn.prepareStatement(deleteSql)) {
- ps.executeUpdate();
- }
-
- conn.commit();
-
- } catch (SQLException e) {
- if (conn != null) {
- try {
- conn.rollback();
- } catch (SQLException ex) {
- ex.printStackTrace();
- }
- }
- e.printStackTrace();
-
- throw new RuntimeException("ERROR: Something went wrong during updating charities table.");
- } finally {
- if (conn != null) {
- try {
- conn.setAutoCommit(true);
- conn.close();
- } catch (SQLException e) {
- e.printStackTrace();
- }
- }
- }
- }
-
- public CharityRegistry getCharitiesFromDB() {
- CharityRegistry registry = null;
- Connection conn = null;
- try {
- conn = connection.getMySqlConnection();
- String sql_query =
- "SELECT UUID_charities, org_number, charity_name, charity_link, pre_approved, status FROM Charities";
- Statement stmt = conn.createStatement();
- ResultSet rs = stmt.executeQuery(sql_query);
-
- registry = new CharityRegistry();
- while (rs.next()) {
- Charity charity =
- new Charity(
- rs.getString("UUID_charities"),
- rs.getString("org_number"),
- rs.getString("charity_link"),
- rs.getString("charity_name"),
- rs.getBoolean("pre_approved"),
- rs.getString("status"));
- registry.addCharity(charity);
- }
- } catch (SQLException e) {
- e.printStackTrace();
- throw new RuntimeException("ERROR: Something went wrong during updating charities table.");
- }
- return registry;
- }
-
- public DonationRegistry getDonationFromDB() {
- DonationRegistry registry = null;
- Connection conn = null;
- try {
- conn = connection.getMySqlConnection();
- String sql_query =
- """
- SELECT
- d.UUID_Donations,
- d.amount,
- d.date,
- c.UUID_charities,
- c.org_number,
- c.charity_name,
- c.charity_link,
- c.pre_approved,
- c.status
- FROM Donations d
- JOIN Charities c
- ON d.Charities_UUID_charities = c.UUID_charities
- """;
- Statement stmt = conn.createStatement();
- ResultSet rs = stmt.executeQuery(sql_query);
-
- registry = new DonationRegistry();
- while (rs.next()) {
- Charity charity =
- new Charity(
- rs.getString("UUID_charities"),
- rs.getString("org_number"),
- rs.getString("charity_name"),
- rs.getString("charity_link"),
- rs.getBoolean("pre_approved"),
- rs.getString("status"));
-
- Donation donation =
- new Donation(
- rs.getString("UUID_Donations"),
- rs.getDouble("amount"),
- rs.getDate("date").toLocalDate(),
- charity);
- registry.addDonation(donation);
- }
- } catch (SQLException e) {
- e.printStackTrace();
- throw new RuntimeException("ERROR: Something went wrong during updating charities table.");
- }
- return registry;
- }
-}
diff --git a/helpmehelpapplication/src/main/java/ntnu/systemutvikling/team6/database/DatabaseSetup.java b/helpmehelpapplication/src/main/java/ntnu/systemutvikling/team6/database/DatabaseSetup.java
new file mode 100644
index 00000000..8d8d69bb
--- /dev/null
+++ b/helpmehelpapplication/src/main/java/ntnu/systemutvikling/team6/database/DatabaseSetup.java
@@ -0,0 +1,317 @@
+package ntnu.systemutvikling.team6.database;
+
+import java.sql.*;
+import ntnu.systemutvikling.team6.models.*;
+import ntnu.systemutvikling.team6.models.user.*;
+
+/**
+ * Manages the Database with MySQL tables and test connection.
+ *
+ *
This class object is able to create MySQL to ntnu localized database and able to
+ * testConnection to it.
+ */
+public class DatabaseSetup {
+ private final DatabaseConnection connection;
+
+ /**
+ * Contractor for DatabaseManager. It uses a DatabaseConnection object that contains a connection
+ * credentials.
+ */
+ public DatabaseSetup(DatabaseConnection connection) {
+ this.connection = connection;
+ }
+
+ /**
+ * Connection test for the Database. Does a simple SELECT SQL query and returns either true og and
+ * Exception if failed
+ *
+ * @return true if Sucsedd or SQLExepction if failed
+ */
+ public boolean testConnection() {
+ try (Connection conn = connection.getMySqlConnection();
+ Statement stmt = conn.createStatement()) {
+
+ ResultSet rs = stmt.executeQuery("SELECT 1");
+
+ if (rs.next()) {
+ System.out.println("Database connection verified.");
+ return true;
+ }
+
+ } catch (SQLException e) {
+ System.out.println("Database connection failed.");
+ e.printStackTrace();
+ }
+ return false;
+ }
+
+ /**
+ * Creates the {@code Charities} and {@code Donations} tables if they do not already exist.
+ *
+ * @throws RuntimeException if a {@link SQLException} occurs while creating the table
+ */
+ public void createTables() {
+ String charitiesTable =
+ """
+ -- -----------------------------------------------------
+ -- Table `HelpMeHelp`.`Charities`
+ -- -----------------------------------------------------
+ CREATE TABLE IF NOT EXISTS `apbaluna`.`Charities` (
+ `UUID_charities` CHAR(36) NOT NULL,
+ `org_number` VARCHAR(255) NOT NULL,
+ `pre_approved` TINYINT NOT NULL,
+ `status` VARCHAR(255) NOT NULL,
+ PRIMARY KEY (`UUID_charities`),
+ UNIQUE INDEX `org_number_UNIQUE` (`org_number` ASC) VISIBLE)
+ ENGINE = InnoDB;
+ """;
+ String donationsTable =
+ """
+ -- -----------------------------------------------------
+ -- Table `HelpMeHelp`.`Donations`
+ -- -----------------------------------------------------
+ CREATE TABLE IF NOT EXISTS `apbaluna`.`Donations` (
+ `UUID_Donations` CHAR(36) NOT NULL,
+ `amount` DECIMAL NOT NULL,
+ `isAnonymous` TINYINT NOT NULL,
+ `date` DATE NOT NULL,
+ `charity_id` CHAR(36) NOT NULL,
+ `user_id` CHAR(36) NOT NULL,
+ PRIMARY KEY (`UUID_Donations`),
+ INDEX `fk_Donations_Charities_idx` (`charity_id` ASC) VISIBLE,
+ INDEX `fk_Donations_User1_idx` (`user_id` ASC) VISIBLE,
+ CONSTRAINT `fk_Donations_Charities`
+ FOREIGN KEY (`charity_id`)
+ REFERENCES `apbaluna`.`Charities` (`UUID_charities`)
+ ON DELETE NO ACTION
+ ON UPDATE NO ACTION,
+ CONSTRAINT `fk_Donations_User1`
+ FOREIGN KEY (`user_id`)
+ REFERENCES `apbaluna`.`User` (`UUID_User`)
+ ON DELETE NO ACTION
+ ON UPDATE NO ACTION)
+ ENGINE = InnoDB;
+ """;
+ String userTable =
+ """
+ -- -----------------------------------------------------
+ -- Table `apbaluna`.`User`
+ -- -----------------------------------------------------
+ CREATE TABLE IF NOT EXISTS `apbaluna`.`User` (
+ `UUID_User` CHAR(36) NOT NULL,
+ `user_name` VARCHAR(255) NOT NULL,
+ `user_email` VARCHAR(255) NOT NULL,
+ `user_password` VARCHAR(255) NOT NULL,
+ `role` VARCHAR(45) NOT NULL,
+ PRIMARY KEY (`UUID_User`))
+ ENGINE = InnoDB;
+ """;
+
+ String settingsTable =
+ """
+ -- -----------------------------------------------------
+ -- Table `apbaluna`.`Settings`
+ -- -----------------------------------------------------
+ CREATE TABLE IF NOT EXISTS `apbaluna`.`Settings` (
+ `UUID_user` CHAR(36) NOT NULL,
+ `isAnonymous` TINYINT NOT NULL,
+ `language` VARCHAR(45) NOT NULL,
+ `lightmode` TINYINT NOT NULL,
+ PRIMARY KEY (`UUID_user`),
+ INDEX `fk_Settings_User1_idx` (`UUID_user` ASC) VISIBLE,
+ CONSTRAINT `fk_Settings_User1`
+ FOREIGN KEY (`UUID_user`)
+ REFERENCES `apbaluna`.`User` (`UUID_User`)
+ ON DELETE NO ACTION
+ ON UPDATE NO ACTION)
+ ENGINE = InnoDB;
+ """;
+
+ String messagesTable =
+ """
+ -- -----------------------------------------------------
+ -- Table `apbaluna`.`Messages`
+ -- -----------------------------------------------------
+ CREATE TABLE IF NOT EXISTS `apbaluna`.`Messages` (
+ `UUID_message` CHAR(36) NOT NULL,
+ `message_title` VARCHAR(255) NOT NULL,
+ `message_content` VARCHAR(255) NOT NULL,
+ `message_date` DATE NOT NULL,
+ `sender_user_id` CHAR(36) NULL,
+ `sender_charity_id` CHAR(36) NULL,
+ `user_id` CHAR(36) NOT NULL,
+ PRIMARY KEY (`UUID_message`),
+ INDEX `fk_Messages_User1_idx` (`sender_user_id` ASC) VISIBLE,
+ INDEX `fk_Messages_Charities1_idx` (`sender_charity_id` ASC) VISIBLE,
+ INDEX `fk_Messages_User2_idx` (`user_id` ASC) VISIBLE,
+ CONSTRAINT `fk_Messages_User1`
+ FOREIGN KEY (`sender_user_id`)
+ REFERENCES `apbaluna`.`User` (`UUID_User`)
+ ON DELETE NO ACTION
+ ON UPDATE NO ACTION,
+ CONSTRAINT `fk_Messages_Charities1`
+ FOREIGN KEY (`sender_charity_id`)
+ REFERENCES `apbaluna`.`Charities` (`UUID_charities`)
+ ON DELETE NO ACTION
+ ON UPDATE NO ACTION,
+ CONSTRAINT `fk_Messages_User2`
+ FOREIGN KEY (`user_id`)
+ REFERENCES `apbaluna`.`User` (`UUID_User`)
+ ON DELETE NO ACTION
+ ON UPDATE NO ACTION)
+ ENGINE = InnoDB;
+ """;
+
+ String feedbackTable =
+ """
+ -- -----------------------------------------------------
+ -- Table `apbaluna`.`Feedback`
+ -- -----------------------------------------------------
+ CREATE TABLE IF NOT EXISTS `apbaluna`.`Feedback` (
+ `UUID_feedback` CHAR(36) NOT NULL,
+ `feedback_comment` VARCHAR(255) NOT NULL,
+ `feedback_date` DATE NOT NULL,
+ `isAnonymous` TINYINT NOT NULL,
+ `charity_id` CHAR(36) NOT NULL,
+ `user_id` CHAR(36) NOT NULL,
+ PRIMARY KEY (`UUID_feedback`),
+ INDEX `fk_Feedback_Charities1_idx` (`charity_id` ASC) VISIBLE,
+ INDEX `fk_Feedback_User1_idx` (`user_id` ASC) VISIBLE,
+ CONSTRAINT `fk_Feedback_Charities1`
+ FOREIGN KEY (`charity_id`)
+ REFERENCES `apbaluna`.`Charities` (`UUID_charities`)
+ ON DELETE NO ACTION
+ ON UPDATE NO ACTION,
+ CONSTRAINT `fk_Feedback_User1`
+ FOREIGN KEY (`user_id`)
+ REFERENCES `apbaluna`.`User` (`UUID_User`)
+ ON DELETE NO ACTION
+ ON UPDATE NO ACTION)
+ ENGINE = InnoDB;
+ """;
+ String categoriesTable =
+ """
+ -- -----------------------------------------------------
+ -- Table `apbaluna`.`Categories`
+ -- -----------------------------------------------------
+ CREATE TABLE IF NOT EXISTS `apbaluna`.`Categories` (
+ `category_id` INT NOT NULL AUTO_INCREMENT,
+ `category` VARCHAR(255) NOT NULL,
+ PRIMARY KEY (`category_id`),
+ UNIQUE (`category`)
+ )
+ ENGINE = InnoDB;
+ """;
+
+ String charityCategoriesTable =
+ """
+ -- -----------------------------------------------------
+ -- Table `apbaluna`.`Charity_Categories`
+ -- -----------------------------------------------------
+ CREATE TABLE IF NOT EXISTS `apbaluna`.`Charity_Categories` (
+ `Categories_category_id` INT NOT NULL,
+ `Charities_UUID_charities` CHAR(36) NOT NULL,
+ PRIMARY KEY (`Categories_category_id`, `Charities_UUID_charities`),
+ INDEX `fk_Categories_has_Charities_Charities1_idx` (`Charities_UUID_charities` ASC) VISIBLE,
+ INDEX `fk_Categories_has_Charities_Categories1_idx` (`Categories_category_id` ASC) VISIBLE,
+ CONSTRAINT `fk_Categories_has_Charities_Categories1`
+ FOREIGN KEY (`Categories_category_id`)
+ REFERENCES `apbaluna`.`Categories` (`category_id`)
+ ON DELETE NO ACTION
+ ON UPDATE NO ACTION,
+ CONSTRAINT `fk_Categories_has_Charities_Charities1`
+ FOREIGN KEY (`Charities_UUID_charities`)
+ REFERENCES `apbaluna`.`Charities` (`UUID_charities`)
+ ON DELETE CASCADE
+ ON UPDATE CASCADE)
+ ENGINE = InnoDB;
+ """;
+
+ String charityUserTable =
+ """
+ -- -----------------------------------------------------
+ -- Table `apbaluna`.`CharityUsers`
+ -- -----------------------------------------------------
+ CREATE TABLE IF NOT EXISTS `apbaluna`.`CharityUsers` (
+ `TheCharity` CHAR(36) NOT NULL,
+ `CharityUserId` CHAR(36) NOT NULL,
+ PRIMARY KEY (`TheCharity`, `CharityUserId`),
+ INDEX `fk_Charities_has_User_User1_idx` (`CharityUserId` ASC) VISIBLE,
+ INDEX `fk_Charities_has_User_Charities1_idx` (`TheCharity` ASC) VISIBLE,
+ CONSTRAINT `fk_Charities_has_User_Charities1`
+ FOREIGN KEY (`TheCharity`)
+ REFERENCES `apbaluna`.`Charities` (`UUID_charities`)
+ ON DELETE NO ACTION
+ ON UPDATE NO ACTION,
+ CONSTRAINT `fk_Charities_has_User_User1`
+ FOREIGN KEY (`CharityUserId`)
+ REFERENCES `apbaluna`.`User` (`UUID_User`)
+ ON DELETE NO ACTION
+ ON UPDATE NO ACTION)
+ ENGINE = InnoDB;
+ """;
+ String charityVanityTable =
+ """
+ CREATE TABLE IF NOT EXISTS `apbaluna`.`CharityVanity` (
+ `UUID_charity` CHAR(36) NOT NULL,
+ `charity_name` VARCHAR(255) NOT NULL,
+ `charity_link` VARCHAR(255) NOT NULL,
+ `description` TEXT NULL,
+ `logoURL` TEXT NULL,
+ `key_values` TEXT NULL,
+ `logoBLOB` MEDIUMBLOB NULL,
+ CONSTRAINT `unique_UUID_charity` UNIQUE (`UUID_charity`),
+ INDEX `fk_CharityVanity_Charities1_idx` (`UUID_charity` ASC) VISIBLE,
+ CONSTRAINT `fk_CharityVanity_Charities1`
+ FOREIGN KEY (`UUID_charity`)
+ REFERENCES `apbaluna`.`Charities` (`UUID_charities`)
+ ON DELETE CASCADE
+ ON UPDATE CASCADE)
+ ENGINE = InnoDB;
+ """;
+
+ String userHasFavourites =
+ """
+ -- -----------------------------------------------------
+ -- Table `apbaluna`.`User_has_favourites`
+ -- -----------------------------------------------------
+ CREATE TABLE IF NOT EXISTS `apbaluna`.`User_has_favourites` (
+ `Favourite_Charity` CHAR(36) NOT NULL,
+ `Favourer` CHAR(36) NOT NULL,
+ PRIMARY KEY (`Favourite_Charity`, `Favourer`),
+ INDEX `fk_Charities_has_User_User2_idx` (`Favourer` ASC) VISIBLE,
+ INDEX `fk_Charities_has_User_Charities2_idx` (`Favourite_Charity` ASC) VISIBLE,
+ CONSTRAINT `fk_Charities_has_User_Charities2`
+ FOREIGN KEY (`Favourite_Charity`)
+ REFERENCES `apbaluna`.`Charities` (`UUID_charities`)
+ ON DELETE NO ACTION
+ ON UPDATE NO ACTION,
+ CONSTRAINT `fk_Charities_has_User_User2`
+ FOREIGN KEY (`Favourer`)
+ REFERENCES `apbaluna`.`User` (`UUID_User`)
+ ON DELETE NO ACTION
+ ON UPDATE NO ACTION)
+ ENGINE = InnoDB;
+ """;
+
+ try (Connection conn = connection.getMySqlConnection();
+ Statement s = conn.createStatement()) {
+
+ s.execute(charitiesTable);
+ s.execute(userTable);
+ s.execute(donationsTable);
+ s.execute(settingsTable);
+ s.execute(messagesTable);
+ s.execute(feedbackTable);
+ s.execute(categoriesTable);
+ s.execute(charityCategoriesTable);
+ s.execute(charityUserTable);
+ s.execute(charityVanityTable);
+ s.execute(userHasFavourites);
+
+ } catch (SQLException e) {
+ e.printStackTrace();
+ throw new RuntimeException("Error creating table.");
+ }
+ }
+}
diff --git a/helpmehelpapplication/src/main/java/ntnu/systemutvikling/team6/models/Charity.java b/helpmehelpapplication/src/main/java/ntnu/systemutvikling/team6/models/Charity.java
index d97d9968..4516abba 100644
--- a/helpmehelpapplication/src/main/java/ntnu/systemutvikling/team6/models/Charity.java
+++ b/helpmehelpapplication/src/main/java/ntnu/systemutvikling/team6/models/Charity.java
@@ -20,8 +20,8 @@ public class Charity {
/* Name of the charity */
private String name;
- /* Description of the charity's mission and activities */
- private String description;
+ /* URL of the charity */
+ private String url;
/* Is the charity verified? */
private String status;
@@ -29,11 +29,40 @@ public class Charity {
private boolean is_pre_approved;
/* Category for the charity */
- private String category;
+ private List category;
+
+ /* Description for the charity */
+ private String description;
+
+ /* URL for the logo of the charity */
+ private String logoURL;
+
+ /* Key values for the charity */
+ private String keyValues;
/* List that contains the charity's Feedbacks */
private List feedbacks;
+ /* Bytecode for the charity logo */
+ private byte[] logoBlob;
+
+ /**
+ * Minimal contructor JUST FOR DONATIONSSELECT. Just cause donation object needs to only contain
+ * information about receiver {@code Chairty } and donator {@code User}, and not necessarily Urls,
+ * logos, and etc.
+ *
+ * @param uuid from DonationSelect
+ * @param org_number matches from DonationSelect
+ * @param is_pre_approved name matches from DonationSelect
+ * @param status name matches from DonationSelect
+ */
+ public Charity(String uuid, String org_number, Boolean is_pre_approved, String status) {
+ this.UUID = java.util.UUID.fromString(uuid);
+ this.org_number = org_number.replaceAll("\\s", "");
+ this.is_pre_approved = is_pre_approved;
+ this.status = status;
+ }
+
/**
* Contructor for creating a new charity. Taylored to match data given from Api. Other attributes
* will just be initialized as empty
@@ -45,19 +74,21 @@ public class Charity {
*/
public Charity(
String org_number, String link, String name, boolean is_pre_approved, String status) {
- this.UUID = java.util.UUID.randomUUID();
+ UUID stableId = UUID.nameUUIDFromBytes((org_number + link + name).getBytes());
+ this.UUID = stableId;
this.org_number = org_number.replaceAll("\\s", "");
this.name = name;
- this.description = "Les mer her: " + link;
+ this.url = link;
this.is_pre_approved = is_pre_approved;
this.status = status;
this.feedbacks = new ArrayList<>();
- this.category = "";
+ this.category = new ArrayList<>();
}
/**
- * Contructor for creating a new charity. Taylored to match data given from DATABASE. Other
- * attributes will just be initialized as empty
+ * Contructor for creating a new charity. Taylored to match data given from DATABASE. Expects
+ * paramaters that will fill all attributes. EXECPT for feedbacks and categories (which is done
+ * right after).
*
* @param org_number matches from innsamlingkontrollen
* @param name matches from innsamlingkontrollen
@@ -67,18 +98,26 @@ public Charity(
public Charity(
String uuid,
String org_number,
- String link,
String name,
+ String url,
+ String status,
boolean is_pre_approved,
- String status) {
+ String description,
+ String logoURL,
+ String keyValues,
+ byte[] logblob) {
this.UUID = UUID.fromString(uuid);
this.org_number = org_number.replaceAll("\\s", "");
this.name = name;
- this.description = link;
+ this.url = url;
this.is_pre_approved = is_pre_approved;
this.status = status;
+ this.category = new ArrayList<>();
+ this.description = description;
+ this.logoURL = logoURL;
+ this.keyValues = keyValues;
this.feedbacks = new ArrayList<>();
- this.category = "";
+ this.logoBlob = logblob;
}
/** Getters for the charity's attributes. */
@@ -102,7 +141,7 @@ public List getFeedbacks() {
return feedbacks;
}
- public String getCategory() {
+ public List getCategory() {
return category;
}
@@ -110,8 +149,29 @@ public String getName() {
return name;
}
+ public String getURL() {
+ return this.url;
+ }
+
public String getDescription() {
- return description;
+ return this.description;
+ }
+
+ public String getLogoURL() {
+ return this.logoURL;
+ }
+
+ public String getKeyValues() {
+ return this.keyValues;
+ }
+
+ public byte[] getLogoBlob() {
+ return this.logoBlob;
+ }
+
+ /** Setter for new name */
+ public void setName(String name) {
+ this.name = name;
}
/** Setter for verification status. This one sets the charity as verified. */
@@ -123,4 +183,38 @@ public void setVerified() {
public void setUnverified() {
this.status = "Veto";
}
+
+ /** Setter for categories. */
+ public void setCategory(List category) {
+ this.category = category;
+ }
+
+ /** Setter for description. */
+ public void setDescription(String description) {
+ this.description = description;
+ }
+
+ /** Setter for the URL of the charity's logo. */
+ public void setLogoURL(String url) {
+ this.logoURL = url;
+ }
+
+ /** Setter for the charity's key values. */
+ public void setKeyValues(String values) {
+ this.keyValues = values;
+ }
+
+ /** Setter for the charity's logo Blob. */
+ public void setLogoBlob(byte[] logoBlob) {
+ this.logoBlob = logoBlob;
+ }
+
+ /** Setter for setting feedbacks */
+ public void setFeedbacks(ArrayList feedbacks) {
+ this.feedbacks = feedbacks;
+ }
+
+ public void setUUIDFromString(String uuid) {
+ this.UUID = java.util.UUID.fromString(uuid);
+ }
}
diff --git a/helpmehelpapplication/src/main/java/ntnu/systemutvikling/team6/models/Donation.java b/helpmehelpapplication/src/main/java/ntnu/systemutvikling/team6/models/Donation.java
index 64d87331..c058688a 100644
--- a/helpmehelpapplication/src/main/java/ntnu/systemutvikling/team6/models/Donation.java
+++ b/helpmehelpapplication/src/main/java/ntnu/systemutvikling/team6/models/Donation.java
@@ -6,7 +6,7 @@
public class Donation {
/* UUID for uniquely identifying each donation */
- private UUID charityId;
+ private UUID donationID;
/* Ammount of money donated */
private double amount;
@@ -33,7 +33,7 @@ public class Donation {
* @param donor
*/
public Donation(double amount, LocalDate date, Charity charity, User donor) {
- this.charityId = UUID.randomUUID();
+ this.donationID = UUID.randomUUID();
this.amount = amount;
this.date = date;
this.charity = charity;
@@ -42,21 +42,28 @@ public Donation(double amount, LocalDate date, Charity charity, User donor) {
}
/**
- * Constructor for creating a new donation. Taylored for getting info FROM DATABASE. NEEDS TO BE
- * CHANGED in phase 3.
+ * Constructor for creating a donation reed from the database.
*
- * @param amount
- * @param date
- * @param charity
- * @param uuid
+ * @param donationId the stored UUID string for this donation; must not be {@code null}
+ * @param amount the donated amount
+ * @param date the date the donation was made; must not be {@code null}
+ * @param charity the receiving charity; must not be {@code null}
+ * @param donor the donating user, or {@code null} if anonymous
+ * @param isAnonymous whether the donation was recorded as anonymous
*/
- public Donation(String uuid, double amount, LocalDate date, Charity charity) {
- this.charityId = UUID.fromString(uuid);
+ public Donation(
+ String donationId,
+ double amount,
+ LocalDate date,
+ Charity charity,
+ User donor,
+ boolean isAnonymous) {
+ this.donationID = UUID.fromString(donationId);
this.amount = amount;
this.date = date;
this.charity = charity;
- this.donor = null;
- this.isAnonymous = true;
+ this.donor = donor;
+ this.isAnonymous = isAnonymous;
}
/* Getters for the donation's attributes */
@@ -65,7 +72,11 @@ public boolean isAnonymous() {
}
public UUID getCharityId() {
- return charityId;
+ return charity.getUUID();
+ }
+
+ public UUID getDonationID() {
+ return donationID;
}
public double getAmount() {
diff --git a/helpmehelpapplication/src/main/java/ntnu/systemutvikling/team6/models/Feedback.java b/helpmehelpapplication/src/main/java/ntnu/systemutvikling/team6/models/Feedback.java
index 490c47e6..6e78c3b2 100644
--- a/helpmehelpapplication/src/main/java/ntnu/systemutvikling/team6/models/Feedback.java
+++ b/helpmehelpapplication/src/main/java/ntnu/systemutvikling/team6/models/Feedback.java
@@ -1,6 +1,6 @@
package ntnu.systemutvikling.team6.models;
-import java.time.LocalDateTime;
+import java.time.LocalDate;
import java.util.UUID;
import ntnu.systemutvikling.team6.models.user.User;
@@ -15,13 +15,13 @@ public class Feedback {
private String comment;
/* The date and time when the feedback was given */
- private LocalDateTime date;
+ private LocalDate date;
/* Is the feedback given anonymously? */
private boolean isAnonymous;
/**
- * Constructor for creating a new feedback.
+ * Constructor for creating a new feedback now.
*
* @param user The user who gives the feedback.
* @param comment The content of the feedback.
@@ -30,7 +30,22 @@ public Feedback(User user, String comment) {
this.feedbackId = UUID.randomUUID();
this.user = user;
this.comment = comment;
- this.date = LocalDateTime.now();
+ this.date = LocalDate.now();
+ this.isAnonymous = user.getSettings().isAnonymous();
+ }
+
+ /**
+ * Constructor for creating a new feedback, based on making a feedback previously made.
+ *
+ * @param user The user who gives the feedback.
+ * @param comment The content of the feedback.
+ * @param feedback_date The content of the feedback.
+ */
+ public Feedback(String feedback_id, User user, String feedback_comment, LocalDate feedback_date) {
+ this.feedbackId = UUID.fromString(feedback_id);
+ this.user = user;
+ this.comment = feedback_comment;
+ this.date = feedback_date;
this.isAnonymous = user.getSettings().isAnonymous();
}
@@ -48,7 +63,7 @@ public String getComment() {
return comment;
}
- public LocalDateTime getDate() {
+ public LocalDate getDate() {
return date;
}
diff --git a/helpmehelpapplication/src/main/java/ntnu/systemutvikling/team6/models/UserRegistry.java b/helpmehelpapplication/src/main/java/ntnu/systemutvikling/team6/models/UserRegistry.java
deleted file mode 100644
index 514cbec1..00000000
--- a/helpmehelpapplication/src/main/java/ntnu/systemutvikling/team6/models/UserRegistry.java
+++ /dev/null
@@ -1,3 +0,0 @@
-package ntnu.systemutvikling.team6.models;
-
-public class UserRegistry {}
diff --git a/helpmehelpapplication/src/main/java/ntnu/systemutvikling/team6/models/CharityRegistry.java b/helpmehelpapplication/src/main/java/ntnu/systemutvikling/team6/models/registry/CharityRegistry.java
similarity index 93%
rename from helpmehelpapplication/src/main/java/ntnu/systemutvikling/team6/models/CharityRegistry.java
rename to helpmehelpapplication/src/main/java/ntnu/systemutvikling/team6/models/registry/CharityRegistry.java
index 962b8338..c6aabbdd 100644
--- a/helpmehelpapplication/src/main/java/ntnu/systemutvikling/team6/models/CharityRegistry.java
+++ b/helpmehelpapplication/src/main/java/ntnu/systemutvikling/team6/models/registry/CharityRegistry.java
@@ -1,6 +1,7 @@
-package ntnu.systemutvikling.team6.models;
+package ntnu.systemutvikling.team6.models.registry;
import java.util.*;
+import ntnu.systemutvikling.team6.models.Charity;
public class CharityRegistry {
private final List charities;
diff --git a/helpmehelpapplication/src/main/java/ntnu/systemutvikling/team6/models/DonationRegistry.java b/helpmehelpapplication/src/main/java/ntnu/systemutvikling/team6/models/registry/DonationRegistry.java
similarity index 81%
rename from helpmehelpapplication/src/main/java/ntnu/systemutvikling/team6/models/DonationRegistry.java
rename to helpmehelpapplication/src/main/java/ntnu/systemutvikling/team6/models/registry/DonationRegistry.java
index b06009dd..d132a710 100644
--- a/helpmehelpapplication/src/main/java/ntnu/systemutvikling/team6/models/DonationRegistry.java
+++ b/helpmehelpapplication/src/main/java/ntnu/systemutvikling/team6/models/registry/DonationRegistry.java
@@ -1,6 +1,7 @@
-package ntnu.systemutvikling.team6.models;
+package ntnu.systemutvikling.team6.models.registry;
import java.util.*;
+import ntnu.systemutvikling.team6.models.Donation;
public class DonationRegistry {
private final List donations;
@@ -18,7 +19,7 @@ public Optional findDonationById(UUID donationId) {
throw new IllegalArgumentException("DonationId can not be null.");
}
return donations.stream()
- .filter(donations -> donationId.equals(donations.getCharityId()))
+ .filter(donations -> donationId.equals(donations.getDonationID()))
.findFirst();
}
@@ -33,6 +34,6 @@ public boolean removeDonation(UUID donationId) {
if (donationId == null) {
throw new IllegalArgumentException("DonationId can not be null.");
}
- return donations.removeIf(donation -> donationId.equals(donation.getCharityId()));
+ return donations.removeIf(donation -> donationId.equals(donation.getDonationID()));
}
}
diff --git a/helpmehelpapplication/src/main/java/ntnu/systemutvikling/team6/models/registry/UserRegistry.java b/helpmehelpapplication/src/main/java/ntnu/systemutvikling/team6/models/registry/UserRegistry.java
new file mode 100644
index 00000000..7aa85fb3
--- /dev/null
+++ b/helpmehelpapplication/src/main/java/ntnu/systemutvikling/team6/models/registry/UserRegistry.java
@@ -0,0 +1,37 @@
+package ntnu.systemutvikling.team6.models.registry;
+
+import java.util.*;
+import ntnu.systemutvikling.team6.models.user.User;
+
+public class UserRegistry {
+ private final List Users;
+
+ public UserRegistry() {
+ this.Users = new ArrayList<>();
+ }
+
+ public List getAllUsers() {
+ return Collections.unmodifiableList(Users);
+ }
+
+ public Optional findUserById(UUID userUUID) {
+ if (userUUID == null) {
+ throw new IllegalArgumentException("DonationId can not be null.");
+ }
+ return Users.stream().filter(u -> userUUID.equals(u.getId())).findFirst();
+ }
+
+ public void addUser(User user) {
+ if (user == null) {
+ throw new IllegalArgumentException("Donation can not be null.");
+ }
+ Users.add(user);
+ }
+
+ public boolean removeUserByUUID(UUID userUUID) {
+ if (userUUID == null) {
+ throw new IllegalArgumentException("DonationId can not be null.");
+ }
+ return Users.removeIf(user -> userUUID.equals(user.getId()));
+ }
+}
diff --git a/helpmehelpapplication/src/main/java/ntnu/systemutvikling/team6/models/user/Language.java b/helpmehelpapplication/src/main/java/ntnu/systemutvikling/team6/models/user/Language.java
index c568ede3..cbd66cf4 100644
--- a/helpmehelpapplication/src/main/java/ntnu/systemutvikling/team6/models/user/Language.java
+++ b/helpmehelpapplication/src/main/java/ntnu/systemutvikling/team6/models/user/Language.java
@@ -6,5 +6,6 @@
* @author Robin Strand Prestmo
*/
public enum Language {
+ NORWEGIAN,
ENGLISH
}
diff --git a/helpmehelpapplication/src/main/java/ntnu/systemutvikling/team6/models/user/Message.java b/helpmehelpapplication/src/main/java/ntnu/systemutvikling/team6/models/user/Message.java
index b70ece1b..ad2370c2 100644
--- a/helpmehelpapplication/src/main/java/ntnu/systemutvikling/team6/models/user/Message.java
+++ b/helpmehelpapplication/src/main/java/ntnu/systemutvikling/team6/models/user/Message.java
@@ -1,7 +1,8 @@
package ntnu.systemutvikling.team6.models.user;
-import java.time.LocalDateTime;
+import java.time.LocalDate;
import java.util.UUID;
+import ntnu.systemutvikling.team6.models.Charity;
// Enhetstester mangler
@@ -13,26 +14,57 @@
public class Message {
private final UUID id;
private final String title;
- private final String from;
+ private final Charity fromCharity;
private final String content;
- private final LocalDateTime timeAndDate;
+ private final LocalDate timeAndDate;
/**
- * Creates a message with a unique identifier. The message includes a title, a string who it's
+ * Creates a message with a unique identifier. The message includes a title, the Charity who it's
* from, content and the time and date.
*
* @param title the title of the message
- * @param from who the message is from
+ * @param charity who the message is from
+ * @param content the content of the message
+ * @throws IllegalArgumentException if title, from or content is null or blank.
+ */
+ public Message(String title, Charity charity, String content) {
+
+ if (title == null || title.isBlank()) {
+ throw new IllegalArgumentException("Title cannot be null or blank.");
+ }
+
+ if (charity == null) {
+ throw new IllegalArgumentException("From cannot be null or blank.");
+ }
+
+ if (content == null || content.isBlank()) {
+ throw new IllegalArgumentException("Content cannot be null or blank.");
+ }
+
+ this.id = UUID.randomUUID();
+ this.title = title;
+ this.fromCharity = charity;
+ this.content = content;
+ this.timeAndDate = LocalDate.now();
+ }
+
+ /**
+ * Creates a message with a unique identifier. The message includes a title, a string who it's
+ * from, content and the time and date. This one creates a message that has been created before.
+ *
+ * @param title the title of the message
+ * @param charity who the message is from
* @param content the content of the message
+ * @param date date of when the message was created
* @throws IllegalArgumentException if title, from or content is null or blank.
*/
- public Message(String title, String from, String content) {
+ public Message(String title, Charity charity, String content, LocalDate date) {
if (title == null || title.isBlank()) {
throw new IllegalArgumentException("Title cannot be null or blank.");
}
- if (from == null || from.isBlank()) {
+ if (charity == null) {
throw new IllegalArgumentException("From cannot be null or blank.");
}
@@ -40,11 +72,15 @@ public Message(String title, String from, String content) {
throw new IllegalArgumentException("Content cannot be null or blank.");
}
+ if (date == null) {
+ throw new IllegalArgumentException("Content cannot be null or blank.");
+ }
+
this.id = UUID.randomUUID();
this.title = title;
- this.from = from;
+ this.fromCharity = charity;
this.content = content;
- this.timeAndDate = LocalDateTime.now();
+ this.timeAndDate = date;
}
public UUID getId() {
@@ -55,15 +91,15 @@ public String getTitle() {
return title;
}
- public String getFrom() {
- return from;
+ public Charity getFrom() {
+ return fromCharity;
}
public String getContent() {
return content;
}
- public LocalDateTime getTimeAndDate() {
+ public LocalDate getTimeAndDate() {
return timeAndDate;
}
}
diff --git a/helpmehelpapplication/src/main/java/ntnu/systemutvikling/team6/models/user/Role.java b/helpmehelpapplication/src/main/java/ntnu/systemutvikling/team6/models/user/Role.java
index 94e0b228..c68752d6 100644
--- a/helpmehelpapplication/src/main/java/ntnu/systemutvikling/team6/models/user/Role.java
+++ b/helpmehelpapplication/src/main/java/ntnu/systemutvikling/team6/models/user/Role.java
@@ -8,4 +8,5 @@
public enum Role {
NORMAL_USER,
CHARITY_USER,
+ ADMIN,
}
diff --git a/helpmehelpapplication/src/main/java/ntnu/systemutvikling/team6/models/user/Settings.java b/helpmehelpapplication/src/main/java/ntnu/systemutvikling/team6/models/user/Settings.java
index d8142836..086ddb9a 100644
--- a/helpmehelpapplication/src/main/java/ntnu/systemutvikling/team6/models/user/Settings.java
+++ b/helpmehelpapplication/src/main/java/ntnu/systemutvikling/team6/models/user/Settings.java
@@ -8,13 +8,13 @@
* @author Robin Strand Prestmo
*/
public class Settings {
- private boolean lightMode;
- private Language language;
private boolean anonymous;
+ private Language language;
+ private boolean lightMode;
/** Sets standard settings. LightMode enabled, language set to English, Anonymous disabled */
public Settings() {
- this(true, Language.ENGLISH, false);
+ this(false, Language.ENGLISH, true);
}
/**
@@ -24,7 +24,7 @@ public Settings() {
* @param language choose language
* @param anonymous choose if user is anonymous
*/
- public Settings(boolean lightMode, Language language, boolean anonymous) {
+ public Settings(boolean anonymous, Language language, boolean lightMode) {
if (language == null) {
throw new IllegalArgumentException("Language cannot be null");
}
diff --git a/helpmehelpapplication/src/main/java/ntnu/systemutvikling/team6/models/user/User.java b/helpmehelpapplication/src/main/java/ntnu/systemutvikling/team6/models/user/User.java
index ef590006..2e03c72e 100644
--- a/helpmehelpapplication/src/main/java/ntnu/systemutvikling/team6/models/user/User.java
+++ b/helpmehelpapplication/src/main/java/ntnu/systemutvikling/team6/models/user/User.java
@@ -17,18 +17,17 @@ public class User {
private static final PasswordHasher passwordHasher = new PasswordHasher();
private final UUID id;
- private String name;
+ private String username;
private String email;
private String passwordHash;
- private final Role role;
- private final Settings settings;
- private final Inbox inbox;
+ private Role role;
+ private Settings settings;
+ private Inbox inbox;
/**
* Creates a new user.
*
- * @param id gives the user a unique identifier with UUID
- * @param name the name of the user
+ * @param username unqiue username used for login
* @param email the email of the user
* @param password the password for the user
* @param role users role
@@ -37,9 +36,8 @@ public class User {
* @throws IllegalArgumentException if any required argument is invalid.
*/
public User(
- String name, String email, String password, Role role, Settings settings, Inbox inbox) {
-
- if (name == null || name.isBlank()) {
+ String username, String email, String password, Role role, Settings settings, Inbox inbox) {
+ if (username == null || username.isBlank()) {
throw new IllegalArgumentException("Name cannot be null or blank.");
}
@@ -61,7 +59,7 @@ public User(
}
this.id = UUID.randomUUID();
- this.name = name;
+ this.username = username;
this.email = email;
this.passwordHash = passwordHasher.getHashPassword(password);
this.role = role;
@@ -69,20 +67,63 @@ public User(
this.inbox = inbox;
}
+ /**
+ * Creates a new user taylored for getting info from DATABASE. Settings and inbox can be set on a
+ * later date throught another method in databaseManager class
+ *
+ * @param uuid gives the user a unique identifier with UUID
+ * @param username the name of the user
+ * @param email the email of the user
+ * @param password the password for the user
+ * @param role users role
+ * @throws IllegalArgumentException if any required argument is invalid.
+ */
+ public User(String uuid, String username, String email, String password, String role) {
+
+ if (uuid == null || uuid.isBlank()) {
+ throw new IllegalArgumentException("UUID cannot be null or blank.");
+ }
+
+ if (username == null || username.isBlank()) {
+ throw new IllegalArgumentException("Name cannot be null or blank.");
+ }
+
+ if (email == null || email.isBlank() || !email.contains("@") || !email.contains(".")) {
+ throw new IllegalArgumentException(
+ "Email cannot be null or blank," + " and must contain '@' and '.'");
+ }
+
+ if (role == null) {
+ throw new IllegalArgumentException("Role cannot be null");
+ }
+
+ this.id = UUID.fromString(uuid);
+ this.username = username;
+ this.email = email;
+ this.passwordHash = password;
+ this.role = Role.valueOf(role);
+ this.settings = null;
+ this.inbox = null;
+ }
+
// Add Getters
public UUID getId() {
return id;
}
- public String getName() {
- return name;
+ public String getUsername() {
+ return username;
}
public String getEmail() {
return email;
}
+ public String getPasswordHash() {
+ return passwordHash;
+ }
+
public Role getRole() {
return role;
}
@@ -103,11 +144,11 @@ public Inbox getInbox() {
* @param name the new name
* @throws IllegalArgumentException if the name is null or blank
*/
- public void setName(String name) {
+ public void setUsername(String name) {
if (name == null || name.isBlank()) {
throw new IllegalArgumentException("Name cannot be null or blank.");
}
- this.name = name;
+ this.username = name;
}
/**
@@ -146,4 +187,25 @@ public void setEmail(String email) {
public boolean checkPassword(String password) {
return passwordHasher.isValidPassword(password, passwordHash);
}
+
+ public void setSettings(Settings settings) {
+ if (settings == null) {
+ throw new IllegalArgumentException("Settings cannot be null");
+ }
+ this.settings = settings;
+ }
+
+ public void setInbox(Inbox inbox) {
+ if (inbox == null) {
+ throw new IllegalArgumentException("Inbox cannot be null");
+ }
+ this.inbox = inbox;
+ }
+
+ public void setRole(Role role) {
+ if (role == null) {
+ throw new IllegalArgumentException("Inbox cannot be null");
+ }
+ this.role = role;
+ }
}
diff --git a/helpmehelpapplication/src/main/java/ntnu/systemutvikling/team6/scraper/FullCharityScrape.java b/helpmehelpapplication/src/main/java/ntnu/systemutvikling/team6/scraper/FullCharityScrape.java
new file mode 100644
index 00000000..e881d424
--- /dev/null
+++ b/helpmehelpapplication/src/main/java/ntnu/systemutvikling/team6/scraper/FullCharityScrape.java
@@ -0,0 +1,110 @@
+package ntnu.systemutvikling.team6.scraper;
+
+import java.io.IOException;
+import java.net.URISyntaxException;
+import java.net.http.HttpClient;
+import ntnu.systemutvikling.team6.models.Charity;
+import ntnu.systemutvikling.team6.models.registry.CharityRegistry;
+import ntnu.systemutvikling.team6.scraper.scraperComponents.APICharityScraper;
+import ntnu.systemutvikling.team6.scraper.scraperComponents.URLCharityScraper;
+
+/**
+ * Orchestrates a full charity data scrape by combining two data sources:
+ *
+ *
+ *
The external charity API (via {@link APICharityScraper}), which provides structured data
+ * such as organisation numbers, approval status, and charity URLs.
+ *
Individual charity web pages (via {@link URLCharityScraper}), which provide richer
+ * presentation data such as descriptions, logos, categories, and key values.
+ *
+ *
+ *
This class acts as a facade — callers only need to invoke {@link #getAPIAndURLCharityData()}
+ * to receive a fully populated {@link CharityRegistry}.
+ */
+public class FullCharityScrape {
+ private final APICharityScraper apiScraper;
+ private final LogoDownloader logoDownloader;
+
+ /**
+ * Constructs a {@code FullCharityScrape} instance and initialises the {@link APICharityScraper}
+ * with a new {@link HttpClient}.
+ *
+ * @throws URISyntaxException if the API endpoint URI used by {@link APICharityScraper} is
+ * malformed
+ */
+ public FullCharityScrape() throws URISyntaxException {
+ HttpClient https = HttpClient.newHttpClient();
+ this.apiScraper = new APICharityScraper(https);
+ this.logoDownloader = new LogoDownloader();
+ }
+
+ /**
+ * Performs a full two-phase scrape and returns a {@link CharityRegistry} populated with all
+ * available charity data.
+ *
+ *
Phase 1 — API scrape: Calls {@link APICharityScraper#checkConnection()} to verify
+ * availability, then fetches and parses the JSON payload into a {@link CharityRegistry}.
+ *
+ *
Phase 2 — URL scrape: Iterates over every {@link Charity} in the registry and uses a
+ * {@link URLCharityScraper} to enrich each entry with its description, logo URL, logo blob,
+ * categories, and key values scraped from the charity's own web page.
+ *
+ *
If {@link APICharityScraper#checkConnection()} throws an exception, it propagates to the
+ * caller and {@code null} is returned. If the connection check passes but returns {@code false},
+ * {@code null} is also returned.
+ *
+ * @return a fully populated {@link CharityRegistry}, or {@code null} if the API is unreachable
+ * @throws IOException if an I/O error occurs during the API request or URL scraping
+ * @throws InterruptedException if the HTTP request thread is interrupted
+ */
+ public CharityRegistry getAPIAndURLCharityData() throws IOException, InterruptedException {
+ try {
+ if (!apiScraper.checkConnection()) {
+ throw new RuntimeException("Connection check returned false");
+ }
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+
+ CharityRegistry charityRegistry = apiScraper.parseJSON(apiScraper.getJSONData());
+ int charityCounter = 0;
+
+ for (Charity charity : charityRegistry.getAllCharities()) {
+ System.out.println(charity.getName());
+ }
+ // Scrapes description, logo, categories, and key values from IK
+ for (Charity charity : charityRegistry.getAllCharities()) {
+ charityCounter++;
+
+ System.out.println(
+ "Scraping charity vanity details: "
+ + charityCounter
+ + " of "
+ + charityRegistry.getAllCharities().size());
+ try {
+ URLCharityScraper urlScraper = new URLCharityScraper(charity.getURL());
+ urlScraper.scrapeCharityPage();
+
+ charity.setDescription(urlScraper.getDescription());
+ charity.setCategory(urlScraper.getCategories());
+ charity.setLogoURL(urlScraper.getLogoURL());
+ charity.setKeyValues(urlScraper.getKeyValues());
+ byte[] logoBlob = LogoDownloader.downloadImageAsBlob(charity.getLogoURL());
+ charity.setLogoBlob(logoBlob);
+ } catch (Exception e) {
+ throw new RuntimeException(
+ "Failed to Scrape for: ["
+ + charityCounter
+ + "]: "
+ + charity.getName()
+ + ": "
+ + e.getMessage());
+ }
+ }
+ return charityRegistry;
+ }
+
+ public APICharityScraper getAPIScraper() {
+ return this.apiScraper;
+ }
+}
diff --git a/helpmehelpapplication/src/main/java/ntnu/systemutvikling/team6/scraper/LogoDownloader.java b/helpmehelpapplication/src/main/java/ntnu/systemutvikling/team6/scraper/LogoDownloader.java
new file mode 100644
index 00000000..2a871ffb
--- /dev/null
+++ b/helpmehelpapplication/src/main/java/ntnu/systemutvikling/team6/scraper/LogoDownloader.java
@@ -0,0 +1,54 @@
+package ntnu.systemutvikling.team6.scraper;
+
+import java.io.InputStream;
+import java.net.URL;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+
+/**
+ * Facilitates downloading of .png images from the individual charity's page on IK, converting them
+ * to bytecode (blob), and then back to a .png.
+ */
+public class LogoDownloader {
+
+ /**
+ * Downloads a image from the given URL and converts it to a blob.
+ *
+ * @param imageUrl the URL of the image
+ * @return a blob containing the image data
+ */
+ public static byte[] downloadImageAsBlob(String imageUrl) {
+ if (imageUrl == null || imageUrl.isBlank()) return null;
+
+ try (InputStream in = new URL(imageUrl).openStream()) {
+ return in.readAllBytes();
+ } catch (Exception e) {
+ System.out.println("Error: Something went wrong when downloading the image.");
+ return null;
+ }
+ }
+
+ /**
+ * Converts a blob of image data back to a .png image file.
+ *
+ * @param imageBytes the blob containing the image data
+ * @param fileName the filename of the .png image file
+ */
+ public static void convertBlobToPNG(byte[] imageBytes, String fileName) {
+ if (imageBytes == null) {
+ return;
+ }
+ try {
+ Path folder = Paths.get("target", "logo");
+ Files.createDirectories(folder);
+
+ Path filePath = folder.resolve(fileName + ".png");
+
+ Files.write(filePath, imageBytes);
+
+ } catch (Exception e) {
+ System.out.println("Error: Something went wrong when converting blob to png.");
+ }
+ }
+}
diff --git a/helpmehelpapplication/src/main/java/ntnu/systemutvikling/team6/scraper/APICharityData.java b/helpmehelpapplication/src/main/java/ntnu/systemutvikling/team6/scraper/scraperComponents/APICharityData.java
similarity index 94%
rename from helpmehelpapplication/src/main/java/ntnu/systemutvikling/team6/scraper/APICharityData.java
rename to helpmehelpapplication/src/main/java/ntnu/systemutvikling/team6/scraper/scraperComponents/APICharityData.java
index 10a489d7..35b10d5d 100644
--- a/helpmehelpapplication/src/main/java/ntnu/systemutvikling/team6/scraper/APICharityData.java
+++ b/helpmehelpapplication/src/main/java/ntnu/systemutvikling/team6/scraper/scraperComponents/APICharityData.java
@@ -1,6 +1,6 @@
-package ntnu.systemutvikling.team6.scraper;
+package ntnu.systemutvikling.team6.scraper.scraperComponents;
-import ntnu.systemutvikling.team6.database.DatabaseManager;
+import ntnu.systemutvikling.team6.database.DatabaseSetup;
/**
* Represents data parsed from the IK API JSON response. Instances are immutable; to update any
@@ -9,7 +9,7 @@
*
Receives data directly from {@link APICharityScraper}.
*
*
{@code org_number} should be a unique number, as it is used as a primary key in {@link
- * DatabaseManager}.
+ * DatabaseSetup}.
*/
public class APICharityData {
private final String org_number;
diff --git a/helpmehelpapplication/src/main/java/ntnu/systemutvikling/team6/scraper/APICharityScraper.java b/helpmehelpapplication/src/main/java/ntnu/systemutvikling/team6/scraper/scraperComponents/APICharityScraper.java
similarity index 93%
rename from helpmehelpapplication/src/main/java/ntnu/systemutvikling/team6/scraper/APICharityScraper.java
rename to helpmehelpapplication/src/main/java/ntnu/systemutvikling/team6/scraper/scraperComponents/APICharityScraper.java
index 89422a1a..16be61f7 100644
--- a/helpmehelpapplication/src/main/java/ntnu/systemutvikling/team6/scraper/APICharityScraper.java
+++ b/helpmehelpapplication/src/main/java/ntnu/systemutvikling/team6/scraper/scraperComponents/APICharityScraper.java
@@ -1,4 +1,4 @@
-package ntnu.systemutvikling.team6.scraper;
+package ntnu.systemutvikling.team6.scraper.scraperComponents;
import com.google.gson.Gson;
import java.io.IOException;
@@ -7,7 +7,7 @@
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import ntnu.systemutvikling.team6.models.Charity;
-import ntnu.systemutvikling.team6.models.CharityRegistry;
+import ntnu.systemutvikling.team6.models.registry.CharityRegistry;
/**
* Fetches JSON information from the IK API and parses the JSON into a list of {@link
@@ -81,6 +81,9 @@ public CharityRegistry parseJSON(String JSONData) {
CharityRegistry charityRegistry = new CharityRegistry();
for (APICharityData apiCharityData : charityData) {
+ if (apiCharityData.getStatus().equalsIgnoreCase("obs")) {
+ continue;
+ }
Charity charity =
new Charity(
apiCharityData.getOrg_number(),
diff --git a/helpmehelpapplication/src/main/java/ntnu/systemutvikling/team6/scraper/scraperComponents/URLCharityScraper.java b/helpmehelpapplication/src/main/java/ntnu/systemutvikling/team6/scraper/scraperComponents/URLCharityScraper.java
new file mode 100644
index 00000000..33b3c529
--- /dev/null
+++ b/helpmehelpapplication/src/main/java/ntnu/systemutvikling/team6/scraper/scraperComponents/URLCharityScraper.java
@@ -0,0 +1,290 @@
+package ntnu.systemutvikling.team6.scraper.scraperComponents;
+
+import java.time.Duration;
+import java.util.ArrayList;
+import java.util.List;
+import org.openqa.selenium.By;
+import org.openqa.selenium.WebDriver;
+import org.openqa.selenium.WebElement;
+import org.openqa.selenium.chrome.ChromeDriver;
+import org.openqa.selenium.chrome.ChromeOptions;
+import org.openqa.selenium.support.ui.ExpectedConditions;
+import org.openqa.selenium.support.ui.WebDriverWait;
+
+/**
+ * Class for scraping the description, URL of the logo, string of categories, and key values of the
+ * charities registered in IK.
+ */
+public class URLCharityScraper {
+ private final String url;
+ private final WebDriver driver;
+ private String description;
+ private String logoURL;
+ private final List categories;
+ private final List keyValues;
+
+ /**
+ * Constructor used for production code.
+ *
+ *
It initializes the lists used for categories and keyValues, as well as defining the
+ * parameters used for the selenium Chromium-based browser that does the scraping.
+ *
+ * @param url the URL for the charity's webpage on IK
+ */
+ public URLCharityScraper(String url) {
+ this.categories = new ArrayList<>();
+ this.keyValues = new ArrayList<>();
+
+ ChromeOptions options = new ChromeOptions();
+ options.addArguments("--headless=new");
+ options.addArguments("--window-size=1920,1080");
+ options.addArguments("--disable-gpu");
+ options.addArguments("--no-sandbox");
+ options.addArguments("--disable-dev-shm-usage");
+
+ this.url = url;
+ this.driver = new ChromeDriver(options);
+ }
+
+ /**
+ * Constructor used for testing.
+ *
+ *
It accepts both a url (should ideally be a dud) and a {@link WebDriver} as parameters. The
+ * WebDriver is passed to make testing easier.
+ *
+ * @param url the URL for the charity's webpage on IK (for this constructor it should not be a
+ * real URL)
+ * @param driver the {@code WebDriver} object used for scraping
+ */
+ public URLCharityScraper(String url, WebDriver driver) {
+ this.categories = new ArrayList<>();
+ this.keyValues = new ArrayList<>();
+ this.url = url;
+ this.driver = driver;
+ }
+
+ /**
+ * Creates a {@link WebDriverWait} object for halting scraping until the correct pre-conditions
+ * are met.
+ *
+ * @return the {@code WebDriverWait} object to be used in the methods
+ */
+ protected WebDriverWait createWait() {
+ return new WebDriverWait(driver, Duration.ofSeconds(10));
+ }
+
+ /**
+ * Calls the {@code findElements} method from the {@code WebDriver} object and returns a list of
+ * the returned {@link WebElement} objects.
+ *
+ * @param by a selector for {@code WebElement} objects
+ * @return a list of found {@code WebElement} objects matching the given selector
+ */
+ public List findElements(By by) {
+ return driver.findElements(by);
+ }
+
+ /**
+ * Calls the {@code findElement} method from the {@code WebDriver} object and returns a list of
+ * the returned {@code WebElement} objects.
+ *
+ * @param by a selector for {@code WebElement} objects
+ * @return a list of found {@code WebElement} objects matching the given selector
+ */
+ protected WebElement findElement(By by) {
+ return driver.findElement(by);
+ }
+
+ /** Quits the driver instance, making it unusable. */
+ protected void closeDriver() {
+ driver.quit();
+ }
+
+ /** Scrapes the URL for the paragraphs containing the description of the charity. */
+ protected void updateDescription() {
+ try {
+ WebDriverWait wait = createWait();
+ StringBuilder descriptionString = new StringBuilder();
+
+ List readMoreLinks = findElements(By.cssSelector("a.read-more"));
+
+ if (!readMoreLinks.isEmpty()) {
+ WebElement readMore = findElement(By.cssSelector("a.read-more"));
+ readMore.click();
+
+ wait.until(ExpectedConditions.visibilityOfElementLocated(By.cssSelector(".extra-info")));
+ }
+
+ wait.until(
+ ExpectedConditions.numberOfElementsToBeMoreThan(By.cssSelector(".information"), 0));
+
+ // Thread.sleep(5000);
+ List firstDescription = findElements(By.cssSelector(".information"));
+
+ for (WebElement element : firstDescription) {
+ if (!element.getText().isBlank()) {
+ descriptionString.append(element.getText()).append("\n\n");
+ }
+ }
+
+ this.description = descriptionString.toString();
+
+ } catch (Exception e) {
+ System.out.println("No description found for " + driver.getCurrentUrl());
+ }
+ }
+
+ /** Scrapes the URL for the image URL of the logo for the charity. */
+ void updateLogo() {
+ try {
+ WebDriverWait wait = createWait();
+ wait.until(ExpectedConditions.visibilityOfElementLocated(By.cssSelector(".logo > img")));
+ // Thread.sleep(5000);
+
+ WebElement logo = findElement(By.cssSelector(".logo > img"));
+ this.logoURL = logo.getAttribute("src");
+
+ } catch (Exception e) {
+ System.out.println("No logo found for " + driver.getCurrentUrl());
+ }
+ }
+
+ /** Scrapes the URL for the category labels containing the categories for the charity. */
+ void updateCategories() {
+ try {
+ WebDriverWait wait = createWait();
+
+ wait.until(ExpectedConditions.presenceOfElementLocated(By.cssSelector(".tag-label")));
+ // Thread.sleep(5000);
+
+ List elements = findElements(By.cssSelector(".tag-label"));
+
+ for (WebElement element : elements) {
+ this.categories.add(element.getText());
+ }
+
+ } catch (Exception e) {
+ System.out.println("No categories found for " + driver.getCurrentUrl());
+ }
+ }
+
+ /**
+ * Scrapes the URL for the statistics of the charity; the percentage collected, the percentage
+ * that goes to the administration, and the percentage that is put towards the cause.
+ */
+ void updateKeyValues() {
+ try {
+ WebDriverWait wait = createWait();
+
+ String percentage;
+ WebElement element;
+
+ wait.until(
+ ExpectedConditions.visibilityOfElementLocated(
+ By.xpath(
+ "//li[.//h2[normalize-space()='Innsamlingsprosent']]//div[@class='graph']")));
+ // Thread.sleep(5000);
+ element =
+ findElement(
+ By.xpath("//li[.//h2[normalize-space()='Innsamlingsprosent']]//div[@class='graph']"));
+ percentage = element.getAttribute("data-percentage");
+ this.keyValues.add(percentage);
+
+ wait.until(
+ ExpectedConditions.visibilityOfElementLocated(
+ By.xpath(
+ "//li[.//h2[normalize-space()='Administrasjonsprosent']]//div[@class='graph']")));
+
+ element =
+ findElement(
+ By.xpath(
+ "//li[.//h2[normalize-space()='Administrasjonsprosent']]//div[@class='graph']"));
+ percentage = element.getAttribute("data-percentage");
+ this.keyValues.add(percentage);
+
+ wait.until(
+ ExpectedConditions.visibilityOfElementLocated(
+ By.xpath("//li[.//h2[normalize-space()='FormĂĄlsprosent']]//div[@class='graph']")));
+
+ element =
+ findElement(
+ By.xpath("//li[.//h2[normalize-space()='FormĂĄlsprosent']]//div[@class='graph']"));
+ percentage = element.getAttribute("data-percentage");
+ this.keyValues.add(percentage);
+ } catch (Exception e) {
+ System.out.println("No key values found for " + driver.getCurrentUrl());
+ }
+ }
+
+ /** Runs all the scraper methods at once, updating the object parameters. */
+ public void scrapeCharityPage() {
+ try {
+ driver.get(this.url);
+ updateDescription();
+ updateLogo();
+ updateCategories();
+ updateKeyValues();
+ // Thread.sleep(1000);
+
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ } finally {
+ closeDriver();
+ }
+ }
+
+ /**
+ * Returns the description of the charity.
+ *
+ * @return a String containing the description of the charity.
+ */
+ public String getDescription() {
+ return description;
+ }
+
+ /**
+ * Returns the URL of the logo for the charity.
+ *
+ * @return a String containing the URL for the logo of the charity.
+ */
+ public String getLogoURL() {
+ return logoURL;
+ }
+
+ /**
+ * Returns a String of the categories for the charity with ',' as a delimiter.
+ *
+ * @return a String of strings containing the categories for the charity
+ */
+ public List getCategories() {
+ /*
+ StringBuilder categoriesString = new StringBuilder();
+
+ for (int i = 0; i < this.categories.size(); i++) {
+ categoriesString.append(this.categories.get(i));
+ if (i < this.categories.size() - 1) {
+ categoriesString.append(",");
+ }
+ }
+ */
+ return categories;
+ }
+
+ /**
+ * Returns a String of the key value percentages for the charity with ':' as a delimiter, verified
+ * by IK.
+ *
+ * @return a String of the key values for the charity-
+ */
+ public String getKeyValues() {
+ StringBuilder keyValuesString = new StringBuilder();
+
+ for (int i = 0; i < this.keyValues.size(); i++) {
+ keyValuesString.append(this.keyValues.get(i));
+ if (i < this.keyValues.size() - 1) {
+ keyValuesString.append(":");
+ }
+ }
+ return keyValuesString.toString();
+ }
+}
diff --git a/helpmehelpapplication/src/main/java/ntnu/systemutvikling/team6/service/APIToDatabaseService.java b/helpmehelpapplication/src/main/java/ntnu/systemutvikling/team6/service/APIToDatabaseService.java
new file mode 100644
index 00000000..e49b8ce6
--- /dev/null
+++ b/helpmehelpapplication/src/main/java/ntnu/systemutvikling/team6/service/APIToDatabaseService.java
@@ -0,0 +1,224 @@
+package ntnu.systemutvikling.team6.service;
+
+import java.sql.Connection;
+import java.sql.PreparedStatement;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.util.List;
+import java.util.UUID;
+import ntnu.systemutvikling.team6.database.DatabaseConnection;
+import ntnu.systemutvikling.team6.models.Charity;
+
+public class APIToDatabaseService {
+ private final DatabaseConnection connection;
+
+ /**
+ * Contractor for APIToDatabaseService. It uses a DatabaseConnection object that contains a
+ * connection credentials.
+ *
+ * @param connection
+ */
+ public APIToDatabaseService(DatabaseConnection connection) {
+ this.connection = connection;
+ }
+
+ /**
+ * This method is used to verify the integrity of the data in the {@code charities} table and to
+ * update it based on the data retrieved from the IK API and the charity's URL. The param
+ * charities are retrieved from the IK API through the APICharityData class. Called in initialize
+ * method in HmHApplication.java, which is the main class of the application, to ensure that the
+ * data is up to date when the application starts. Uses a temp table to ensure that the data in
+ * the database is consistent with the data from the API.
+ *
+ *
Uses a URLScraper object to get data not contained in the API, and static methods from
+ * LogoDownloader to get the charity's logo as a blob.
+ *
+ * @param charities a list of {@code Charity} objects to add to the database
+ */
+ public void addAPIDataToTable(List charities) {
+ Connection conn = null;
+ 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 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 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.nameUUIDFromBytes(
+ (charity.getOrg_number() + charity.getURL() + charity.getName()).getBytes())
+ .toString();
+ charity.setUUIDFromString(uuid);
+ System.out.println("API object doesn't have UUID, assigning stable UUID");
+ } else {
+ uuid = charity.getUUID().toString();
+ }
+
+ ps1.setString(1, uuid);
+ ps1.setString(2, charity.getOrg_number().replaceAll("\\s", ""));
+ ps1.setBoolean(3, charity.getPreApproved());
+ ps1.setString(4, charity.getStatus());
+ ps1.executeUpdate();
+
+ ps2.setString(1, uuid);
+ ps2.setString(2, charity.getName());
+ ps2.setString(3, charity.getURL());
+ ps2.setString(4, charity.getDescription());
+ ps2.setString(5, charity.getLogoURL());
+ ps2.setString(6, charity.getKeyValues());
+ ps2.setBytes(7, charity.getLogoBlob());
+ ps2.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) {
+ e.printStackTrace();
+ throw new RuntimeException(e);
+ }
+
+ // Integrity Check
+ String createTemp =
+ """
+ CREATE TEMPORARY TABLE temp_api_charities (
+ org_number VARCHAR(255) PRIMARY KEY
+ )
+ """;
+ try (PreparedStatement ps = conn.prepareStatement(createTemp)) {
+ ps.execute();
+ }
+
+ String insertTemp = "INSERT IGNORE INTO temp_api_charities (org_number) VALUES (?)";
+ try (PreparedStatement ps = conn.prepareStatement(insertTemp)) {
+ for (Charity charity : charities) {
+ ps.setString(1, charity.getOrg_number().replaceAll("\\s", ""));
+ ps.addBatch();
+ }
+ ps.executeBatch();
+ }
+
+ String deleteSql =
+ """
+ DELETE FROM Charities c
+ WHERE NOT EXISTS (
+ SELECT 1 FROM temp_api_charities t
+ WHERE t.org_number = c.org_number
+ )
+ AND NOT EXISTS (
+ SELECT 1 FROM Donations d WHERE d.charity_id = c.UUID_charities
+ )
+ AND NOT EXISTS (
+ SELECT 1 FROM Feedback f WHERE f.charity_id = c.UUID_charities
+ )
+ AND NOT EXISTS (
+ SELECT 1 FROM CharityVanity cv WHERE cv.UUID_charity = c.UUID_charities
+ )
+ AND NOT EXISTS (
+ SELECT 1 FROM CharityUsers cu WHERE cu.TheCharity = 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();
+
+ } catch (SQLException e) {
+ if (conn != null) {
+ try {
+ conn.rollback();
+ } catch (SQLException ex) {
+ ex.printStackTrace();
+ }
+ }
+ e.printStackTrace();
+ throw new RuntimeException("ERROR: Something went wrong during updating charities table.");
+ } finally {
+ if (conn != null) {
+ try {
+ conn.setAutoCommit(true);
+ conn.close();
+ } catch (SQLException e) {
+ e.printStackTrace();
+ }
+ }
+ }
+ }
+}
diff --git a/helpmehelpapplication/src/main/java/ntnu/systemutvikling/team6/service/AuthenticationService.java b/helpmehelpapplication/src/main/java/ntnu/systemutvikling/team6/service/AuthenticationService.java
index 8b137891..639e0862 100644
--- a/helpmehelpapplication/src/main/java/ntnu/systemutvikling/team6/service/AuthenticationService.java
+++ b/helpmehelpapplication/src/main/java/ntnu/systemutvikling/team6/service/AuthenticationService.java
@@ -1 +1,122 @@
+package ntnu.systemutvikling.team6.service;
+import ntnu.systemutvikling.team6.database.DAO.CharityUserDAO;
+import ntnu.systemutvikling.team6.database.DAO.UserDAO;
+import ntnu.systemutvikling.team6.database.DatabaseConnection;
+import ntnu.systemutvikling.team6.models.Charity;
+import ntnu.systemutvikling.team6.models.user.Inbox;
+import ntnu.systemutvikling.team6.models.user.Role;
+import ntnu.systemutvikling.team6.models.user.Settings;
+import ntnu.systemutvikling.team6.models.user.User;
+
+/**
+ * Service class responsible for handling user authentication operations, including login,
+ * registration, and logout functionality.
+ *
+ *
Maintains the state of the currently authenticated user throughout the session.
+ */
+public class AuthenticationService {
+ /** Handles read operations for user data from the database. */
+ private final UserDAO userDataAcsessObject;
+
+ /** Handles write operations for user data to the database. */
+
+ /** The currently authenticated user, or {@code null} if no user is logged in. */
+ private User currentUser;
+
+ private Charity isCharityUser;
+
+ /**
+ * Constructs an {@code AuthenticationService} with the specified data access objects.
+ *
+ * @param userDAO the data reader used to query user information from the database
+ */
+ public AuthenticationService(UserDAO userDAO) {
+ this.userDataAcsessObject = userDAO;
+ }
+
+ /**
+ * Attempts to authenticate a user with the given credentials.
+ *
+ *
If a matching user is found in the database, they are set as the current user and the method
+ * returns {@code true}.
+ *
+ * @param email the username of the user attempting to log in
+ * @param password the password of the user attempting to log in
+ * @return {@code true} if authentication was successful; {@code false} otherwise
+ */
+ public boolean login(String email, String password) {
+ User user = userDataAcsessObject.getUserFromDBEmailAndPassword(email, password);
+
+ if (user != null) {
+ currentUser = user;
+ CharityUserDAO charityUserDAO = new CharityUserDAO(new DatabaseConnection());
+ isCharityUser = charityUserDAO.getUserCharityUser(currentUser.getId().toString());
+ if (isCharityUser != null) {
+ currentUser.setRole(Role.CHARITY_USER);
+ }
+ System.out.println("User gotten");
+ return true;
+ }
+
+ return false;
+ }
+
+ /**
+ * Registers a new user account with the provided details.
+ *
+ *
\ No newline at end of file
diff --git a/helpmehelpapplication/target/site/jacoco/jacoco-resources/branchfc.gif b/helpmehelpapplication/target/site/jacoco/jacoco-resources/branchfc.gif
new file mode 100644
index 00000000..989b46d3
Binary files /dev/null and b/helpmehelpapplication/target/site/jacoco/jacoco-resources/branchfc.gif differ
diff --git a/helpmehelpapplication/target/site/jacoco/jacoco-resources/branchnc.gif b/helpmehelpapplication/target/site/jacoco/jacoco-resources/branchnc.gif
new file mode 100644
index 00000000..1933e07c
Binary files /dev/null and b/helpmehelpapplication/target/site/jacoco/jacoco-resources/branchnc.gif differ
diff --git a/helpmehelpapplication/target/site/jacoco/jacoco-resources/branchpc.gif b/helpmehelpapplication/target/site/jacoco/jacoco-resources/branchpc.gif
new file mode 100644
index 00000000..cbf711b7
Binary files /dev/null and b/helpmehelpapplication/target/site/jacoco/jacoco-resources/branchpc.gif differ
diff --git a/helpmehelpapplication/target/site/jacoco/jacoco-resources/bundle.gif b/helpmehelpapplication/target/site/jacoco/jacoco-resources/bundle.gif
new file mode 100644
index 00000000..fca9c53e
Binary files /dev/null and b/helpmehelpapplication/target/site/jacoco/jacoco-resources/bundle.gif differ
diff --git a/helpmehelpapplication/target/site/jacoco/jacoco-resources/class.gif b/helpmehelpapplication/target/site/jacoco/jacoco-resources/class.gif
new file mode 100644
index 00000000..eb348fb0
Binary files /dev/null and b/helpmehelpapplication/target/site/jacoco/jacoco-resources/class.gif differ
diff --git a/helpmehelpapplication/target/site/jacoco/jacoco-resources/down.gif b/helpmehelpapplication/target/site/jacoco/jacoco-resources/down.gif
new file mode 100644
index 00000000..440a14db
Binary files /dev/null and b/helpmehelpapplication/target/site/jacoco/jacoco-resources/down.gif differ
diff --git a/helpmehelpapplication/target/site/jacoco/jacoco-resources/greenbar.gif b/helpmehelpapplication/target/site/jacoco/jacoco-resources/greenbar.gif
new file mode 100644
index 00000000..0ba65672
Binary files /dev/null and b/helpmehelpapplication/target/site/jacoco/jacoco-resources/greenbar.gif differ
diff --git a/helpmehelpapplication/target/site/jacoco/jacoco-resources/group.gif b/helpmehelpapplication/target/site/jacoco/jacoco-resources/group.gif
new file mode 100644
index 00000000..a4ea580d
Binary files /dev/null and b/helpmehelpapplication/target/site/jacoco/jacoco-resources/group.gif differ
diff --git a/helpmehelpapplication/target/site/jacoco/jacoco-resources/method.gif b/helpmehelpapplication/target/site/jacoco/jacoco-resources/method.gif
new file mode 100644
index 00000000..7d24707e
Binary files /dev/null and b/helpmehelpapplication/target/site/jacoco/jacoco-resources/method.gif differ
diff --git a/helpmehelpapplication/target/site/jacoco/jacoco-resources/package.gif b/helpmehelpapplication/target/site/jacoco/jacoco-resources/package.gif
new file mode 100644
index 00000000..131c28da
Binary files /dev/null and b/helpmehelpapplication/target/site/jacoco/jacoco-resources/package.gif differ
diff --git a/helpmehelpapplication/target/site/jacoco/jacoco-resources/prettify.css b/helpmehelpapplication/target/site/jacoco/jacoco-resources/prettify.css
new file mode 100644
index 00000000..be5166e0
--- /dev/null
+++ b/helpmehelpapplication/target/site/jacoco/jacoco-resources/prettify.css
@@ -0,0 +1,13 @@
+/* Pretty printing styles. Used with prettify.js. */
+
+.str { color: #2A00FF; }
+.kwd { color: #7F0055; font-weight:bold; }
+.com { color: #3F5FBF; }
+.typ { color: #606; }
+.lit { color: #066; }
+.pun { color: #660; }
+.pln { color: #000; }
+.tag { color: #008; }
+.atn { color: #606; }
+.atv { color: #080; }
+.dec { color: #606; }
diff --git a/helpmehelpapplication/target/site/jacoco/jacoco-resources/prettify.js b/helpmehelpapplication/target/site/jacoco/jacoco-resources/prettify.js
new file mode 100644
index 00000000..b2766fe0
--- /dev/null
+++ b/helpmehelpapplication/target/site/jacoco/jacoco-resources/prettify.js
@@ -0,0 +1,1510 @@
+// Copyright (C) 2006 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+
+/**
+ * @fileoverview
+ * some functions for browser-side pretty printing of code contained in html.
+ *
+ *
+ * For a fairly comprehensive set of languages see the
+ * README
+ * file that came with this source. At a minimum, the lexer should work on a
+ * number of languages including C and friends, Java, Python, Bash, SQL, HTML,
+ * XML, CSS, Javascript, and Makefiles. It works passably on Ruby, PHP and Awk
+ * and a subset of Perl, but, because of commenting conventions, doesn't work on
+ * Smalltalk, Lisp-like, or CAML-like languages without an explicit lang class.
+ *
+ * Usage:
+ *
include this source file in an html page via
+ * {@code }
+ *
define style rules. See the example page for examples.
+ *
mark the {@code
} and {@code } tags in your source with
+ * {@code class=prettyprint.}
+ * You can also use the (html deprecated) {@code } tag, but the pretty
+ * printer needs to do more substantial DOM manipulations to support that, so
+ * some css styles may not be preserved.
+ *
+ * That's it. I wanted to keep the API as simple as possible, so there's no
+ * need to specify which language the code is in, but if you wish, you can add
+ * another class to the {@code
} or {@code } element to specify the
+ * language, as in {@code
}. Any class that
+ * starts with "lang-" followed by a file extension, specifies the file type.
+ * See the "lang-*.js" files in this directory for code that implements
+ * per-language file handlers.
+ *
+ * Change log:
+ * cbeust, 2006/08/22
+ *
+ * Java annotations (start with "@") are now captured as literals ("lit")
+ *
+ * @requires console
+ */
+
+// JSLint declarations
+/*global console, document, navigator, setTimeout, window */
+
+/**
+ * Split {@code prettyPrint} into multiple timeouts so as not to interfere with
+ * UI events.
+ * If set to {@code false}, {@code prettyPrint()} is synchronous.
+ */
+window['PR_SHOULD_USE_CONTINUATION'] = true;
+
+/** the number of characters between tab columns */
+window['PR_TAB_WIDTH'] = 8;
+
+/** Walks the DOM returning a properly escaped version of innerHTML.
+ * @param {Node} node
+ * @param {Array.} out output buffer that receives chunks of HTML.
+ */
+window['PR_normalizedHtml']
+
+/** Contains functions for creating and registering new language handlers.
+ * @type {Object}
+ */
+ = window['PR']
+
+/** Pretty print a chunk of code.
+ *
+ * @param {string} sourceCodeHtml code as html
+ * @return {string} code as html, but prettier
+ */
+ = window['prettyPrintOne']
+/** Find all the {@code
} and {@code } tags in the DOM with
+ * {@code class=prettyprint} and prettify them.
+ * @param {Function?} opt_whenDone if specified, called when the last entry
+ * has been finished.
+ */
+ = window['prettyPrint'] = void 0;
+
+/** browser detection. @extern @returns false if not IE, otherwise the major version. */
+window['_pr_isIE6'] = function () {
+ var ieVersion = navigator && navigator.userAgent &&
+ navigator.userAgent.match(/\bMSIE ([678])\./);
+ ieVersion = ieVersion ? +ieVersion[1] : false;
+ window['_pr_isIE6'] = function () { return ieVersion; };
+ return ieVersion;
+};
+
+
+(function () {
+ // Keyword lists for various languages.
+ var FLOW_CONTROL_KEYWORDS =
+ "break continue do else for if return while ";
+ var C_KEYWORDS = FLOW_CONTROL_KEYWORDS + "auto case char const default " +
+ "double enum extern float goto int long register short signed sizeof " +
+ "static struct switch typedef union unsigned void volatile ";
+ var COMMON_KEYWORDS = C_KEYWORDS + "catch class delete false import " +
+ "new operator private protected public this throw true try typeof ";
+ var CPP_KEYWORDS = COMMON_KEYWORDS + "alignof align_union asm axiom bool " +
+ "concept concept_map const_cast constexpr decltype " +
+ "dynamic_cast explicit export friend inline late_check " +
+ "mutable namespace nullptr reinterpret_cast static_assert static_cast " +
+ "template typeid typename using virtual wchar_t where ";
+ var JAVA_KEYWORDS = COMMON_KEYWORDS +
+ "abstract boolean byte extends final finally implements import " +
+ "instanceof null native package strictfp super synchronized throws " +
+ "transient ";
+ var CSHARP_KEYWORDS = JAVA_KEYWORDS +
+ "as base by checked decimal delegate descending event " +
+ "fixed foreach from group implicit in interface internal into is lock " +
+ "object out override orderby params partial readonly ref sbyte sealed " +
+ "stackalloc string select uint ulong unchecked unsafe ushort var ";
+ var JSCRIPT_KEYWORDS = COMMON_KEYWORDS +
+ "debugger eval export function get null set undefined var with " +
+ "Infinity NaN ";
+ var PERL_KEYWORDS = "caller delete die do dump elsif eval exit foreach for " +
+ "goto if import last local my next no our print package redo require " +
+ "sub undef unless until use wantarray while BEGIN END ";
+ var PYTHON_KEYWORDS = FLOW_CONTROL_KEYWORDS + "and as assert class def del " +
+ "elif except exec finally from global import in is lambda " +
+ "nonlocal not or pass print raise try with yield " +
+ "False True None ";
+ var RUBY_KEYWORDS = FLOW_CONTROL_KEYWORDS + "alias and begin case class def" +
+ " defined elsif end ensure false in module next nil not or redo rescue " +
+ "retry self super then true undef unless until when yield BEGIN END ";
+ var SH_KEYWORDS = FLOW_CONTROL_KEYWORDS + "case done elif esac eval fi " +
+ "function in local set then until ";
+ var ALL_KEYWORDS = (
+ CPP_KEYWORDS + CSHARP_KEYWORDS + JSCRIPT_KEYWORDS + PERL_KEYWORDS +
+ PYTHON_KEYWORDS + RUBY_KEYWORDS + SH_KEYWORDS);
+
+ // token style names. correspond to css classes
+ /** token style for a string literal */
+ var PR_STRING = 'str';
+ /** token style for a keyword */
+ var PR_KEYWORD = 'kwd';
+ /** token style for a comment */
+ var PR_COMMENT = 'com';
+ /** token style for a type */
+ var PR_TYPE = 'typ';
+ /** token style for a literal value. e.g. 1, null, true. */
+ var PR_LITERAL = 'lit';
+ /** token style for a punctuation string. */
+ var PR_PUNCTUATION = 'pun';
+ /** token style for a punctuation string. */
+ var PR_PLAIN = 'pln';
+
+ /** token style for an sgml tag. */
+ var PR_TAG = 'tag';
+ /** token style for a markup declaration such as a DOCTYPE. */
+ var PR_DECLARATION = 'dec';
+ /** token style for embedded source. */
+ var PR_SOURCE = 'src';
+ /** token style for an sgml attribute name. */
+ var PR_ATTRIB_NAME = 'atn';
+ /** token style for an sgml attribute value. */
+ var PR_ATTRIB_VALUE = 'atv';
+
+ /**
+ * A class that indicates a section of markup that is not code, e.g. to allow
+ * embedding of line numbers within code listings.
+ */
+ var PR_NOCODE = 'nocode';
+
+ /** A set of tokens that can precede a regular expression literal in
+ * javascript.
+ * http://www.mozilla.org/js/language/js20/rationale/syntax.html has the full
+ * list, but I've removed ones that might be problematic when seen in
+ * languages that don't support regular expression literals.
+ *
+ *
Specifically, I've removed any keywords that can't precede a regexp
+ * literal in a syntactically legal javascript program, and I've removed the
+ * "in" keyword since it's not a keyword in many languages, and might be used
+ * as a count of inches.
+ *
+ *
The link a above does not accurately describe EcmaScript rules since
+ * it fails to distinguish between (a=++/b/i) and (a++/b/i) but it works
+ * very well in practice.
+ *
+ * @private
+ */
+ var REGEXP_PRECEDER_PATTERN = function () {
+ var preceders = [
+ "!", "!=", "!==", "#", "%", "%=", "&", "&&", "&&=",
+ "&=", "(", "*", "*=", /* "+", */ "+=", ",", /* "-", */ "-=",
+ "->", /*".", "..", "...", handled below */ "/", "/=", ":", "::", ";",
+ "<", "<<", "<<=", "<=", "=", "==", "===", ">",
+ ">=", ">>", ">>=", ">>>", ">>>=", "?", "@", "[",
+ "^", "^=", "^^", "^^=", "{", "|", "|=", "||",
+ "||=", "~" /* handles =~ and !~ */,
+ "break", "case", "continue", "delete",
+ "do", "else", "finally", "instanceof",
+ "return", "throw", "try", "typeof"
+ ];
+ var pattern = '(?:^^|[+-]';
+ for (var i = 0; i < preceders.length; ++i) {
+ pattern += '|' + preceders[i].replace(/([^=<>:&a-z])/g, '\\$1');
+ }
+ pattern += ')\\s*'; // matches at end, and matches empty string
+ return pattern;
+ // CAVEAT: this does not properly handle the case where a regular
+ // expression immediately follows another since a regular expression may
+ // have flags for case-sensitivity and the like. Having regexp tokens
+ // adjacent is not valid in any language I'm aware of, so I'm punting.
+ // TODO: maybe style special characters inside a regexp as punctuation.
+ }();
+
+ // Define regexps here so that the interpreter doesn't have to create an
+ // object each time the function containing them is called.
+ // The language spec requires a new object created even if you don't access
+ // the $1 members.
+ var pr_amp = /&/g;
+ var pr_lt = //g;
+ var pr_quot = /\"/g;
+ /** like textToHtml but escapes double quotes to be attribute safe. */
+ function attribToHtml(str) {
+ return str.replace(pr_amp, '&')
+ .replace(pr_lt, '<')
+ .replace(pr_gt, '>')
+ .replace(pr_quot, '"');
+ }
+
+ /** escapest html special characters to html. */
+ function textToHtml(str) {
+ return str.replace(pr_amp, '&')
+ .replace(pr_lt, '<')
+ .replace(pr_gt, '>');
+ }
+
+
+ var pr_ltEnt = /</g;
+ var pr_gtEnt = />/g;
+ var pr_aposEnt = /'/g;
+ var pr_quotEnt = /"/g;
+ var pr_ampEnt = /&/g;
+ var pr_nbspEnt = / /g;
+ /** unescapes html to plain text. */
+ function htmlToText(html) {
+ var pos = html.indexOf('&');
+ if (pos < 0) { return html; }
+ // Handle numeric entities specially. We can't use functional substitution
+ // since that doesn't work in older versions of Safari.
+ // These should be rare since most browsers convert them to normal chars.
+ for (--pos; (pos = html.indexOf('', pos + 1)) >= 0;) {
+ var end = html.indexOf(';', pos);
+ if (end >= 0) {
+ var num = html.substring(pos + 3, end);
+ var radix = 10;
+ if (num && num.charAt(0) === 'x') {
+ num = num.substring(1);
+ radix = 16;
+ }
+ var codePoint = parseInt(num, radix);
+ if (!isNaN(codePoint)) {
+ html = (html.substring(0, pos) + String.fromCharCode(codePoint) +
+ html.substring(end + 1));
+ }
+ }
+ }
+
+ return html.replace(pr_ltEnt, '<')
+ .replace(pr_gtEnt, '>')
+ .replace(pr_aposEnt, "'")
+ .replace(pr_quotEnt, '"')
+ .replace(pr_nbspEnt, ' ')
+ .replace(pr_ampEnt, '&');
+ }
+
+ /** is the given node's innerHTML normally unescaped? */
+ function isRawContent(node) {
+ return 'XMP' === node.tagName;
+ }
+
+ var newlineRe = /[\r\n]/g;
+ /**
+ * Are newlines and adjacent spaces significant in the given node's innerHTML?
+ */
+ function isPreformatted(node, content) {
+ // PRE means preformatted, and is a very common case, so don't create
+ // unnecessary computed style objects.
+ if ('PRE' === node.tagName) { return true; }
+ if (!newlineRe.test(content)) { return true; } // Don't care
+ var whitespace = '';
+ // For disconnected nodes, IE has no currentStyle.
+ if (node.currentStyle) {
+ whitespace = node.currentStyle.whiteSpace;
+ } else if (window.getComputedStyle) {
+ // Firefox makes a best guess if node is disconnected whereas Safari
+ // returns the empty string.
+ whitespace = window.getComputedStyle(node, null).whiteSpace;
+ }
+ return !whitespace || whitespace === 'pre';
+ }
+
+ function normalizedHtml(node, out, opt_sortAttrs) {
+ switch (node.nodeType) {
+ case 1: // an element
+ var name = node.tagName.toLowerCase();
+
+ out.push('<', name);
+ var attrs = node.attributes;
+ var n = attrs.length;
+ if (n) {
+ if (opt_sortAttrs) {
+ var sortedAttrs = [];
+ for (var i = n; --i >= 0;) { sortedAttrs[i] = attrs[i]; }
+ sortedAttrs.sort(function (a, b) {
+ return (a.name < b.name) ? -1 : a.name === b.name ? 0 : 1;
+ });
+ attrs = sortedAttrs;
+ }
+ for (var i = 0; i < n; ++i) {
+ var attr = attrs[i];
+ if (!attr.specified) { continue; }
+ out.push(' ', attr.name.toLowerCase(),
+ '="', attribToHtml(attr.value), '"');
+ }
+ }
+ out.push('>');
+ for (var child = node.firstChild; child; child = child.nextSibling) {
+ normalizedHtml(child, out, opt_sortAttrs);
+ }
+ if (node.firstChild || !/^(?:br|link|img)$/.test(name)) {
+ out.push('<\/', name, '>');
+ }
+ break;
+ case 3: case 4: // text
+ out.push(textToHtml(node.nodeValue));
+ break;
+ }
+ }
+
+ /**
+ * Given a group of {@link RegExp}s, returns a {@code RegExp} that globally
+ * matches the union o the sets o strings matched d by the input RegExp.
+ * Since it matches globally, if the input strings have a start-of-input
+ * anchor (/^.../), it is ignored for the purposes of unioning.
+ * @param {Array.} regexs non multiline, non-global regexs.
+ * @return {RegExp} a global regex.
+ */
+ function combinePrefixPatterns(regexs) {
+ var capturedGroupIndex = 0;
+
+ var needToFoldCase = false;
+ var ignoreCase = false;
+ for (var i = 0, n = regexs.length; i < n; ++i) {
+ var regex = regexs[i];
+ if (regex.ignoreCase) {
+ ignoreCase = true;
+ } else if (/[a-z]/i.test(regex.source.replace(
+ /\\u[0-9a-f]{4}|\\x[0-9a-f]{2}|\\[^ux]/gi, ''))) {
+ needToFoldCase = true;
+ ignoreCase = false;
+ break;
+ }
+ }
+
+ function decodeEscape(charsetPart) {
+ if (charsetPart.charAt(0) !== '\\') { return charsetPart.charCodeAt(0); }
+ switch (charsetPart.charAt(1)) {
+ case 'b': return 8;
+ case 't': return 9;
+ case 'n': return 0xa;
+ case 'v': return 0xb;
+ case 'f': return 0xc;
+ case 'r': return 0xd;
+ case 'u': case 'x':
+ return parseInt(charsetPart.substring(2), 16)
+ || charsetPart.charCodeAt(1);
+ case '0': case '1': case '2': case '3': case '4':
+ case '5': case '6': case '7':
+ return parseInt(charsetPart.substring(1), 8);
+ default: return charsetPart.charCodeAt(1);
+ }
+ }
+
+ function encodeEscape(charCode) {
+ if (charCode < 0x20) {
+ return (charCode < 0x10 ? '\\x0' : '\\x') + charCode.toString(16);
+ }
+ var ch = String.fromCharCode(charCode);
+ if (ch === '\\' || ch === '-' || ch === '[' || ch === ']') {
+ ch = '\\' + ch;
+ }
+ return ch;
+ }
+
+ function caseFoldCharset(charSet) {
+ var charsetParts = charSet.substring(1, charSet.length - 1).match(
+ new RegExp(
+ '\\\\u[0-9A-Fa-f]{4}'
+ + '|\\\\x[0-9A-Fa-f]{2}'
+ + '|\\\\[0-3][0-7]{0,2}'
+ + '|\\\\[0-7]{1,2}'
+ + '|\\\\[\\s\\S]'
+ + '|-'
+ + '|[^-\\\\]',
+ 'g'));
+ var groups = [];
+ var ranges = [];
+ var inverse = charsetParts[0] === '^';
+ for (var i = inverse ? 1 : 0, n = charsetParts.length; i < n; ++i) {
+ var p = charsetParts[i];
+ switch (p) {
+ case '\\B': case '\\b':
+ case '\\D': case '\\d':
+ case '\\S': case '\\s':
+ case '\\W': case '\\w':
+ groups.push(p);
+ continue;
+ }
+ var start = decodeEscape(p);
+ var end;
+ if (i + 2 < n && '-' === charsetParts[i + 1]) {
+ end = decodeEscape(charsetParts[i + 2]);
+ i += 2;
+ } else {
+ end = start;
+ }
+ ranges.push([start, end]);
+ // If the range might intersect letters, then expand it.
+ if (!(end < 65 || start > 122)) {
+ if (!(end < 65 || start > 90)) {
+ ranges.push([Math.max(65, start) | 32, Math.min(end, 90) | 32]);
+ }
+ if (!(end < 97 || start > 122)) {
+ ranges.push([Math.max(97, start) & ~32, Math.min(end, 122) & ~32]);
+ }
+ }
+ }
+
+ // [[1, 10], [3, 4], [8, 12], [14, 14], [16, 16], [17, 17]]
+ // -> [[1, 12], [14, 14], [16, 17]]
+ ranges.sort(function (a, b) { return (a[0] - b[0]) || (b[1] - a[1]); });
+ var consolidatedRanges = [];
+ var lastRange = [NaN, NaN];
+ for (var i = 0; i < ranges.length; ++i) {
+ var range = ranges[i];
+ if (range[0] <= lastRange[1] + 1) {
+ lastRange[1] = Math.max(lastRange[1], range[1]);
+ } else {
+ consolidatedRanges.push(lastRange = range);
+ }
+ }
+
+ var out = ['['];
+ if (inverse) { out.push('^'); }
+ out.push.apply(out, groups);
+ for (var i = 0; i < consolidatedRanges.length; ++i) {
+ var range = consolidatedRanges[i];
+ out.push(encodeEscape(range[0]));
+ if (range[1] > range[0]) {
+ if (range[1] + 1 > range[0]) { out.push('-'); }
+ out.push(encodeEscape(range[1]));
+ }
+ }
+ out.push(']');
+ return out.join('');
+ }
+
+ function allowAnywhereFoldCaseAndRenumberGroups(regex) {
+ // Split into character sets, escape sequences, punctuation strings
+ // like ('(', '(?:', ')', '^'), and runs of characters that do not
+ // include any of the above.
+ var parts = regex.source.match(
+ new RegExp(
+ '(?:'
+ + '\\[(?:[^\\x5C\\x5D]|\\\\[\\s\\S])*\\]' // a character set
+ + '|\\\\u[A-Fa-f0-9]{4}' // a unicode escape
+ + '|\\\\x[A-Fa-f0-9]{2}' // a hex escape
+ + '|\\\\[0-9]+' // a back-reference or octal escape
+ + '|\\\\[^ux0-9]' // other escape sequence
+ + '|\\(\\?[:!=]' // start of a non-capturing group
+ + '|[\\(\\)\\^]' // start/emd of a group, or line start
+ + '|[^\\x5B\\x5C\\(\\)\\^]+' // run of other characters
+ + ')',
+ 'g'));
+ var n = parts.length;
+
+ // Maps captured group numbers to the number they will occupy in
+ // the output or to -1 if that has not been determined, or to
+ // undefined if they need not be capturing in the output.
+ var capturedGroups = [];
+
+ // Walk over and identify back references to build the capturedGroups
+ // mapping.
+ for (var i = 0, groupIndex = 0; i < n; ++i) {
+ var p = parts[i];
+ if (p === '(') {
+ // groups are 1-indexed, so max group index is count of '('
+ ++groupIndex;
+ } else if ('\\' === p.charAt(0)) {
+ var decimalValue = +p.substring(1);
+ if (decimalValue && decimalValue <= groupIndex) {
+ capturedGroups[decimalValue] = -1;
+ }
+ }
+ }
+
+ // Renumber groups and reduce capturing groups to non-capturing groups
+ // where possible.
+ for (var i = 1; i < capturedGroups.length; ++i) {
+ if (-1 === capturedGroups[i]) {
+ capturedGroups[i] = ++capturedGroupIndex;
+ }
+ }
+ for (var i = 0, groupIndex = 0; i < n; ++i) {
+ var p = parts[i];
+ if (p === '(') {
+ ++groupIndex;
+ if (capturedGroups[groupIndex] === undefined) {
+ parts[i] = '(?:';
+ }
+ } else if ('\\' === p.charAt(0)) {
+ var decimalValue = +p.substring(1);
+ if (decimalValue && decimalValue <= groupIndex) {
+ parts[i] = '\\' + capturedGroups[groupIndex];
+ }
+ }
+ }
+
+ // Remove any prefix anchors so that the output will match anywhere.
+ // ^^ really does mean an anchored match though.
+ for (var i = 0, groupIndex = 0; i < n; ++i) {
+ if ('^' === parts[i] && '^' !== parts[i + 1]) { parts[i] = ''; }
+ }
+
+ // Expand letters to groupts to handle mixing of case-sensitive and
+ // case-insensitive patterns if necessary.
+ if (regex.ignoreCase && needToFoldCase) {
+ for (var i = 0; i < n; ++i) {
+ var p = parts[i];
+ var ch0 = p.charAt(0);
+ if (p.length >= 2 && ch0 === '[') {
+ parts[i] = caseFoldCharset(p);
+ } else if (ch0 !== '\\') {
+ // TODO: handle letters in numeric escapes.
+ parts[i] = p.replace(
+ /[a-zA-Z]/g,
+ function (ch) {
+ var cc = ch.charCodeAt(0);
+ return '[' + String.fromCharCode(cc & ~32, cc | 32) + ']';
+ });
+ }
+ }
+ }
+
+ return parts.join('');
+ }
+
+ var rewritten = [];
+ for (var i = 0, n = regexs.length; i < n; ++i) {
+ var regex = regexs[i];
+ if (regex.global || regex.multiline) { throw new Error('' + regex); }
+ rewritten.push(
+ '(?:' + allowAnywhereFoldCaseAndRenumberGroups(regex) + ')');
+ }
+
+ return new RegExp(rewritten.join('|'), ignoreCase ? 'gi' : 'g');
+ }
+
+ var PR_innerHtmlWorks = null;
+ function getInnerHtml(node) {
+ // inner html is hopelessly broken in Safari 2.0.4 when the content is
+ // an html description of well formed XML and the containing tag is a PRE
+ // tag, so we detect that case and emulate innerHTML.
+ if (null === PR_innerHtmlWorks) {
+ var testNode = document.createElement('PRE');
+ testNode.appendChild(
+ document.createTextNode('\n'));
+ PR_innerHtmlWorks = !/)[\r\n]+/g, '$1')
+ .replace(/(?:[\r\n]+[ \t]*)+/g, ' ');
+ }
+ return content;
+ }
+
+ var out = [];
+ for (var child = node.firstChild; child; child = child.nextSibling) {
+ normalizedHtml(child, out);
+ }
+ return out.join('');
+ }
+
+ /** returns a function that expand tabs to spaces. This function can be fed
+ * successive chunks of text, and will maintain its own internal state to
+ * keep track of how tabs are expanded.
+ * @return {function (string) : string} a function that takes
+ * plain text and return the text with tabs expanded.
+ * @private
+ */
+ function makeTabExpander(tabWidth) {
+ var SPACES = ' ';
+ var charInLine = 0;
+
+ return function (plainText) {
+ // walk over each character looking for tabs and newlines.
+ // On tabs, expand them. On newlines, reset charInLine.
+ // Otherwise increment charInLine
+ var out = null;
+ var pos = 0;
+ for (var i = 0, n = plainText.length; i < n; ++i) {
+ var ch = plainText.charAt(i);
+
+ switch (ch) {
+ case '\t':
+ if (!out) { out = []; }
+ out.push(plainText.substring(pos, i));
+ // calculate how much space we need in front of this part
+ // nSpaces is the amount of padding -- the number of spaces needed
+ // to move us to the next column, where columns occur at factors of
+ // tabWidth.
+ var nSpaces = tabWidth - (charInLine % tabWidth);
+ charInLine += nSpaces;
+ for (; nSpaces >= 0; nSpaces -= SPACES.length) {
+ out.push(SPACES.substring(0, nSpaces));
+ }
+ pos = i + 1;
+ break;
+ case '\n':
+ charInLine = 0;
+ break;
+ default:
+ ++charInLine;
+ }
+ }
+ if (!out) { return plainText; }
+ out.push(plainText.substring(pos));
+ return out.join('');
+ };
+ }
+
+ var pr_chunkPattern = new RegExp(
+ '[^<]+' // A run of characters other than '<'
+ + '|<\!--[\\s\\S]*?--\>' // an HTML comment
+ + '|' // a CDATA section
+ // a probable tag that should not be highlighted
+ + '|<\/?[a-zA-Z](?:[^>\"\']|\'[^\']*\'|\"[^\"]*\")*>'
+ + '|<', // A '<' that does not begin a larger chunk
+ 'g');
+ var pr_commentPrefix = /^<\!--/;
+ var pr_cdataPrefix = /^) into their textual equivalent.
+ *
+ * @param {string} s html where whitespace is considered significant.
+ * @return {Object} source code and extracted tags.
+ * @private
+ */
+ function extractTags(s) {
+ // since the pattern has the 'g' modifier and defines no capturing groups,
+ // this will return a list of all chunks which we then classify and wrap as
+ // PR_Tokens
+ var matches = s.match(pr_chunkPattern);
+ var sourceBuf = [];
+ var sourceBufLen = 0;
+ var extractedTags = [];
+ if (matches) {
+ for (var i = 0, n = matches.length; i < n; ++i) {
+ var match = matches[i];
+ if (match.length > 1 && match.charAt(0) === '<') {
+ if (pr_commentPrefix.test(match)) { continue; }
+ if (pr_cdataPrefix.test(match)) {
+ // strip CDATA prefix and suffix. Don't unescape since it's CDATA
+ sourceBuf.push(match.substring(9, match.length - 3));
+ sourceBufLen += match.length - 12;
+ } else if (pr_brPrefix.test(match)) {
+ // tags are lexically significant so convert them to text.
+ // This is undone later.
+ sourceBuf.push('\n');
+ ++sourceBufLen;
+ } else {
+ if (match.indexOf(PR_NOCODE) >= 0 && isNoCodeTag(match)) {
+ // A will start a section that should be
+ // ignored. Continue walking the list until we see a matching end
+ // tag.
+ var name = match.match(pr_tagNameRe)[2];
+ var depth = 1;
+ var j;
+ end_tag_loop:
+ for (j = i + 1; j < n; ++j) {
+ var name2 = matches[j].match(pr_tagNameRe);
+ if (name2 && name2[2] === name) {
+ if (name2[1] === '/') {
+ if (--depth === 0) { break end_tag_loop; }
+ } else {
+ ++depth;
+ }
+ }
+ }
+ if (j < n) {
+ extractedTags.push(
+ sourceBufLen, matches.slice(i, j + 1).join(''));
+ i = j;
+ } else { // Ignore unclosed sections.
+ extractedTags.push(sourceBufLen, match);
+ }
+ } else {
+ extractedTags.push(sourceBufLen, match);
+ }
+ }
+ } else {
+ var literalText = htmlToText(match);
+ sourceBuf.push(literalText);
+ sourceBufLen += literalText.length;
+ }
+ }
+ }
+ return { source: sourceBuf.join(''), tags: extractedTags };
+ }
+
+ /** True if the given tag contains a class attribute with the nocode class. */
+ function isNoCodeTag(tag) {
+ return !!tag
+ // First canonicalize the representation of attributes
+ .replace(/\s(\w+)\s*=\s*(?:\"([^\"]*)\"|'([^\']*)'|(\S+))/g,
+ ' $1="$2$3$4"')
+ // Then look for the attribute we want.
+ .match(/[cC][lL][aA][sS][sS]=\"[^\"]*\bnocode\b/);
+ }
+
+ /**
+ * Apply the given language handler to sourceCode and add the resulting
+ * decorations to out.
+ * @param {number} basePos the index of sourceCode within the chunk of source
+ * whose decorations are already present on out.
+ */
+ function appendDecorations(basePos, sourceCode, langHandler, out) {
+ if (!sourceCode) { return; }
+ var job = {
+ source: sourceCode,
+ basePos: basePos
+ };
+ langHandler(job);
+ out.push.apply(out, job.decorations);
+ }
+
+ /** Given triples of [style, pattern, context] returns a lexing function,
+ * The lexing function interprets the patterns to find token boundaries and
+ * returns a decoration list of the form
+ * [index_0, style_0, index_1, style_1, ..., index_n, style_n]
+ * where index_n is an index into the sourceCode, and style_n is a style
+ * constant like PR_PLAIN. index_n-1 <= index_n, and style_n-1 applies to
+ * all characters in sourceCode[index_n-1:index_n].
+ *
+ * The stylePatterns is a list whose elements have the form
+ * [style : string, pattern : RegExp, DEPRECATED, shortcut : string].
+ *
+ * Style is a style constant like PR_PLAIN, or can be a string of the
+ * form 'lang-FOO', where FOO is a language extension describing the
+ * language of the portion of the token in $1 after pattern executes.
+ * E.g., if style is 'lang-lisp', and group 1 contains the text
+ * '(hello (world))', then that portion of the token will be passed to the
+ * registered lisp handler for formatting.
+ * The text before and after group 1 will be restyled using this decorator
+ * so decorators should take care that this doesn't result in infinite
+ * recursion. For example, the HTML lexer rule for SCRIPT elements looks
+ * something like ['lang-js', /<[s]cript>(.+?)<\/script>/]. This may match
+ * '