aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--README.md1
-rw-r--r--docs/DesignDoc.md101
-rw-r--r--docs/u-fund-model.drawio.pngbin0 -> 173754 bytes
-rw-r--r--docs/u-fund-model.drawio.xml103
-rw-r--r--docs/u-fund-viewmodel.drawio.pngbin0 -> 149547 bytes
-rw-r--r--docs/u-fund-viewmodel.drawio.xml134
6 files changed, 242 insertions, 97 deletions
diff --git a/README.md b/README.md
index 42f5a07..52dc26e 100644
--- a/README.md
+++ b/README.md
@@ -1,5 +1,4 @@
# U-Fund: __Coral Reefs__
-# Modify this document to expand any and all sections that are applicable for a better understanding from your users/testers/collaborators (remove this comment and other instructions areas for your FINAL release)
An online U-Fund system built in Java **21** and __Angular 19__
diff --git a/docs/DesignDoc.md b/docs/DesignDoc.md
index 0323b7f..b125558 100644
--- a/docs/DesignDoc.md
+++ b/docs/DesignDoc.md
@@ -22,13 +22,8 @@ Our project is intended to create a space to fund aquatic conservation, from phy
The purpose of this project is to provide an avenue for individuals to help support ocean conservation, with helpers donating physical goods and money to support efforts and managers creating and editing needs to
-> _**[Sprint 2 & 4]** Provide a very brief statement about the project and the most
-> important user group and user goals._
-
### Glossary and Acronyms
-> _**[Sprint 2 & 4]** Provide a table of terms and acronyms._
-
| Term | Definition |
| -------------- | ---------------------------------------------- |
| SPA | Single Page |
@@ -52,10 +47,6 @@ This section describes the features of the application.
The requirements for this u-fund project to be functional include a system for organizations (administrators) to create funding goals (needs) that can be satisfied by clients (users).
-> _In this section you do not need to be exhaustive and list every
-> story. Focus on top-level features from the Vision document and
-> maybe Epics and critical Stories._
-
#### Uniform Application Design
All pages and components should be consitent in style and layout. The user experience should be consistent across pages. Everything should be clear and consistent for usability, especially if a user is unfamilliar with our site. Usabilty options should consistently apply, such as different layouts, light/dark mode, or different devices across all pages and components.
@@ -70,14 +61,10 @@ Common code and features should be consolidated into services and classes in ord
### Definition of MVP
-> _**[Sprint 2 & 4]** Provide a simple description of the Minimum Viable Product._
-
Users are able to login to the Ufund, either as a manager or helper. They can create secure passwords for account security. Helpers are able to go to the cupboard and can view needs, search for needs, add/remove needs to their funding basket, and check out and fund needs. Managers can add, remove, and edit needs in the cupboard. When editing needs, managers can edit one or more properties and update needs for users. Needs have many properties including images, descriptions, goal types and amounts. Needs are saved so users and managers will see when they are updated. Managers can view stats on a managment page.
### MVP Features
-> _**[Sprint 4]** Provide a list of top-level Epics and/or Stories of the MVP._
-
- AS a Developer I WANT to create a funding basket component SO THAT needs can be viewed within the cupboard on the dashboard.
- As a user I want to see a homepage so that I can navigate the site.
- AS a User I WANT to see a home page when visiting the site SO THAT I can easily navigate through pages.
@@ -97,8 +84,6 @@ Users are able to login to the Ufund, either as a manager or helper. They can cr
### Enhancements
-> _**[Sprint 4]** Describe what enhancements you have implemented for the project._
-
Security
- We have implemented a set of security features to enhance the security of the application
- We have added an authentication system, which verifies a user can access the correct pages, and keeps them logged in after they have logged in once
@@ -126,10 +111,6 @@ Sort filters
This section describes the application domain.
-> _**[Sprint 2 & 4]** Provide a high-level overview of the domain for this application. You
-> can discuss the more important domain entities and their relationship
-> to each other._
-
![alt text](domain-model.png)
Each user views 1 cupboard which can contain 0 or more needs. They can take 1 or more needs and put them in their funding basket which they can then check out. Each user has 1 funding basket. 1 manager will manage the 1 cupboard, add, removing, and editing needs within it. Needs have two or more properties. Managers cannot interact with the 1 funding basket each user has.
@@ -143,9 +124,6 @@ The application is seperated into three tiers. These include the view, viewmodel
### Summary
The following Tiers/Layers model shows a high-level view of the webapp's architecture.
-**NOTE**: detailed diagrams are required in later sections of this document.
-
-> _**[Sprint 1]** (Augment this diagram with your **own** rendition and representations of sample system classes, placing them into the appropriate M/V/VM (orange rectangle) tier section. Focus on what is currently required to support **Sprint 1 - Demo requirements**. Make sure to describe your design choices in the corresponding _**Tier Section**_ and also in the _**OO Design Principles**_ section below.)_
![The Tiers & Layers of the Architecture](architecture-tiers-and-layers.png)
@@ -157,13 +135,12 @@ The View is the client-side SPA built with Angular utilizing HTML, CSS and TypeS
Both the ViewModel and Model are built using Java and Spring Framework. Details of the components within these tiers are supplied below.
+![Full UML](u-fund.drawio.png)
+
### Overview of User Interface
This section describes the web interface flow; this is how the user views and interacts with the web application.
-> _Provide a summary of the application's user interface. Describe, from the user's perspective, the flow of the pages/navigation in the web application.
-> (Add low-fidelity mockups prior to initiating your **[Sprint 2]** work so you have a good idea of the user interactions.) Eventually replace with representative screen shots of your high-fidelity results as these become available and finally include future recommendations improvement recommendations for your **[Sprint 4]** )_
-
When a user first visits the website they will arrive at our home page. This gives some info about us and leads them to the cupboard where they can views needs. When viewing a need a user will be prompted to login, or they can click on login at the top right at any time. Once at the login page they can either login or click signup to go to the signup page and create an account. Either page will log the user into the website and bring them to the page they were previously on. Once logged in as a helper a user can view and add needs to their funding basket, letting them checkout and contribute to funding. Otherwise, a user can login as a manager, where they have access to the cupboard and dashboard. In the dashboard they can view information about the website. On the cupboard a manager has the option to control needs, such as adding news ones or editing/deleting existing news.
Home Page:
@@ -199,36 +176,16 @@ Our improvements would be to make the website mobile accessible, allowing anyone
### View Tier
-> _**[Sprint 4]** Provide a summary of the View Tier UI of your architecture.
-> Describe the types of components in the tier and describe their
-> responsibilities. This should be a narrative description, i.e. it has
-> a flow or "story line" that the reader can follow._
-
As following our UI description, our view tier has a similar flow. There is a component for the home page when first visiting the page, having basic html to showcase the website. From there, users can go to the login and signup pages which are individual components. They contain the logic to log users in and create an account. Additionaly, signup contains logic to check password strength and make sure a user creates an account with a strong password. Next, a user can visit the cupboard component. The cupboard component uses the need list component which shows a lists a needs. Similar, the funding basket uses the need list component to display needs. When a need is clicked on a user can view the need page component, which displays more info about a need. As a manager, a user can view the dashboard. The dashboard contains info about money raised and users, as well as uses the mini-need-list which can be populated with a list of needs, in this case it's used to display top needs. Additionally, the toast and modal components are used across pages, to display error/info messages and allow managers to edit/create needs.
-> _**[Sprint 4]** You must provide at least **2 sequence diagrams** as is relevant to a particular aspects
-> of the design that you are describing. (**For example**, in a shopping experience application you might create a
-> sequence diagram of a customer searching for an item and adding to their cart.)
-> As these can span multiple tiers, be sure to include an relevant HTTP requests from the client-side to the server-side
-> to help illustrate the end-to-end flow._
-
Sequence for checking out a need:
![alt text](checkoutNeeds.png)
Sequence for adding needs to the funding basket:
![alt text](addNeedsToFundingBasket.png)
-> _**[Sprint 4]** To adequately show your system, you will need to present the **class diagrams** where relevant in your design. Some additional tips:_
->
-> - _Class diagrams only apply to the **ViewModel** and **Model** Tier_
-> - _A single class diagram of the entire system will not be effective. You may start with one, but will be need to break it down into smaller sections to account for requirements of each of the Tier static models below._
-> - _Correct labeling of relationships with proper notation for the relationship type, multiplicities, and navigation information will be important._
-> - _Include other details such as attributes and method signatures that you think are needed to support the level of detail in your discussion._
-
### ViewModel Tier
-> _**[Sprint 1]** List the classes supporting this tier and provide a description of their purpose._
-
Controllers
- AuthController: Handles http requests to login and logout users, calling to the authService
- CupboardController: Handles http requests to interact with needs, calling to the cupboardService
@@ -239,21 +196,12 @@ Services
- CupboardService: Handles all logic dealing with needs, verifying inputs and throwing errors.
- UserService: Handles all logic dealing with users, verifying inputs and throwing errors.
-> _**[Sprint 4]** Provide a summary of this tier of your architecture. This
-> section will follow the same instructions that are given for the View
-> Tier above._
-
Requests are sent from the view tier and handled by controllers. For example, when creating a new need a POST request is sent from the frontend and caught by the cupboardController. The cupboardController first authenticates the user, calling the authService and making sure the user has proper access. Next, the cupboardService is called to create a need. The cupboardService checks for a valid goal and name, and then creates a need from the model tier, returning back up. The cupboardController and userController all call authService to authenticate users.
-> _At appropriate places as part of this narrative provide **one** or more updated and **properly labeled**
-> static models (UML class diagrams) with some details such as associations (connections) between classes, and critical attributes and methods. (**Be sure** to revisit the Static **UML Review Sheet** to ensure your class diagrams are using correct format and syntax.)_
->
-![UML](u-fund.drawio.png)
+![UML](u-fund-viewmodel.drawio.png)
### Model Tier
-> _**[Sprint 1]** List the classes supporting this tier and provide a description of there purpose._
-
Models
- Need: A need contains information pertaining to donating, such as a goal and current donations, as well as additional information to provide specifics for each need
- User: A user represents a single account on our website, containing info like a username and a hashed password as well as other fields for specifics
@@ -264,23 +212,14 @@ Persistence:
- UserDAO: Stores users, allowing CRUD and other basic operations on users
- UserAuthDAO: Stores UserAuths, allowing CRUD operations and basic operations on userAuths
-> _**[Sprint 2, 3 & 4]** Provide a summary of this tier of your architecture. This
-> section will follow the same instructions that are given for the View
-> Tier above._
-
In our model tier we have a Need class, a User class, and a UserAuth class. The Need class represents needs and has fields for all of their values. Users have a passwordHash field, storing a hashed version of their password, an List of integers, representing the ID's of needs in the basket, and the userType which determines their privileges. The UserAuth class stores a key, a username, and expiration. A key is generated for a user and is used to authenticate the user. The username is the name associated with the key and the expiration is how long until the user must login again.
-> _At appropriate places as part of this narrative provide **one** or more updated and **properly labeled**
-> static models (UML class diagrams) with some details such as associations (connections) between classes, and critical attributes and methods. (**Be sure** to revisit the Static **UML Review Sheet** to ensure your class diagrams are using correct format and syntax.)_
-
-![UML](u-fund.drawio.png)
+![UML](u-fund-model.drawio.png)
## OO Design Principles
-> _**[Sprint 1]** Name and describe the initial OO Principles that your team has considered in support of your design (and implementation) for this first Sprint._
-
- Law of Demeter: Classes only talk to nearby classes.
- -
+ - The Law of Demeter is present when viewing our architecture, as our MVVM architecture has different tiers, from controllers, to services, all the way to the DAOs. Each tier only talks to nearby layers and never interacts too far. A `CupboardController` never interacts with the `CupboardDAO`, ensuring low coupling and a seamless architecture.
- Low Coupling: Limit the number of classes connected to each individual class.
- Low Coupling is present in our design when viewing the ViewModel tier, as controllers only talk to their own services as well as `AuthService`. Services have minimal interactions with other services and their own DAO's, demonstrating that our architecture is present in our implementation. No class has that bad of coupling, with `AuthService` having the most but it could be changed to only interact with controllers.
- Pure Fabrication: Using helper functions to split up larger classes into more managable chunks.
@@ -288,17 +227,8 @@ In our model tier we have a Need class, a User class, and a UserAuth class. The
- Single Responsibility: Each function should only have one function to prevent unintended errors.
- This principle is present in our controllers, as they only handle communication between the front and back end, letting the services deal with logic and error handling. For example, in `CupboardController`, it's methods only deal with handling back end calls for needs and passing the calls to the services, where they check for faulty values and other issues with need creation, editing, etc.
-> _**[Sprint 2, 3 & 4]** Will eventually address upto **4 key OO Principles** in your final design. Follow guidance in augmenting those completed in previous Sprints as indicated to you by instructor. Be sure to include any diagrams (or clearly refer to ones elsewhere in your Tier sections above) to support your claims._
-
-> _**[Sprint 3 & 4]** OO Design Principles should span across **all tiers.**_
-
## Static Code Analysis/Future Design Improvements
-> _**[Sprint 4]** With the results from the Static Code Analysis exercise,
-> **Identify 3-4** areas within your code that have been flagged by the Static Code
-> Analysis Tool (SonarQube) and provide your analysis and recommendations.
-> Include any relevant screenshot(s) with each area._
-
![sonarQube](sonarQube.png)
- Overall, our results from SonarQube were very good. We had overall good coverage and did not have any major issues. Though we definitely have a number of issues flagged, the majority of them are info, with less than 10 high or medium severity issues. Even then, the major severity issues are fairly minor, such as reusing a string in the field of a thrown error and not putting comments in a nested class.
@@ -306,8 +236,6 @@ In our model tier we have a Need class, a User class, and a UserAuth class. The
![Sonar severity](severity.png)
![Sonar error](sonarError.png)
-> _**[Sprint 4]** Discuss **future** refactoring and other design improvements your team would explore if the team had additional time._
-
Some refactoring and improvements we would have made if we had additional time are as follows:
- Improve tests consistency and raise code coverage
- Add more information and create an overall more polished manager dashboard
@@ -336,39 +264,20 @@ After:
## Testing
-> _This section will provide information about the testing performed
-> and the results of the testing._
-
Currently around 115 tests, with roughly 90% coverage overall. Model tier is above 95% individually.
### Acceptance Testing
We have around 40 passing Acceptance Tests. A few tests, such as being logged out after long enough aren't easy to test, as our authentication removes authentications that are over 30 days old. There are probably a few pages and edge cases which are not accounted for in user testing, but have been thoroughly tested through the backend, frontend, or have been individually dealt with.
-> _**[Sprint 2 & 4]** Report on the number of user stories that have passed all their
-> acceptance criteria tests, the number that have some acceptance
-> criteria tests failing, and the number of user stories that
-> have not had any testing yet. Highlight the issues found during
-> acceptance testing and if there are any concerns._
-
### Unit Testing and Code Coverage
-> _**[Sprint 4]** Discuss your unit testing strategy. Report on the code coverage
-> achieved from unit testing of the code base. Discuss the team's
-> coverage targets, why you selected those values, and how well your
-> code coverage met your targets._
-
Our unit testing strategy was to use a consistent styles of test across our persistence tier, model tier, and service/controller tier. On top of this, later on we had a single team member work on the majority of tests, resulting in a consistent style across all of the testing. On top of this, we consistently generated coverage reports and then wrote new tests to cover missing lines/branches. Overall, we had ~95% code coverage with the lowest coverage of a class being 82%. Our model tier was above 90% coverage.
-> _**[Sprint 2, 3 & 4]** **Include images of your code coverage report.** If there are any anomalies, discuss
-> those._
-
![alt text](CodeCoverage.png)
## Ongoing Rationale
-> _**[Sprint 1, 2, 3 & 4]** Throughout the project, provide a time stamp **(yyyy/mm/dd): Sprint # and description** of any _**major**_ team decisions or design milestones/changes and corresponding justification._
-
**(2025/02/12): Sprint #1: Decided on the U-fund theme as Aquatic / Coral Reef.**
**(2025/02/15): Sprint #1: Outlined structure of Back-End Application.**
diff --git a/docs/u-fund-model.drawio.png b/docs/u-fund-model.drawio.png
new file mode 100644
index 0000000..3d6d6b9
--- /dev/null
+++ b/docs/u-fund-model.drawio.png
Binary files differ
diff --git a/docs/u-fund-model.drawio.xml b/docs/u-fund-model.drawio.xml
new file mode 100644
index 0000000..69138d4
--- /dev/null
+++ b/docs/u-fund-model.drawio.xml
@@ -0,0 +1,103 @@
+<mxfile host="Electron" agent="Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) draw.io/26.2.2 Chrome/134.0.6998.178 Electron/35.1.2 Safari/537.36" version="26.2.2">
+ <diagram id="WOSjVCChJG0uuKkKcy1j" name="Page-1">
+ <mxGraphModel dx="3484" dy="1652" grid="1" gridSize="10" guides="1" tooltips="1" connect="1" arrows="1" fold="1" page="0" pageScale="1" pageWidth="850" pageHeight="1100" background="none" math="0" shadow="0">
+ <root>
+ <mxCell id="0" />
+ <mxCell id="1" parent="0" />
+ <mxCell id="node17" value="&lt;p style=&quot;margin:0px;margin-top:4px;text-align:center;&quot;&gt;&lt;i&gt;&amp;lt;&amp;lt;interface&amp;gt;&amp;gt;&lt;/i&gt;&lt;br/&gt;&lt;b&gt;CupboardDAO&lt;/b&gt;&lt;/p&gt;&lt;hr size=&quot;1&quot;/&gt;&lt;p style=&quot;margin:0 0 0 4px;line-height:1.6;&quot;&gt;+ deleteNeed(int): boolean&lt;br/&gt;+ getNeed(int): Need&lt;br/&gt;+ addNeed(Need): Need&lt;br/&gt;+ updateNeed(Need): Need&lt;/p&gt;&lt;hr size=&quot;1&quot;/&gt;&lt;p style=&quot;margin:0 0 0 4px;line-height:1.6;&quot;&gt; needs: Need[]&lt;/p&gt;" style="verticalAlign=top;align=left;overflow=fill;fontSize=14;fontFamily=Helvetica;html=1;rounded=0;shadow=0;comic=0;labelBackgroundColor=none;strokeWidth=1;" parent="1" vertex="1">
+ <mxGeometry x="-750" y="750" width="192" height="142" as="geometry" />
+ </mxCell>
+ <mxCell id="node2" value="&lt;p style=&quot;margin:0px;margin-top:4px;text-align:center;&quot;&gt;&lt;b&gt;CupboardFileDAO&lt;/b&gt;&lt;/p&gt;&lt;hr size=&quot;1&quot;/&gt;&lt;p style=&quot;margin:0 0 0 4px;line-height:1.6;&quot;&gt;+ CupboardFileDAO(String, ObjectMapper): &lt;/p&gt;&lt;hr size=&quot;1&quot;/&gt;&lt;p style=&quot;margin:0 0 0 4px;line-height:1.6;&quot;&gt;- needs: Map&amp;lt;Integer, Need&amp;gt;&lt;/p&gt;&lt;hr size=&quot;1&quot;/&gt;&lt;p style=&quot;margin:0 0 0 4px;line-height:1.6;&quot;&gt;+ getNeed(int): Need&lt;br/&gt;+ addNeed(Need): Need&lt;br/&gt;- save(): void&lt;br/&gt;+ deleteNeed(int): boolean&lt;br/&gt;- load(): void&lt;br/&gt;- nextId(): int&lt;br/&gt;+ updateNeed(Need): Need&lt;/p&gt;&lt;hr size=&quot;1&quot;/&gt;&lt;p style=&quot;margin:0 0 0 4px;line-height:1.6;&quot;&gt; needs: Need[]&lt;/p&gt;" style="verticalAlign=top;align=left;overflow=fill;fontSize=14;fontFamily=Helvetica;html=1;rounded=0;shadow=0;comic=0;labelBackgroundColor=none;strokeWidth=1;" parent="1" vertex="1">
+ <mxGeometry x="-472" y="687.5" width="286" height="267" as="geometry" />
+ </mxCell>
+ <mxCell id="node19" value="&lt;p style=&quot;margin:0px;margin-top:4px;text-align:center;&quot;&gt;&lt;i&gt;&amp;lt;&amp;lt;enumeration&amp;gt;&amp;gt;&lt;/i&gt;&lt;br/&gt;&lt;b&gt;GoalType&lt;/b&gt;&lt;/p&gt;&lt;hr size=&quot;1&quot;/&gt;&lt;p style=&quot;margin:0 0 0 4px;line-height:1.6;&quot;&gt;+ GoalType(): &lt;/p&gt;&lt;hr size=&quot;1&quot;/&gt;&lt;p style=&quot;margin:0 0 0 4px;line-height:1.6;&quot;&gt;+ valueOf(String): GoalType&lt;br/&gt;+ values(): GoalType[]&lt;/p&gt;" style="verticalAlign=top;align=left;overflow=fill;fontSize=14;fontFamily=Helvetica;html=1;rounded=0;shadow=0;comic=0;labelBackgroundColor=none;strokeWidth=1;" parent="1" vertex="1">
+ <mxGeometry x="-401.5" y="190" width="193" height="136" as="geometry" />
+ </mxCell>
+ <mxCell id="node9" value="&lt;p style=&quot;margin:0px;margin-top:4px;text-align:center;&quot;&gt;&lt;b&gt;Need&lt;/b&gt;&lt;/p&gt;&lt;hr size=&quot;1&quot;/&gt;&lt;p style=&quot;margin:0 0 0 4px;line-height:1.6;&quot;&gt;+ Need(String, String, String, double, GoalType, boolean, String): &lt;br/&gt;+ Need(Need): &lt;br/&gt;+ Need(String, String, String, int, double, GoalType, boolean, String): &lt;/p&gt;&lt;hr size=&quot;1&quot;/&gt;&lt;p style=&quot;margin:0 0 0 4px;line-height:1.6;&quot;&gt;- name: String&lt;br/&gt;- type: GoalType&lt;br/&gt;- current: double&lt;br/&gt;- id: int&lt;br/&gt;- maxGoal: double&lt;br/&gt;- filterAttributes: String[]&lt;/p&gt;&lt;hr size=&quot;1&quot;/&gt;&lt;p style=&quot;margin:0 0 0 4px;line-height:1.6;&quot;&gt;+ toString(): String&lt;br/&gt;+ incrementCurrent(double): void&lt;/p&gt;&lt;hr size=&quot;1&quot;/&gt;&lt;p style=&quot;margin:0 0 0 4px;line-height:1.6;&quot;&gt; name: String&lt;br/&gt; id: int&lt;br/&gt; ID: int&lt;br/&gt; type: GoalType&lt;br/&gt; current: double&lt;br/&gt; maxGoal: double&lt;br/&gt; filterAttributes: String[]&lt;/p&gt;" style="verticalAlign=top;align=left;overflow=fill;fontSize=14;fontFamily=Helvetica;html=1;rounded=0;shadow=0;comic=0;labelBackgroundColor=none;strokeWidth=1;" parent="1" vertex="1">
+ <mxGeometry x="-880" y="180" width="392" height="309" as="geometry" />
+ </mxCell>
+ <mxCell id="node10" value="&lt;p style=&quot;margin:0px;margin-top:4px;text-align:center;&quot;&gt;&lt;b&gt;User&lt;/b&gt;&lt;/p&gt;&lt;hr size=&quot;1&quot;/&gt;&lt;p style=&quot;margin:0 0 0 4px;line-height:1.6;&quot;&gt;+ User(String, int, List&amp;lt;Integer&amp;gt;, UserType): &lt;/p&gt;&lt;hr size=&quot;1&quot;/&gt;&lt;p style=&quot;margin:0 0 0 4px;line-height:1.6;&quot;&gt;- type: UserType&lt;br/&gt;- username: String&lt;br/&gt;- basket: List&amp;lt;Integer&amp;gt;&lt;/p&gt;&lt;hr size=&quot;1&quot;/&gt;&lt;p style=&quot;margin:0 0 0 4px;line-height:1.6;&quot;&gt;+ addToBasket(Need): void&lt;br/&gt;+ withoutPasswordHash(): User&lt;br/&gt;+ verifyPassword(String): boolean&lt;br/&gt;+ create(String, String): User&lt;br/&gt;+ removeBasketNeed(Integer): void&lt;br/&gt;+ toString(): String&lt;br/&gt;+ copyPassword(User): void&lt;/p&gt;&lt;hr size=&quot;1&quot;/&gt;&lt;p style=&quot;margin:0 0 0 4px;line-height:1.6;&quot;&gt; type: UserType&lt;br/&gt; basket: Integer[]&lt;br/&gt; username: String&lt;/p&gt;" style="verticalAlign=top;align=left;overflow=fill;fontSize=14;fontFamily=Helvetica;html=1;rounded=0;shadow=0;comic=0;labelBackgroundColor=none;strokeWidth=1;" parent="1" vertex="1">
+ <mxGeometry x="-410.5" y="1310" width="293" height="320" as="geometry" />
+ </mxCell>
+ <mxCell id="node14" value="&lt;p style=&quot;margin:0px;margin-top:4px;text-align:center;&quot;&gt;&lt;b&gt;UserAuth&lt;/b&gt;&lt;/p&gt;&lt;hr size=&quot;1&quot;/&gt;&lt;p style=&quot;margin:0 0 0 4px;line-height:1.6;&quot;&gt;+ UserAuth(String, String, LocalDateTime): &lt;/p&gt;&lt;hr size=&quot;1&quot;/&gt;&lt;p style=&quot;margin:0 0 0 4px;line-height:1.6;&quot;&gt;~ key: String&lt;br/&gt;~ username: String&lt;br/&gt;~ expiration: LocalDateTime&lt;/p&gt;&lt;hr size=&quot;1&quot;/&gt;&lt;p style=&quot;margin:0 0 0 4px;line-height:1.6;&quot;&gt;+ generate(String): UserAuth&lt;/p&gt;&lt;hr size=&quot;1&quot;/&gt;&lt;p style=&quot;margin:0 0 0 4px;line-height:1.6;&quot;&gt; expiration: LocalDateTime&lt;br/&gt; key: String&lt;br/&gt; username: String&lt;/p&gt;" style="verticalAlign=top;align=left;overflow=fill;fontSize=14;fontFamily=Helvetica;html=1;rounded=0;shadow=0;comic=0;labelBackgroundColor=none;strokeWidth=1;" parent="1" vertex="1">
+ <mxGeometry x="-237" y="1011" width="282" height="179" as="geometry" />
+ </mxCell>
+ <mxCell id="node1" value="&lt;p style=&quot;margin:0px;margin-top:4px;text-align:center;&quot;&gt;&lt;i&gt;&amp;lt;&amp;lt;interface&amp;gt;&amp;gt;&lt;/i&gt;&lt;br/&gt;&lt;b&gt;UserAuthDAO&lt;/b&gt;&lt;/p&gt;&lt;hr size=&quot;1&quot;/&gt;&lt;p style=&quot;margin:0 0 0 4px;line-height:1.6;&quot;&gt;+ addUserAuth(UserAuth): void&lt;br/&gt;+ removeUserAuth(String): void&lt;br/&gt;+ getUserAuth(String): UserAuth&lt;/p&gt;" style="verticalAlign=top;align=left;overflow=fill;fontSize=14;fontFamily=Helvetica;html=1;rounded=0;shadow=0;comic=0;labelBackgroundColor=none;strokeWidth=1;" parent="1" vertex="1">
+ <mxGeometry x="-930" y="1040.5" width="218" height="120" as="geometry" />
+ </mxCell>
+ <mxCell id="node3" value="&lt;p style=&quot;margin:0px;margin-top:4px;text-align:center;&quot;&gt;&lt;b&gt;UserAuthFIleDAO&lt;/b&gt;&lt;/p&gt;&lt;hr size=&quot;1&quot;/&gt;&lt;p style=&quot;margin:0 0 0 4px;line-height:1.6;&quot;&gt;+ UserAuthFIleDAO(ObjectMapper, String): &lt;/p&gt;&lt;hr size=&quot;1&quot;/&gt;&lt;p style=&quot;margin:0 0 0 4px;line-height:1.6;&quot;&gt;- save(): void&lt;br/&gt;- load(): void&lt;br/&gt;+ addUserAuth(UserAuth): void&lt;br/&gt;+ getUserAuth(String): UserAuth&lt;br/&gt;+ removeUserAuth(String): void&lt;/p&gt;" style="verticalAlign=top;align=left;overflow=fill;fontSize=14;fontFamily=Helvetica;html=1;rounded=0;shadow=0;comic=0;labelBackgroundColor=none;strokeWidth=1;" parent="1" vertex="1">
+ <mxGeometry x="-650" y="1018.5" width="283" height="164" as="geometry" />
+ </mxCell>
+ <mxCell id="node4" value="&lt;p style=&quot;margin:0px;margin-top:4px;text-align:center;&quot;&gt;&lt;i&gt;&amp;lt;&amp;lt;interface&amp;gt;&amp;gt;&lt;/i&gt;&lt;br/&gt;&lt;b&gt;UserDAO&lt;/b&gt;&lt;/p&gt;&lt;hr size=&quot;1&quot;/&gt;&lt;p style=&quot;margin:0 0 0 4px;line-height:1.6;&quot;&gt;+ updateUser(User): User&lt;br/&gt;+ deleteUser(String): boolean&lt;br/&gt;+ getUser(String): User&lt;br/&gt;+ addUser(User): User&lt;/p&gt;&lt;hr size=&quot;1&quot;/&gt;&lt;p style=&quot;margin:0 0 0 4px;line-height:1.6;&quot;&gt; users: User[]&lt;br/&gt; userCount: int&lt;/p&gt;" style="verticalAlign=top;align=left;overflow=fill;fontSize=14;fontFamily=Helvetica;html=1;rounded=0;shadow=0;comic=0;labelBackgroundColor=none;strokeWidth=1;" parent="1" vertex="1">
+ <mxGeometry x="-1070" y="1387" width="203" height="150" as="geometry" />
+ </mxCell>
+ <mxCell id="node0" value="&lt;p style=&quot;margin:0px;margin-top:4px;text-align:center;&quot;&gt;&lt;b&gt;UserFileDAO&lt;/b&gt;&lt;/p&gt;&lt;hr size=&quot;1&quot;/&gt;&lt;p style=&quot;margin:0 0 0 4px;line-height:1.6;&quot;&gt;+ UserFileDAO(String, ObjectMapper): &lt;/p&gt;&lt;hr size=&quot;1&quot;/&gt;&lt;p style=&quot;margin:0 0 0 4px;line-height:1.6;&quot;&gt;- users: Map&amp;lt;String, User&amp;gt;&lt;/p&gt;&lt;hr size=&quot;1&quot;/&gt;&lt;p style=&quot;margin:0 0 0 4px;line-height:1.6;&quot;&gt;+ addUser(User): User&lt;br/&gt;+ deleteUser(String): boolean&lt;br/&gt;+ updateUser(User): User&lt;br/&gt;- save(): void&lt;br/&gt;+ getUser(String): User&lt;br/&gt;- load(): void&lt;/p&gt;&lt;hr size=&quot;1&quot;/&gt;&lt;p style=&quot;margin:0 0 0 4px;line-height:1.6;&quot;&gt; users: User[]&lt;br/&gt; userCount: int&lt;/p&gt;" style="verticalAlign=top;align=left;overflow=fill;fontSize=14;fontFamily=Helvetica;html=1;rounded=0;shadow=0;comic=0;labelBackgroundColor=none;strokeWidth=1;" parent="1" vertex="1">
+ <mxGeometry x="-770" y="1339" width="257" height="246" as="geometry" />
+ </mxCell>
+ <mxCell id="node18" value="&lt;p style=&quot;margin:0px;margin-top:4px;text-align:center;&quot;&gt;&lt;i&gt;&amp;lt;&amp;lt;enumeration&amp;gt;&amp;gt;&lt;/i&gt;&lt;br/&gt;&lt;b&gt;UserType&lt;/b&gt;&lt;/p&gt;&lt;hr size=&quot;1&quot;/&gt;&lt;p style=&quot;margin:0 0 0 4px;line-height:1.6;&quot;&gt;+ UserType(): &lt;/p&gt;&lt;hr size=&quot;1&quot;/&gt;&lt;p style=&quot;margin:0 0 0 4px;line-height:1.6;&quot;&gt;+ valueOf(String): UserType&lt;br/&gt;+ values(): UserType[]&lt;/p&gt;" style="verticalAlign=top;align=left;overflow=fill;fontSize=14;fontFamily=Helvetica;html=1;rounded=0;shadow=0;comic=0;labelBackgroundColor=none;strokeWidth=1;" parent="1" vertex="1">
+ <mxGeometry x="1.5" y="1400.25" width="194" height="139.5" as="geometry" />
+ </mxCell>
+ <mxCell id="edge0" value="" style="html=1;rounded=1;edgeStyle=orthogonalEdgeStyle;dashed=1;startArrow=none;endArrow=block;endSize=12;exitX=0.000;exitY=0.500;exitDx=0;exitDy=0;" parent="1" source="node2" target="node17" edge="1">
+ <mxGeometry width="50" height="50" relative="1" as="geometry">
+ <Array as="points" />
+ </mxGeometry>
+ </mxCell>
+ <mxCell id="edge5" value="" style="html=1;rounded=1;edgeStyle=orthogonalEdgeStyle;dashed=1;startArrow=none;endArrow=openThin;endSize=12;strokeColor=#595959;exitX=0.500;exitY=0.000;exitDx=0;exitDy=0;entryX=0.75;entryY=1;entryDx=0;entryDy=0;" parent="1" source="node2" target="node9" edge="1">
+ <mxGeometry width="50" height="50" relative="1" as="geometry">
+ <Array as="points">
+ <mxPoint x="-329" y="603" />
+ <mxPoint x="-586" y="603" />
+ </Array>
+ </mxGeometry>
+ </mxCell>
+ <mxCell id="edge19" value="" style="html=1;rounded=1;edgeStyle=orthogonalEdgeStyle;dashed=0;startArrow=diamondThin;endArrow=openThin;endSize=12;strokeColor=#595959;exitX=0.552;exitY=0.000;exitDx=0;exitDy=0;entryX=1;entryY=0.75;entryDx=0;entryDy=0;jumpStyle=none;startFill=1;" parent="1" source="node2" target="node9" edge="1">
+ <mxGeometry width="50" height="50" relative="1" as="geometry">
+ <Array as="points">
+ <mxPoint x="-314" y="412" />
+ <mxPoint x="-350" y="412" />
+ </Array>
+ </mxGeometry>
+ </mxCell>
+ <mxCell id="edge16" value="" style="html=1;rounded=1;edgeStyle=orthogonalEdgeStyle;dashed=0;startArrow=diamondThinstartSize=12;endArrow=openThin;endSize=12;strokeColor=#595959;exitX=1.000;exitY=0.319;exitDx=0;exitDy=0;entryX=0.000;entryY=0.653;entryDx=0;entryDy=0;" parent="1" source="node9" target="node19" edge="1">
+ <mxGeometry width="50" height="50" relative="1" as="geometry">
+ <Array as="points" />
+ </mxGeometry>
+ </mxCell>
+ <mxCell id="edge24" value="" style="html=1;rounded=1;edgeStyle=orthogonalEdgeStyle;dashed=0;startArrow=diamondThinstartSize=12;endArrow=openThin;endSize=12;strokeColor=#595959;" parent="1" source="node10" target="node18" edge="1">
+ <mxGeometry width="50" height="50" relative="1" as="geometry">
+ <Array as="points" />
+ <mxPoint x="-78.5" y="1380" as="sourcePoint" />
+ <mxPoint x="-28.5" y="1440" as="targetPoint" />
+ </mxGeometry>
+ </mxCell>
+ <mxCell id="edge21" value="" style="html=1;rounded=1;edgeStyle=orthogonalEdgeStyle;dashed=0;startArrow=diamondThin;endArrow=openThin;endSize=12;strokeColor=#595959;exitX=1;exitY=0.5;exitDx=0;exitDy=0;startFill=1;entryX=0;entryY=0.5;entryDx=0;entryDy=0;" parent="1" source="node3" target="node14" edge="1">
+ <mxGeometry width="50" height="50" relative="1" as="geometry">
+ <Array as="points">
+ <mxPoint x="-350" y="1100" />
+ </Array>
+ <mxPoint x="-290" y="1080" as="targetPoint" />
+ </mxGeometry>
+ </mxCell>
+ <mxCell id="label126" value="1" style="edgeLabel;resizable=0;html=1;align=left;verticalAlign=top;strokeColor=default;" parent="edge21" vertex="1" connectable="0">
+ <mxGeometry x="-456" y="1091" as="geometry" />
+ </mxCell>
+ <mxCell id="edge2" value="" style="html=1;rounded=1;edgeStyle=orthogonalEdgeStyle;dashed=1;startArrow=none;endArrow=block;endSize=12;exitX=0.000;exitY=0.500;exitDx=0;exitDy=0;entryX=1.000;entryY=0.500;entryDx=0;entryDy=0;jumpStyle=none;" parent="1" source="node3" target="node1" edge="1">
+ <mxGeometry width="50" height="50" relative="1" as="geometry">
+ <Array as="points" />
+ </mxGeometry>
+ </mxCell>
+ <mxCell id="edge6" value="" style="html=1;rounded=1;edgeStyle=orthogonalEdgeStyle;dashed=0;startArrow=diamondThin;endArrow=openThin;endSize=12;strokeColor=#595959;entryX=0.005;entryY=0.466;entryDx=0;entryDy=0;entryPerimeter=0;startFill=1;exitX=1;exitY=0.488;exitDx=0;exitDy=0;exitPerimeter=0;" parent="1" source="node0" target="node10" edge="1">
+ <mxGeometry width="50" height="50" relative="1" as="geometry">
+ <Array as="points" />
+ <mxPoint x="-490" y="1459" as="sourcePoint" />
+ </mxGeometry>
+ </mxCell>
+ <mxCell id="edge1" value="" style="html=1;rounded=1;edgeStyle=orthogonalEdgeStyle;dashed=1;startArrow=none;endArrow=block;endSize=12;exitX=0.000;exitY=0.500;exitDx=0;exitDy=0;entryX=1.000;entryY=0.500;entryDx=0;entryDy=0;" parent="1" source="node0" target="node4" edge="1">
+ <mxGeometry width="50" height="50" relative="1" as="geometry">
+ <Array as="points" />
+ </mxGeometry>
+ </mxCell>
+ </root>
+ </mxGraphModel>
+ </diagram>
+</mxfile>
diff --git a/docs/u-fund-viewmodel.drawio.png b/docs/u-fund-viewmodel.drawio.png
new file mode 100644
index 0000000..d92098b
--- /dev/null
+++ b/docs/u-fund-viewmodel.drawio.png
Binary files differ
diff --git a/docs/u-fund-viewmodel.drawio.xml b/docs/u-fund-viewmodel.drawio.xml
new file mode 100644
index 0000000..afd1d33
--- /dev/null
+++ b/docs/u-fund-viewmodel.drawio.xml
@@ -0,0 +1,134 @@
+<mxfile host="Electron" agent="Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) draw.io/26.2.2 Chrome/134.0.6998.178 Electron/35.1.2 Safari/537.36" version="26.2.2">
+ <diagram id="WOSjVCChJG0uuKkKcy1j" name="Page-1">
+ <mxGraphModel dx="3757" dy="1271" grid="1" gridSize="10" guides="1" tooltips="1" connect="1" arrows="1" fold="1" page="0" pageScale="1" pageWidth="850" pageHeight="1100" background="none" math="0" shadow="0">
+ <root>
+ <mxCell id="0" />
+ <mxCell id="1" parent="0" />
+ <mxCell id="node5" value="&lt;p style=&quot;margin:0px;margin-top:4px;text-align:center;&quot;&gt;&lt;b&gt;AuthController&lt;/b&gt;&lt;/p&gt;&lt;hr size=&quot;1&quot;/&gt;&lt;p style=&quot;margin:0 0 0 4px;line-height:1.6;&quot;&gt;+ AuthController(AuthService): &lt;/p&gt;&lt;hr size=&quot;1&quot;/&gt;&lt;p style=&quot;margin:0 0 0 4px;line-height:1.6;&quot;&gt;+ login(Map&amp;lt;String, String&amp;gt;): ResponseEntity&amp;lt;String&amp;gt;&lt;br/&gt;+ logout(String): ResponseEntity&amp;lt;Object&amp;gt;&lt;/p&gt;" style="verticalAlign=top;align=left;overflow=fill;fontSize=14;fontFamily=Helvetica;html=1;rounded=0;shadow=0;comic=0;labelBackgroundColor=none;strokeWidth=1;" parent="1" vertex="1">
+ <mxGeometry x="-1910" y="940" width="340" height="129" as="geometry" />
+ </mxCell>
+ <mxCell id="node13" value="&lt;p style=&quot;margin:0px;margin-top:4px;text-align:center;&quot;&gt;&lt;b&gt;AuthService&lt;/b&gt;&lt;/p&gt;&lt;hr size=&quot;1&quot;/&gt;&lt;p style=&quot;margin:0 0 0 4px;line-height:1.6;&quot;&gt;+ AuthService(UserAuthDAO, UserService): &lt;/p&gt;&lt;hr size=&quot;1&quot;/&gt;&lt;p style=&quot;margin:0 0 0 4px;line-height:1.6;&quot;&gt;+ login(String, String): String&lt;br/&gt;+ keyIsValid(String): void&lt;br/&gt;+ keyHasAccessToCupboard(String): void&lt;br/&gt;+ keyHasAccessToUser(String, String): void&lt;br/&gt;+ logout(String): void&lt;/p&gt;" style="verticalAlign=top;align=left;overflow=fill;fontSize=14;fontFamily=Helvetica;html=1;rounded=0;shadow=0;comic=0;labelBackgroundColor=none;strokeWidth=1;" parent="1" vertex="1">
+ <mxGeometry x="-1337" y="930" width="285" height="190" as="geometry" />
+ </mxCell>
+ <mxCell id="node12" value="&lt;p style=&quot;margin:0px;margin-top:4px;text-align:center;&quot;&gt;&lt;b&gt;CupboardController&lt;/b&gt;&lt;/p&gt;&lt;hr size=&quot;1&quot;/&gt;&lt;p style=&quot;margin:0 0 0 4px;line-height:1.6;&quot;&gt;+ CupboardController(CupboardService, AuthService): &lt;/p&gt;&lt;hr size=&quot;1&quot;/&gt;&lt;p style=&quot;margin:0 0 0 4px;line-height:1.6;&quot;&gt;+ createNeed(Map&amp;lt;String, Object&amp;gt;, String): ResponseEntity&amp;lt;Object&amp;gt;&lt;br/&gt;+ deleteNeed(int, String): ResponseEntity&amp;lt;Object&amp;gt;&lt;br/&gt;- of(Object[]): Object[]&lt;br/&gt;+ getNeed(int): ResponseEntity&amp;lt;Object&amp;gt;&lt;br/&gt;+ checkoutNeeds(List&amp;lt;Map&amp;lt;String, Integer&amp;gt;&amp;gt;, String): ResponseEntity&amp;lt;Object&amp;gt;&lt;br/&gt;+ searchNeeds(String): ResponseEntity&amp;lt;Object&amp;gt;&lt;br/&gt;+ updateNeed(Need, int, String): ResponseEntity&amp;lt;Object&amp;gt;&lt;/p&gt;&lt;hr size=&quot;1&quot;/&gt;&lt;p style=&quot;margin:0 0 0 4px;line-height:1.6;&quot;&gt; needs: ResponseEntity&amp;lt;Object&amp;gt;&lt;/p&gt;" style="verticalAlign=top;align=left;overflow=fill;fontSize=14;fontFamily=Helvetica;html=1;rounded=0;shadow=0;comic=0;labelBackgroundColor=none;strokeWidth=1;" parent="1" vertex="1">
+ <mxGeometry x="-1771" y="395" width="501" height="235" as="geometry" />
+ </mxCell>
+ <mxCell id="node16" value="&lt;p style=&quot;margin:0px;margin-top:4px;text-align:center;&quot;&gt;&lt;b&gt;CupboardService&lt;/b&gt;&lt;/p&gt;&lt;hr size=&quot;1&quot;/&gt;&lt;p style=&quot;margin:0 0 0 4px;line-height:1.6;&quot;&gt;+ CupboardService(AuthService, CupboardDAO): &lt;/p&gt;&lt;hr size=&quot;1&quot;/&gt;&lt;p style=&quot;margin:0 0 0 4px;line-height:1.6;&quot;&gt;+ checkoutNeed(int, double, String): void&lt;br/&gt;+ createNeed(String, String, String, double, GoalType, boolean, String): Need&lt;br/&gt;+ getNeed(int): Need&lt;br/&gt;+ deleteNeed(int): boolean&lt;br/&gt;+ searchNeeds(String): Need[]&lt;br/&gt;+ updateNeed(Need, int): Need&lt;/p&gt;&lt;hr size=&quot;1&quot;/&gt;&lt;p style=&quot;margin:0 0 0 4px;line-height:1.6;&quot;&gt; needs: Need[]&lt;/p&gt;" style="verticalAlign=top;align=left;overflow=fill;fontSize=14;fontFamily=Helvetica;html=1;rounded=0;shadow=0;comic=0;labelBackgroundColor=none;strokeWidth=1;" parent="1" vertex="1">
+ <mxGeometry x="-1185" y="406" width="485" height="210" as="geometry" />
+ </mxCell>
+ <mxCell id="node11" value="&lt;p style=&quot;margin:0px;margin-top:4px;text-align:center;&quot;&gt;&lt;b&gt;UserController&lt;/b&gt;&lt;/p&gt;&lt;hr size=&quot;1&quot;/&gt;&lt;p style=&quot;margin:0 0 0 4px;line-height:1.6;&quot;&gt;+ UserController(UserService, AuthService): &lt;/p&gt;&lt;hr size=&quot;1&quot;/&gt;&lt;p style=&quot;margin:0 0 0 4px;line-height:1.6;&quot;&gt;+ deleteUser(String, String): ResponseEntity&amp;lt;Object&amp;gt;&lt;br/&gt;+ getUserCount(String): ResponseEntity&amp;lt;Object&amp;gt;&lt;br/&gt;+ createUser(Map&amp;lt;String, String&amp;gt;): ResponseEntity&amp;lt;Object&amp;gt;&lt;br/&gt;- of(Object[]): Object[]&lt;br/&gt;+ getUser(String, String): ResponseEntity&amp;lt;Object&amp;gt;&lt;br/&gt;+ updateUser(User, String, String): ResponseEntity&amp;lt;Object&amp;gt;&lt;/p&gt;" style="verticalAlign=top;align=left;overflow=fill;fontSize=14;fontFamily=Helvetica;html=1;rounded=0;shadow=0;comic=0;labelBackgroundColor=none;strokeWidth=1;" parent="1" vertex="1">
+ <mxGeometry x="-1898" y="1539" width="382" height="186" as="geometry" />
+ </mxCell>
+ <mxCell id="node8" value="&lt;p style=&quot;margin:0px;margin-top:4px;text-align:center;&quot;&gt;&lt;b&gt;UserService&lt;/b&gt;&lt;/p&gt;&lt;hr size=&quot;1&quot;/&gt;&lt;p style=&quot;margin:0 0 0 4px;line-height:1.6;&quot;&gt;+ UserService(UserDAO, CupboardService): &lt;/p&gt;&lt;hr size=&quot;1&quot;/&gt;&lt;p style=&quot;margin:0 0 0 4px;line-height:1.6;&quot;&gt;+ updateUser(User, String): User&lt;br/&gt;+ deleteUser(String): boolean&lt;br/&gt;+ createUser(String, String): User&lt;br/&gt;+ getUser(String): User&lt;/p&gt;&lt;hr size=&quot;1&quot;/&gt;&lt;p style=&quot;margin:0 0 0 4px;line-height:1.6;&quot;&gt; userCount: int&lt;/p&gt;" style="verticalAlign=top;align=left;overflow=fill;fontSize=14;fontFamily=Helvetica;html=1;rounded=0;shadow=0;comic=0;labelBackgroundColor=none;strokeWidth=1;" parent="1" vertex="1">
+ <mxGeometry x="-1326" y="1549" width="289" height="166" as="geometry" />
+ </mxCell>
+ <mxCell id="edge8" value="" style="html=1;rounded=1;edgeStyle=orthogonalEdgeStyle;dashed=0;startArrow=diamondThinstartSize=12;endArrow=openThin;endSize=12;strokeColor=#595959;entryX=0.000;entryY=0.399;entryDx=0;entryDy=0;" parent="1" target="node13" edge="1">
+ <mxGeometry width="50" height="50" relative="1" as="geometry">
+ <Array as="points">
+ <mxPoint x="-1570" y="1006" />
+ <mxPoint x="-1510" y="1006" />
+ </Array>
+ <mxPoint x="-1570" y="1006" as="sourcePoint" />
+ </mxGeometry>
+ </mxCell>
+ <mxCell id="label48" value="1" style="edgeLabel;resizable=0;html=1;align=left;verticalAlign=top;strokeColor=default;" parent="edge8" vertex="1" connectable="0">
+ <mxGeometry x="-1560" y="980" as="geometry" />
+ </mxCell>
+ <mxCell id="label52" value="1" style="edgeLabel;resizable=0;html=1;align=left;verticalAlign=top;strokeColor=default;" parent="edge8" vertex="1" connectable="0">
+ <mxGeometry x="-1360" y="990" as="geometry" />
+ </mxCell>
+ <mxCell id="edge14" value="" style="html=1;rounded=1;edgeStyle=orthogonalEdgeStyle;dashed=0;startArrow=diamondThinstartSize=12;endArrow=openThin;endSize=12;strokeColor=#595959;exitX=0.425;exitY=1.000;exitDx=0;exitDy=0;entryX=0.382;entryY=0.000;entryDx=0;entryDy=0;" parent="1" source="node13" target="node8" edge="1">
+ <mxGeometry width="50" height="50" relative="1" as="geometry">
+ <Array as="points" />
+ </mxGeometry>
+ </mxCell>
+ <mxCell id="label84" value="1" style="edgeLabel;resizable=0;html=1;align=left;verticalAlign=top;strokeColor=default;" parent="edge14" vertex="1" connectable="0">
+ <mxGeometry x="-1211" y="1090" as="geometry" />
+ </mxCell>
+ <mxCell id="label88" value="1" style="edgeLabel;resizable=0;html=1;align=left;verticalAlign=top;strokeColor=default;" parent="edge14" vertex="1" connectable="0">
+ <mxGeometry x="-1230" y="1520" as="geometry" />
+ </mxCell>
+ <mxCell id="edge18" value="" style="html=1;rounded=1;edgeStyle=orthogonalEdgeStyle;dashed=0;startArrow=diamondThinstartSize=12;endArrow=openThin;endSize=12;strokeColor=#595959;entryX=0.342;entryY=0.000;entryDx=0;entryDy=0;" parent="1" source="node12" target="node13" edge="1">
+ <mxGeometry width="50" height="50" relative="1" as="geometry">
+ <Array as="points">
+ <mxPoint x="-1525" y="885" />
+ <mxPoint x="-1239" y="885" />
+ </Array>
+ </mxGeometry>
+ </mxCell>
+ <mxCell id="label108" value="1" style="edgeLabel;resizable=0;html=1;align=left;verticalAlign=top;strokeColor=default;" parent="edge18" vertex="1" connectable="0">
+ <mxGeometry x="-1525" y="632" as="geometry" />
+ </mxCell>
+ <mxCell id="label112" value="1" style="edgeLabel;resizable=0;html=1;align=left;verticalAlign=top;strokeColor=default;" parent="edge18" vertex="1" connectable="0">
+ <mxGeometry x="-1260" y="900" as="geometry" />
+ </mxCell>
+ <mxCell id="edge17" value="" style="html=1;rounded=1;edgeStyle=orthogonalEdgeStyle;dashed=0;startArrow=diamondThinstartSize=12;endArrow=openThin;endSize=12;strokeColor=#595959;exitX=1.000;exitY=0.528;exitDx=0;exitDy=0;entryX=0.000;entryY=0.531;entryDx=0;entryDy=0;" parent="1" source="node12" target="node16" edge="1">
+ <mxGeometry width="50" height="50" relative="1" as="geometry">
+ <Array as="points" />
+ </mxGeometry>
+ </mxCell>
+ <mxCell id="label102" value="1" style="edgeLabel;resizable=0;html=1;align=left;verticalAlign=top;strokeColor=default;" parent="edge17" vertex="1" connectable="0">
+ <mxGeometry x="-1270" y="502" as="geometry" />
+ </mxCell>
+ <mxCell id="label106" value="1" style="edgeLabel;resizable=0;html=1;align=left;verticalAlign=top;strokeColor=default;" parent="edge17" vertex="1" connectable="0">
+ <mxGeometry x="-1210" y="520" as="geometry" />
+ </mxCell>
+ <mxCell id="edge22" value="" style="html=1;rounded=1;edgeStyle=orthogonalEdgeStyle;dashed=0;startArrow=diamondThinstartSize=12;endArrow=openThin;endSize=12;strokeColor=#595959;entryX=0.500;entryY=0.000;entryDx=0;entryDy=0;" parent="1" source="node16" target="node13" edge="1">
+ <mxGeometry width="50" height="50" relative="1" as="geometry">
+ <Array as="points">
+ <mxPoint x="-978" y="773" />
+ <mxPoint x="-1194" y="773" />
+ </Array>
+ </mxGeometry>
+ </mxCell>
+ <mxCell id="label132" value="1" style="edgeLabel;resizable=0;html=1;align=left;verticalAlign=top;strokeColor=default;" parent="edge22" vertex="1" connectable="0">
+ <mxGeometry x="-989" y="630" as="geometry" />
+ </mxCell>
+ <mxCell id="label136" value="1" style="edgeLabel;resizable=0;html=1;align=left;verticalAlign=top;strokeColor=default;" parent="edge22" vertex="1" connectable="0">
+ <mxGeometry x="-1205" y="870" as="geometry" />
+ </mxCell>
+ <mxCell id="edge7" value="" style="html=1;rounded=1;edgeStyle=orthogonalEdgeStyle;dashed=0;startArrow=diamondThinstartSize=12;endArrow=openThin;endSize=12;strokeColor=#595959;exitX=0.987;exitY=0.000;exitDx=0;exitDy=0;" parent="1" source="node11" target="node13" edge="1">
+ <mxGeometry width="50" height="50" relative="1" as="geometry">
+ <Array as="points">
+ <mxPoint x="-1521" y="1052" />
+ </Array>
+ </mxGeometry>
+ </mxCell>
+ <mxCell id="label42" value="1" style="edgeLabel;resizable=0;html=1;align=left;verticalAlign=top;strokeColor=default;" parent="edge7" vertex="1" connectable="0">
+ <mxGeometry x="-1532" y="1510" as="geometry" />
+ </mxCell>
+ <mxCell id="label46" value="1" style="edgeLabel;resizable=0;html=1;align=left;verticalAlign=top;strokeColor=default;" parent="edge7" vertex="1" connectable="0">
+ <mxGeometry x="-1360" y="1060" as="geometry" />
+ </mxCell>
+ <mxCell id="edge13" value="" style="html=1;rounded=1;edgeStyle=orthogonalEdgeStyle;dashed=0;startArrow=diamondThinstartSize=12;endArrow=openThin;endSize=12;strokeColor=#595959;exitX=1.000;exitY=0.515;exitDx=0;exitDy=0;" parent="1" source="node11" target="node8" edge="1">
+ <mxGeometry width="50" height="50" relative="1" as="geometry">
+ <Array as="points">
+ <mxPoint x="-1370" y="1635" />
+ <mxPoint x="-1370" y="1635" />
+ </Array>
+ </mxGeometry>
+ </mxCell>
+ <mxCell id="label78" value="1" style="edgeLabel;resizable=0;html=1;align=left;verticalAlign=top;strokeColor=default;" parent="edge13" vertex="1" connectable="0">
+ <mxGeometry x="-1495" y="1614" as="geometry" />
+ </mxCell>
+ <mxCell id="label82" value="1" style="edgeLabel;resizable=0;html=1;align=left;verticalAlign=top;strokeColor=default;" parent="edge13" vertex="1" connectable="0">
+ <mxGeometry x="-1363" y="1636" as="geometry" />
+ </mxCell>
+ <mxCell id="edge12" value="" style="html=1;rounded=1;edgeStyle=orthogonalEdgeStyle;dashed=0;startArrow=diamondThinstartSize=12;endArrow=openThin;endSize=12;strokeColor=#595959;exitX=0.500;exitY=0.000;exitDx=0;exitDy=0;" parent="1" source="node8" target="node16" edge="1">
+ <mxGeometry width="50" height="50" relative="1" as="geometry">
+ <Array as="points">
+ <mxPoint x="-1181" y="1286" />
+ <mxPoint x="-963" y="1286" />
+ </Array>
+ </mxGeometry>
+ </mxCell>
+ <mxCell id="label72" value="1" style="edgeLabel;resizable=0;html=1;align=left;verticalAlign=top;strokeColor=default;" parent="edge12" vertex="1" connectable="0">
+ <mxGeometry x="-1192" y="1524" as="geometry" />
+ </mxCell>
+ <mxCell id="label76" value="1" style="edgeLabel;resizable=0;html=1;align=left;verticalAlign=top;strokeColor=default;" parent="edge12" vertex="1" connectable="0">
+ <mxGeometry x="-950" y="621" as="geometry" />
+ </mxCell>
+ </root>
+ </mxGraphModel>
+ </diagram>
+</mxfile>