aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorsowgro <tpoke.ferrari@gmail.com>2025-03-17 23:16:29 -0400
committersowgro <tpoke.ferrari@gmail.com>2025-03-17 23:16:29 -0400
commit68515441acd77d3356e8ec8b58700411661fec13 (patch)
treef3e08e4eecb5c06c8149d56ca08253a3c2d92607
parent7a5c5073e9e410b3ccc3ab7902a0d6f558277c7d (diff)
parent275a6062007380389b7a8f1b8958e8033b4f0925 (diff)
downloadJellySolutions-68515441acd77d3356e8ec8b58700411661fec13.tar.gz
JellySolutions-68515441acd77d3356e8ec8b58700411661fec13.tar.bz2
JellySolutions-68515441acd77d3356e8ec8b58700411661fec13.zip
Merge remote-tracking branch 'refs/remotes/origin/main' into funding_basket
# Conflicts: # ufund-ui/src/app/components/funding-basket/funding-basket.component.ts
-rw-r--r--docs/CodeCoverage.pngbin72506 -> 84383 bytes
-rw-r--r--docs/CupboardPage.jpegbin0 -> 2359696 bytes
-rw-r--r--docs/DashboardPage.jpegbin0 -> 2424408 bytes
-rw-r--r--docs/DesignDoc.md38
-rw-r--r--docs/FundingBasketPage.jpegbin0 -> 2508103 bytes
-rw-r--r--docs/HomePage.jpegbin0 -> 2330104 bytes
-rw-r--r--docs/LoginPage.jpegbin0 -> 2347376 bytes
-rw-r--r--docs/NeedPage.jpegbin0 -> 2471361 bytes
-rw-r--r--ufund-api/data/cupboard.json4
-rw-r--r--ufund-api/data/userAuths.json1
-rw-r--r--ufund-api/data/users.json15
-rw-r--r--ufund-api/pom.xml6
-rw-r--r--ufund-api/src/main/java/com/ufund/api/ufundapi/DuplicateKeyException.java7
-rw-r--r--ufund-api/src/main/java/com/ufund/api/ufundapi/controller/AuthController.java62
-rw-r--r--ufund-api/src/main/java/com/ufund/api/ufundapi/controller/CupboardController.java74
-rw-r--r--ufund-api/src/main/java/com/ufund/api/ufundapi/controller/UserController.java81
-rw-r--r--ufund-api/src/main/java/com/ufund/api/ufundapi/model/Need.java15
-rw-r--r--ufund-api/src/main/java/com/ufund/api/ufundapi/model/User.java66
-rw-r--r--ufund-api/src/main/java/com/ufund/api/ufundapi/model/UserAuth.java43
-rw-r--r--ufund-api/src/main/java/com/ufund/api/ufundapi/persistence/CupboardDAO.java21
-rw-r--r--ufund-api/src/main/java/com/ufund/api/ufundapi/persistence/CupboardFileDAO.java (renamed from ufund-api/src/main/java/com/ufund/api/ufundapi/persistence/CupboardFileDao.java)27
-rw-r--r--ufund-api/src/main/java/com/ufund/api/ufundapi/persistence/UserAuthDAO.java32
-rw-r--r--ufund-api/src/main/java/com/ufund/api/ufundapi/persistence/UserAuthFIleDAO.java73
-rw-r--r--ufund-api/src/main/java/com/ufund/api/ufundapi/persistence/UserDAO.java21
-rw-r--r--ufund-api/src/main/java/com/ufund/api/ufundapi/persistence/UserFileDAO.java88
-rw-r--r--ufund-api/src/main/java/com/ufund/api/ufundapi/service/AuthService.java70
-rw-r--r--ufund-api/src/main/java/com/ufund/api/ufundapi/service/CupboardService.java109
-rw-r--r--ufund-api/src/main/java/com/ufund/api/ufundapi/service/UserService.java72
-rw-r--r--ufund-api/src/main/resources/application.properties2
-rw-r--r--ufund-api/src/test/java/com/ufund/api/ufundapi/controller/AuthControllerTest.java104
-rw-r--r--ufund-api/src/test/java/com/ufund/api/ufundapi/controller/CupboardControllerTest.java114
-rw-r--r--ufund-api/src/test/java/com/ufund/api/ufundapi/controller/UserControllerTest.java191
-rw-r--r--ufund-api/src/test/java/com/ufund/api/ufundapi/model/NeedTest.java25
-rw-r--r--ufund-api/src/test/java/com/ufund/api/ufundapi/model/UserTest.java50
-rw-r--r--ufund-api/src/test/java/com/ufund/api/ufundapi/persistence/CupboardFileDAOTest.java (renamed from ufund-api/src/test/java/com/ufund/api/ufundapi/persistence/CupboardFileDaoTest.java)58
-rw-r--r--ufund-api/src/test/java/com/ufund/api/ufundapi/persistence/UserAuthFileDAOTest.java63
-rw-r--r--ufund-api/src/test/java/com/ufund/api/ufundapi/persistence/UserFileDAOTest.java51
-rw-r--r--ufund-api/src/test/java/com/ufund/api/ufundapi/service/AuthServiceTest.java110
-rw-r--r--ufund-api/src/test/java/com/ufund/api/ufundapi/service/CupboardServiceTest.java197
-rw-r--r--ufund-api/src/test/java/com/ufund/api/ufundapi/service/UserServiceTest.java126
-rw-r--r--ufund-ui/src/app/app-routing.module.ts2
-rw-r--r--ufund-ui/src/app/app.component.html4
-rw-r--r--ufund-ui/src/app/app.component.ts23
-rw-r--r--ufund-ui/src/app/app.module.ts14
-rw-r--r--ufund-ui/src/app/components/cupboard/cupboard.component.css21
-rw-r--r--ufund-ui/src/app/components/cupboard/cupboard.component.html63
-rw-r--r--ufund-ui/src/app/components/cupboard/cupboard.component.ts208
-rw-r--r--ufund-ui/src/app/components/dashboard/dashboard.component.html6
-rw-r--r--ufund-ui/src/app/components/dashboard/dashboard.component.ts4
-rw-r--r--ufund-ui/src/app/components/funding-basket/funding-basket.component.ts6
-rw-r--r--ufund-ui/src/app/components/home-page/home-page.component.html3
-rw-r--r--ufund-ui/src/app/components/login/login.component.css12
-rw-r--r--ufund-ui/src/app/components/login/login.component.html10
-rw-r--r--ufund-ui/src/app/components/login/login.component.ts93
-rw-r--r--ufund-ui/src/app/components/need-list/need-list.component.css24
-rw-r--r--ufund-ui/src/app/components/need-list/need-list.component.html20
-rw-r--r--ufund-ui/src/app/components/need-list/need-list.component.ts87
-rw-r--r--ufund-ui/src/app/components/need-page/need-page.component.html28
-rw-r--r--ufund-ui/src/app/components/need-page/need-page.component.ts11
-rw-r--r--ufund-ui/src/app/models/Need.ts2
-rw-r--r--ufund-ui/src/app/models/User.ts10
-rw-r--r--ufund-ui/src/app/services/cupboard.service.ts5
-rw-r--r--ufund-ui/src/app/services/users.service.ts48
63 files changed, 2230 insertions, 490 deletions
diff --git a/docs/CodeCoverage.png b/docs/CodeCoverage.png
index 72cbfc4..a795ef8 100644
--- a/docs/CodeCoverage.png
+++ b/docs/CodeCoverage.png
Binary files differ
diff --git a/docs/CupboardPage.jpeg b/docs/CupboardPage.jpeg
new file mode 100644
index 0000000..983563f
--- /dev/null
+++ b/docs/CupboardPage.jpeg
Binary files differ
diff --git a/docs/DashboardPage.jpeg b/docs/DashboardPage.jpeg
new file mode 100644
index 0000000..48a862b
--- /dev/null
+++ b/docs/DashboardPage.jpeg
Binary files differ
diff --git a/docs/DesignDoc.md b/docs/DesignDoc.md
index 80d3778..5d673b2 100644
--- a/docs/DesignDoc.md
+++ b/docs/DesignDoc.md
@@ -20,12 +20,14 @@ Our project is intended to create a space to fund aquatic conservation, from phy
> _**[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 |
+| Term | Definition |
+|------|-------------|
+| SPA | Single Page |
## Requirements
@@ -39,6 +41,8 @@ This section describes the features of the application.
### 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. 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. Needs are saved so users and managers will see when they are updated.
+
### MVP Features
> _**[Sprint 4]** Provide a list of top-level Epics and/or Stories of the MVP._
@@ -50,12 +54,13 @@ This section describes the features of the application.
This section describes the application domain.
-![Domain Model](domain-model-placeholder.png)
+![Domain Model](domain-model.png)
> _**[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._
+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. 1 or more managers will manaager the 1 cupboard, add, removing, and editing needs within it.
## Architecture and Design
@@ -86,6 +91,25 @@ This section describes the web interface flow; this is how the user views and in
> (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]** )_
+Home Page:
+![The Tiers & Layers of the Architecture](HomePage.jpeg)
+
+Login Page:
+![The Tiers & Layers of the Architecture](LoginPage.jpeg)
+
+Dashboard:
+![The Tiers & Layers of the Architecture](DashboardPage.jpeg)
+
+Cupboard:
+![The Tiers & Layers of the Architecture](CuboardPage.jpeg)
+
+Need page:
+![The Tiers & Layers of the Architecture](NeedPage.jpeg)
+
+Funding basket:
+![The Tiers & Layers of the Architecture](FundingBasketPage.jpeg)
+
+
### 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
@@ -127,10 +151,12 @@ The Model Tier contains the classes responsible for handling and serving Need da
> 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.)_
>
-![Replace with your Model Tier class diagram 1, etc.](model-placeholder.png)
+![Replace with your Model Tier class diagram 1, etc.](u-fund.drawio.png)
## OO Design Principles
@@ -157,6 +183,8 @@ The Model Tier contains the classes responsible for handling and serving Need da
> _This section will provide information about the testing performed
> and the results of the testing._
+Currently around 100 tests, with roughly 95% coverage overall. Model tier is above 95% individually.
+
### Acceptance Testing
> _**[Sprint 2 & 4]** Report on the number of user stories that have passed all their
> acceptance criteria tests, the number that have some acceptance
diff --git a/docs/FundingBasketPage.jpeg b/docs/FundingBasketPage.jpeg
new file mode 100644
index 0000000..733d332
--- /dev/null
+++ b/docs/FundingBasketPage.jpeg
Binary files differ
diff --git a/docs/HomePage.jpeg b/docs/HomePage.jpeg
new file mode 100644
index 0000000..f747f0f
--- /dev/null
+++ b/docs/HomePage.jpeg
Binary files differ
diff --git a/docs/LoginPage.jpeg b/docs/LoginPage.jpeg
new file mode 100644
index 0000000..00ef5af
--- /dev/null
+++ b/docs/LoginPage.jpeg
Binary files differ
diff --git a/docs/NeedPage.jpeg b/docs/NeedPage.jpeg
new file mode 100644
index 0000000..8ffec79
--- /dev/null
+++ b/docs/NeedPage.jpeg
Binary files differ
diff --git a/ufund-api/data/cupboard.json b/ufund-api/data/cupboard.json
index bb7ec03..abc2293 100644
--- a/ufund-api/data/cupboard.json
+++ b/ufund-api/data/cupboard.json
@@ -1,3 +1 @@
-[
- {"name":"Money for coral","id":1,"maxGoal":100.0,"type":"MONETARY","filterAttributes":null,"Current":0.0}
-] \ No newline at end of file
+[{"name":"Jellyfish Hats","id":26,"maxGoal":10.0,"type":"MONETARY","filterAttributes":["#savethejellyfish","Clothing","Storefront"],"current":0.0},{"name":"Pollution Re-Filtering","id":27,"maxGoal":1.0E7,"type":"MONETARY","filterAttributes":["Cleanup","Donations","Third-Party"],"current":0.0},{"name":"Coral re-re-habilitation","id":28,"maxGoal":10000.0,"type":"MONETARY","filterAttributes":["Preservation","#savethecoral","#helloPhil"],"current":0.0}] \ No newline at end of file
diff --git a/ufund-api/data/userAuths.json b/ufund-api/data/userAuths.json
new file mode 100644
index 0000000..fea8a6f
--- /dev/null
+++ b/ufund-api/data/userAuths.json
@@ -0,0 +1 @@
+[{"key":"e48872fa-b89f-494a-b681-11a809d32ff4","username":"phil","expiration":"2025-04-14T17:20:23.265745224"},{"key":"31fcbc15-9902-41d2-8d6f-5b0e40ebddd2","username":"phil","expiration":"2025-04-14T16:45:41.082560826"},{"key":"3fdd4e7e-bc59-4e3a-ba5c-177d0833022a","username":"sowgro","expiration":"2025-04-14T18:35:26.42935739"},{"key":"13d12a6d-6825-4c1d-8b22-ba960de140b8","username":"phil","expiration":"2025-04-14T17:20:58.531711142"},{"key":"a07ae51f-f80b-4001-95f1-48c11d4917a4","username":"phil","expiration":"2025-04-05T15:04:30.900359001"},{"key":"cc49c007-fd36-4828-b8fa-f5b85ad0676d","username":"phil","expiration":"2025-04-14T16:46:12.80566798"},{"key":"da61796e-402a-4a80-88ae-7607a37989a4","username":"phil","expiration":"2025-04-14T17:07:10.618039573"},{"key":"e14f8ee5-5780-4b9b-bf34-7a41c2bbfcb4","username":"phil","expiration":"2025-04-05T13:46:10.90733016"},{"key":"d7cef571-0f76-49fe-941f-ecbeae69557a","username":"phil","expiration":"2025-04-05T15:14:00.363201102"},{"key":"125a4847-3a1c-4834-961f-7f96e997f92e","username":"sowgro","expiration":"2025-04-14T18:35:38.922687686"},{"key":"3fc557b6-0306-4779-9b74-b7292a5cf1cc","username":"phil","expiration":"2025-04-14T16:06:08.564069822"},{"key":"77392d17-6e0c-45ec-857d-6595a55ddd97","username":"phil","expiration":"2025-04-14T16:06:48.335542315"},{"key":"eeea7b02-7265-4a26-96de-a8ad1860c533","username":"phil","expiration":"2025-03-31T23:04:47.455490668"},{"key":"e121c7c6-e534-4fde-8a78-4f175e9db9c8","username":"phil","expiration":"2025-04-14T17:23:23.218442063"},{"key":"af38add5-b100-4b96-9ffb-5afaccd59979","username":"adf","expiration":"2025-04-14T18:18:47.670506361"},{"key":"2aeaab28-99c9-4b45-bdef-82096c70945e","username":"phil","expiration":"2025-04-14T17:19:48.552121268"},{"key":"58e4e2a2-3a36-4fd6-8bb1-ad0831664d01","username":"phil","expiration":"2025-04-12T23:17:42.638952959"},{"key":"4df8bb43-f597-49ca-863a-6e0da5280d79","username":"phil","expiration":"2025-04-14T01:13:53.799331844"},{"key":"ad6d92d4-c496-407c-823a-edaa386e67ed","username":"phil","expiration":"2025-04-14T17:07:36.032623002"},{"key":"718be1e2-cfc7-44a6-b3c6-965684d1d0a9","username":"adf","expiration":"2025-04-14T18:35:58.888847176"},{"key":"85319427-4603-4a16-af33-2e9525dda8c0","username":"phil","expiration":"2025-04-14T00:39:34.952183453"},{"key":"f5f53053-ef5e-4850-93a0-3dc20646f78b","username":"sowgro","expiration":"2025-04-14T18:11:29.438554549"},{"key":"efc531fb-ab24-4d5a-a2f5-7f4ede74819f","username":"phil","expiration":"2025-04-13T19:41:51.017327545"},{"key":"f1d6a110-4232-4ef3-b6ec-9a2962664158","username":"phil","expiration":"2025-04-14T17:23:40.834526839"}] \ No newline at end of file
diff --git a/ufund-api/data/users.json b/ufund-api/data/users.json
index 4e98a14..8543a55 100644
--- a/ufund-api/data/users.json
+++ b/ufund-api/data/users.json
@@ -1 +1,14 @@
-[{"name":"steve","password":null}] \ No newline at end of file
+[
+ {
+ "username": "phil",
+ "passwordHash": -1054080181,
+ "basket": [],
+ "type": "HELPER"
+ },
+ {
+ "username": "admin",
+ "passwordHash": 92668751,
+ "basket": [],
+ "type": "MANAGER"
+ }
+] \ No newline at end of file
diff --git a/ufund-api/pom.xml b/ufund-api/pom.xml
index ce96d60..d2e8fb8 100644
--- a/ufund-api/pom.xml
+++ b/ufund-api/pom.xml
@@ -73,8 +73,10 @@
<artifactId>jacoco-maven-plugin</artifactId>
<version>${jacoco.version}</version>
<configuration>
- <destfile>/target/coverage-reports/jacoco-unit.exec</destfile>
- <datafile>/target/coverage-reports/jacoco-unit.exec</datafile>
+ <!-- not sure what these were supposed to do, but 'datafile' is not a
+ configuration key for jacoco, and changing it to 'dataFile' breaks the build -->
+ <!-- <datafile>/target/coverage-reports/jacoco-unit.exec</datafile>-->
+ <!-- <datafile>/target/coverage-reports/jacoco-unit.exec</datafile>-->
</configuration>
<executions>
<execution>
diff --git a/ufund-api/src/main/java/com/ufund/api/ufundapi/DuplicateKeyException.java b/ufund-api/src/main/java/com/ufund/api/ufundapi/DuplicateKeyException.java
new file mode 100644
index 0000000..69ce6c0
--- /dev/null
+++ b/ufund-api/src/main/java/com/ufund/api/ufundapi/DuplicateKeyException.java
@@ -0,0 +1,7 @@
+package com.ufund.api.ufundapi;
+
+public class DuplicateKeyException extends Exception {
+ public DuplicateKeyException(String message) {
+ super(message);
+ }
+}
diff --git a/ufund-api/src/main/java/com/ufund/api/ufundapi/controller/AuthController.java b/ufund-api/src/main/java/com/ufund/api/ufundapi/controller/AuthController.java
new file mode 100644
index 0000000..b46d4ee
--- /dev/null
+++ b/ufund-api/src/main/java/com/ufund/api/ufundapi/controller/AuthController.java
@@ -0,0 +1,62 @@
+package com.ufund.api.ufundapi.controller;
+
+import java.io.IOException;
+import java.util.Map;
+
+import org.springframework.http.HttpStatus;
+import org.springframework.http.ResponseEntity;
+import org.springframework.web.bind.annotation.DeleteMapping;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestHeader;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+import com.ufund.api.ufundapi.service.AuthService;
+
+@RestController
+@RequestMapping("auth")
+public class AuthController {
+ private final AuthService authService;
+
+ public AuthController(AuthService authService) {
+ this.authService = authService;
+ }
+
+ /**
+ * Attempts to log in as a user
+ *
+ * @param params A json object in the format {username: string, password: string}
+ * @return An api key and status OK if the authentication was successful,
+ * Status UNAUTHORIZED if the authentication failed and INTERNAL SERVER ERROR otherwise.
+ */
+ @PostMapping("")
+ public ResponseEntity<String> login(@RequestBody Map<String, String> params) {
+ String username = params.get("username");
+ String password = params.get("password");
+ try {
+ String key = authService.login(username, password);
+ return new ResponseEntity<>(key, HttpStatus.OK);
+ } catch (IllegalAccessException e) {
+ return new ResponseEntity<>(HttpStatus.UNAUTHORIZED);
+ } catch (IOException ex) {
+ return new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR);
+ }
+ }
+
+ /**
+ * Logs out the current user
+ *
+ * @param key The API sent by the client in the header
+ * @return OK if the user was successfully logged out, INTERNAL_SERVER_ERROR otherwise.
+ */
+ @DeleteMapping("")
+ public ResponseEntity<Object> logout(@RequestHeader("jelly-api-key") String key) {
+ try {
+ authService.logout(key);
+ return new ResponseEntity<>(HttpStatus.OK);
+ } catch (IOException e) {
+ return new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR);
+ }
+ }
+}
diff --git a/ufund-api/src/main/java/com/ufund/api/ufundapi/controller/CupboardController.java b/ufund-api/src/main/java/com/ufund/api/ufundapi/controller/CupboardController.java
index faaa98a..9592490 100644
--- a/ufund-api/src/main/java/com/ufund/api/ufundapi/controller/CupboardController.java
+++ b/ufund-api/src/main/java/com/ufund/api/ufundapi/controller/CupboardController.java
@@ -1,6 +1,7 @@
package com.ufund.api.ufundapi.controller;
import java.io.IOException;
+import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;
@@ -17,37 +18,48 @@ import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import com.ufund.api.ufundapi.model.Need;
-import com.ufund.api.ufundapi.persistence.CupboardDAO;
+import com.ufund.api.ufundapi.model.Need.GoalType;
+import com.ufund.api.ufundapi.service.CupboardService;
+import com.ufund.api.ufundapi.DuplicateKeyException;
@RestController
@RequestMapping("cupboard")
public class CupboardController {
private static final Logger LOG = Logger.getLogger(CupboardController.class.getName());
- private final CupboardDAO cupboardDAO;
+ private final CupboardService cupboardService;
/**
* Create a cupboard controller to receive REST signals
*
- * @param cupboardDAO The Data Access Object
+ * @param cupboardService The Data Access Object
*/
- public CupboardController(CupboardDAO cupboardDAO) {
- this.cupboardDAO = cupboardDAO;
+ public CupboardController(CupboardService cupboardService) {
+ this.cupboardService = cupboardService;
}
/**
* Creates a Need with the provided object
*
- * @param need The need to create
- * @return OK response and the need if it was successful, INTERNAL_SERVER_ERROR otherwise
+ * @param params The need to create
+ * @return OK response and the need if it was successful,
+ * CONFLICT if another need with the same name exists
+ * UNPROCESSABLE_ENTITY if the need contains bad data
+ * INTERNAL_SERVER_ERROR otherwise
*/
@PostMapping("")
- public ResponseEntity<Need> createNeed(@RequestBody Need need) {
+ public ResponseEntity<Need> createNeed(@RequestBody Map<String, Object> params) {
+ System.out.println(params);
+ String name = (String) params.get("name");
+ int maxGoal = (int) params.get("maxGoal");
+ Need.GoalType goalType = GoalType.valueOf((String) params.get("type"));
+
try {
- if (need.getMaxGoal() <= 0) {
- return new ResponseEntity<>(HttpStatus.BAD_REQUEST);
- }
- cupboardDAO.createNeed(need);
+ Need need = cupboardService.createNeed(name, maxGoal, goalType);
return new ResponseEntity<>(need, HttpStatus.OK);
+ } catch (DuplicateKeyException ex) {
+ return new ResponseEntity<>(HttpStatus.CONFLICT);
+ } catch (IllegalArgumentException ex) {
+ return new ResponseEntity<>(HttpStatus.UNPROCESSABLE_ENTITY);
} catch (IOException ex) {
return new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR);
}
@@ -66,7 +78,7 @@ public class CupboardController {
LOG.info("GET /needs");
try {
- Need[] needs = cupboardDAO.getNeeds();
+ Need[] needs = cupboardService.getNeeds();
return new ResponseEntity<>(needs, HttpStatus.OK);
} catch (IOException e) {
LOG.log(Level.SEVERE, e.getLocalizedMessage());
@@ -90,8 +102,8 @@ public class CupboardController {
LOG.info("GET /need/?name="+name);
try {
- Need[] needArray = cupboardDAO.findNeeds(name);
- return new ResponseEntity<>(needArray, HttpStatus.OK);
+ Need[] needs = cupboardService.searchNeeds(name);
+ return new ResponseEntity<>(needs, HttpStatus.OK);
} catch (IOException e) {
LOG.log(Level.SEVERE,e.getLocalizedMessage());
return new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR);
@@ -103,23 +115,20 @@ public class CupboardController {
*
* @param id The id used to locate the {@link Need need}
*
- * @return ResponseEntity with {@link Need need} object and HTTP status of OK if
- * found<br>
+ * @return ResponseEntity with {@link Need need} object and HTTP status of OK if found<br>
* ResponseEntity with HTTP status of NOT_FOUND if not found<br>
- * ResponseEntity with HTTP status of INTERNAL_SERVER_ERROR otherwise
*/
@GetMapping("/{id}")
public ResponseEntity<Need> getNeed(@PathVariable int id) {
LOG.log(Level.INFO, "GET /need/{0}", id);
try {
- Need need = cupboardDAO.getNeed(id);
+ Need need = cupboardService.getNeed(id);
if (need != null) {
return new ResponseEntity<>(need, HttpStatus.OK);
} else {
return new ResponseEntity<>(HttpStatus.NOT_FOUND);
}
-
} catch (IOException e) {
LOG.log(Level.SEVERE, e.getLocalizedMessage());
return new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR);
@@ -133,13 +142,20 @@ public class CupboardController {
* @param need The need to update
* @return OK response and the need if it was successful, or INTERNAL_SERVER_ERROR if there was an issue
*/
-
- @PutMapping("")
- public ResponseEntity<Need> updateNeed(@RequestBody Need need) {
+ @PutMapping("/{id}")
+ public ResponseEntity<Need> updateNeed(@RequestBody Need need, @PathVariable int id) {
try {
- need = cupboardDAO.updateNeed(need);
- return new ResponseEntity<>(need, HttpStatus.OK);
- } catch (IOException e) {
+ Need updatedNeed = cupboardService.updateNeed(need, id);
+ if (updatedNeed != null) {
+ return new ResponseEntity<>(need, HttpStatus.OK);
+ } else {
+ return new ResponseEntity<>(HttpStatus.NOT_FOUND);
+ }
+ } catch (IllegalArgumentException ex) {
+ ex.printStackTrace();
+ return new ResponseEntity<>(HttpStatus.BAD_REQUEST);
+ } catch (IOException ex) {
+ ex.printStackTrace();
return new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR);
}
}
@@ -153,9 +169,9 @@ public class CupboardController {
@DeleteMapping("/{id}")
public ResponseEntity<Need> deleteNeed(@PathVariable int id) {
try {
- if (cupboardDAO.getNeed(id) != null) {
- cupboardDAO.deleteNeed(id);
- return new ResponseEntity<>(HttpStatus.OK);
+ Need need = cupboardService.getNeed(id);
+ if (cupboardService.deleteNeed(id)) {
+ return new ResponseEntity<>(need, HttpStatus.OK);
} else {
return new ResponseEntity<>(HttpStatus.NOT_FOUND);
}
diff --git a/ufund-api/src/main/java/com/ufund/api/ufundapi/controller/UserController.java b/ufund-api/src/main/java/com/ufund/api/ufundapi/controller/UserController.java
index 4e5f156..adf17a1 100644
--- a/ufund-api/src/main/java/com/ufund/api/ufundapi/controller/UserController.java
+++ b/ufund-api/src/main/java/com/ufund/api/ufundapi/controller/UserController.java
@@ -1,6 +1,8 @@
package com.ufund.api.ufundapi.controller;
import java.io.IOException;
+import java.security.InvalidParameterException;
+import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;
@@ -12,43 +14,47 @@ import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestHeader;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
+import com.ufund.api.ufundapi.DuplicateKeyException;
import com.ufund.api.ufundapi.model.User;
-import com.ufund.api.ufundapi.persistence.UserDAO;
+import com.ufund.api.ufundapi.service.AuthService;
+import com.ufund.api.ufundapi.service.UserService;
@RestController
@RequestMapping("users")
public class UserController {
- private static final Logger LOG = Logger.getLogger(CupboardController.class.getName());
- private final UserDAO UserDAO;
+ private static final Logger LOG = Logger.getLogger(UserController.class.getName());
+ private final UserService userService;
+ private final AuthService authService;
- /**
- * Create a user controller to receive REST signals
- *
- * @param userDAO The Data Access Object
- */
- public UserController(UserDAO userDAO) {
- this.UserDAO = userDAO;
+ public UserController(UserService userService, AuthService authService) {
+ this.userService = userService;
+ this.authService = authService;
}
/**
* Creates a User with the provided object
- *
- * @param user The user to create
+ * @param params A map consisting of the parameters for a user
* @return OK response and the user if it was successful, INTERNAL_SERVER_ERROR
* otherwise
*/
@PostMapping("")
- public ResponseEntity<User> createUser(@RequestBody User user) {
+ public ResponseEntity<User> createUser(@RequestBody Map<String, String> params) {
+ String username = params.get("username");
+ String password = params.get("password");
+
try {
- if (UserDAO.createUser(user) != null) {
+ User user = userService.createUser(username, password);
+ if (user != null) {
return new ResponseEntity<>(user, HttpStatus.CREATED);
} else {
return new ResponseEntity<>(HttpStatus.CONFLICT);
}
-
+ } catch (DuplicateKeyException ex) {
+ return new ResponseEntity<>(HttpStatus.CONFLICT);
} catch (IOException ex) {
return new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR);
}
@@ -57,23 +63,27 @@ public class UserController {
/**
* Responds to the GET request for a {@linkplain User user} for the given id
*
+ * @param username The name of the user
+ * @param key The authentication key of the user
* @return ResponseEntity with {@link User user} object and HTTP status of OK if
* found<br>
* ResponseEntity with HTTP status of NOT_FOUND if not found<br>
* ResponseEntity with HTTP status of INTERNAL_SERVER_ERROR otherwise
*/
- @GetMapping("/{name}")
- public ResponseEntity<User> getUser(@PathVariable String name) {
- LOG.log(Level.INFO, "GET /user/{0}", name);
+ @GetMapping("/{username}")
+ public ResponseEntity<User> getUser(@PathVariable String username, @RequestHeader("jelly-api-key") String key) {
+ LOG.log(Level.INFO, "GET /user/{0}", username);
try {
- User user = UserDAO.getUser(name);
+ authService.authenticate(username, key);
+ User user = userService.getUser(username);
if (user != null) {
- return new ResponseEntity<>(user, HttpStatus.OK);
+ return new ResponseEntity<>(user.withoutPasswordHash(), HttpStatus.OK);
} else {
return new ResponseEntity<>(HttpStatus.NOT_FOUND);
}
-
+ } catch (IllegalAccessException ex) {
+ return new ResponseEntity<>(HttpStatus.UNAUTHORIZED);
} catch (IOException e) {
LOG.log(Level.SEVERE, e.getLocalizedMessage());
return new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR);
@@ -84,42 +94,53 @@ public class UserController {
/**
* Updates a User with the provided one
*
- * @param user The user to update
+ * @param user The user to update
+ * @param username The name of the user
+ * @param key The authentication key of the user
* @return OK response and the user if it was successful, or
* INTERNAL_SERVER_ERROR if there was an issue
*/
- @PutMapping("/{name}")
- public ResponseEntity<User> updateUser(@RequestBody User user, @PathVariable String name) {
+ @PutMapping("/{username}")
+ public ResponseEntity<User> updateUser(@RequestBody User user, @PathVariable String username, @RequestHeader("jelly-api-key") String key) {
try {
- user = UserDAO.updateUser(user, name);
+ authService.authenticate(username, key);
+ user = userService.updateUser(user, username);
if (user != null) {
return new ResponseEntity<>(user, HttpStatus.OK);
} else {
return new ResponseEntity<>(HttpStatus.NOT_FOUND);
}
-
+ } catch (InvalidParameterException ex) {
+ return new ResponseEntity<>(HttpStatus.BAD_REQUEST);
} catch (IOException e) {
return new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR);
+ } catch (IllegalAccessException e) {
+ return new ResponseEntity<>(HttpStatus.UNAUTHORIZED);
}
}
/**
* Deletes a user with the desired name
*
- * @param name The name of the user
+ * @param username The name of the user
+ * @param key The authentication key of the user
* @return OK if the user was deleted, NOT_FOUND if the user was not found, or
* INTERNAL_SERVER_ERROR if an error occurred
*/
- @DeleteMapping("/{name}")
- public ResponseEntity<User> deleteUser(@PathVariable String name) {
+ @DeleteMapping("/{username}")
+ public ResponseEntity<Boolean> deleteUser(@PathVariable String username, @RequestHeader("jelly-api-key") String key) {
+
try {
- if (UserDAO.deleteUser(name)) {
+ authService.authenticate(username, key);
+ if (userService.deleteUser(username)) {
return new ResponseEntity<>(HttpStatus.OK);
} else {
return new ResponseEntity<>(HttpStatus.NOT_FOUND);
}
} catch (IOException e) {
return new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR);
+ } catch (IllegalAccessException e) {
+ return new ResponseEntity<>(HttpStatus.UNAUTHORIZED);
}
}
diff --git a/ufund-api/src/main/java/com/ufund/api/ufundapi/model/Need.java b/ufund-api/src/main/java/com/ufund/api/ufundapi/model/Need.java
index 2611357..c0e9214 100644
--- a/ufund-api/src/main/java/com/ufund/api/ufundapi/model/Need.java
+++ b/ufund-api/src/main/java/com/ufund/api/ufundapi/model/Need.java
@@ -14,7 +14,7 @@ public class Need {
@JsonProperty("filterAttributes") private String[] filterAttributes;
@JsonProperty("type") final private GoalType type;
@JsonProperty("maxGoal") private double maxGoal;
- @JsonProperty("Current") private double current;
+ @JsonProperty("current") private double current;
/**
* Create a new need
@@ -32,6 +32,19 @@ public class Need {
}
/**
+ * Create a new need
+ *
+ * @param name The name of the need
+ * @param maxGoal The maximum goal for this need
+ * @param type The type of need (monetary, physical)
+ */
+ public Need(String name, GoalType type, double maxGoal) {
+ this.name = name;
+ this.type = type;
+ this.maxGoal = maxGoal;
+ }
+
+ /**
* Create a deep copy of another need
*
* @param other The need to copy from
diff --git a/ufund-api/src/main/java/com/ufund/api/ufundapi/model/User.java b/ufund-api/src/main/java/com/ufund/api/ufundapi/model/User.java
index 59f4c46..f08f9f0 100644
--- a/ufund-api/src/main/java/com/ufund/api/ufundapi/model/User.java
+++ b/ufund-api/src/main/java/com/ufund/api/ufundapi/model/User.java
@@ -7,46 +7,40 @@ import com.fasterxml.jackson.annotation.JsonProperty;
public class User {
- @JsonProperty("name")
- private final String name;
- @JsonProperty("passwordHash")
- private int passwordHash;
- @JsonProperty("basket")
- private final List<Need> basket;
-
- /**
- * Create a new user
- *
- * @param name The name of the user
- */
- public User(String name) {
- this.name = name;
- basket = new ArrayList<>();
+ public enum UserType {
+ HELPER,
+ MANAGER
}
+ @JsonProperty("username") private final String username;
+ @JsonProperty("passwordHash") private int passwordHash;
+ @JsonProperty("basket") private final List<Integer> basket;
+ @JsonProperty("type") private final UserType type;
+
/**
* Create a new user
*
- * @param name The name of the user
+ * @param username The name of the user
* @param basket A basket to copy from
*/
- public User(@JsonProperty("name") String name, @JsonProperty("basket") List<Need> basket) {
- this.name = name;
+ public User(@JsonProperty("username") String username, @JsonProperty("passwordHash") int passwordHash, @JsonProperty("basket") List<Integer> basket, @JsonProperty("type") UserType userType) {
+ this.username = username;
this.basket = basket;
+ this.passwordHash = passwordHash;
+ this.type = userType;
}
- /**
- * Create a deep copy of another user
- *
- * @param other The user to copy from
- */
- public User(User other) {
- this.name = other.name;
- this.basket = other.basket;
+ public static User create(String username, String password) {
+ return new User(
+ username,
+ password.hashCode(),
+ new ArrayList<>(),
+ UserType.HELPER
+ );
}
- public String getName() {
- return name;
+ public String getUsername() {
+ return username;
}
public boolean verifyPassword(String password) {
@@ -54,15 +48,23 @@ public class User {
}
public void addToBasket(Need need) {
- basket.add(need);
+ basket.add(need.getId());
}
- public Need[] getBasketNeeds() {
- return basket.toArray(Need[]::new);
+ public Integer[] getBasketNeeds() {
+ return basket.toArray(Integer[]::new);
}
public void removeBasketNeed(Need need) {
- basket.remove(need);
+ basket.remove(need.getId());
+ }
+
+ public User withoutPasswordHash() {
+ return new User(this.username, 0, this.basket, this.type);
+ }
+
+ public UserType getType() {
+ return type;
}
}
diff --git a/ufund-api/src/main/java/com/ufund/api/ufundapi/model/UserAuth.java b/ufund-api/src/main/java/com/ufund/api/ufundapi/model/UserAuth.java
new file mode 100644
index 0000000..1c11a28
--- /dev/null
+++ b/ufund-api/src/main/java/com/ufund/api/ufundapi/model/UserAuth.java
@@ -0,0 +1,43 @@
+package com.ufund.api.ufundapi.model;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+import java.time.LocalDateTime;
+import java.util.UUID;
+
+public class UserAuth {
+ @JsonProperty("key") String key;
+ @JsonProperty("username") String username;
+ @JsonProperty("expiration") LocalDateTime expiration;
+
+ public UserAuth(@JsonProperty("key") String key, @JsonProperty("username") String username, @JsonProperty("expiration") LocalDateTime expiration) {
+ this.key = key;
+ this.expiration = expiration;
+ this.username = username;
+ }
+
+ /**
+ * Generate a new user authentication profile
+ * @param username the username the key will belong to
+ * @return The new user authentication profile
+ */
+ public static UserAuth generate(String username) {
+ return new UserAuth(
+ UUID.randomUUID().toString(),
+ username,
+ LocalDateTime.now().plusDays(30)
+ );
+ }
+
+ public String getKey() {
+ return key;
+ }
+
+ public String getUsername() {
+ return username;
+ }
+
+ public LocalDateTime getExpiration() {
+ return expiration;
+ }
+}
diff --git a/ufund-api/src/main/java/com/ufund/api/ufundapi/persistence/CupboardDAO.java b/ufund-api/src/main/java/com/ufund/api/ufundapi/persistence/CupboardDAO.java
index 1435410..c8285a0 100644
--- a/ufund-api/src/main/java/com/ufund/api/ufundapi/persistence/CupboardDAO.java
+++ b/ufund-api/src/main/java/com/ufund/api/ufundapi/persistence/CupboardDAO.java
@@ -1,9 +1,9 @@
package com.ufund.api.ufundapi.persistence;
-import com.ufund.api.ufundapi.model.Need;
-
import java.io.IOException;
+import com.ufund.api.ufundapi.model.Need;
+
/**
* Defines the interface for Need object persistence
*
@@ -14,23 +14,10 @@ public interface CupboardDAO {
* Retrieves all {@linkplain Need needs}
*
* @return An array of {@link Need need} objects, may be empty
- *
- * @throws IOException if an issue with underlying storage
*/
Need[] getNeeds() throws IOException;
/**
- * Finds all {@linkplain Need needs} whose name contains the given text
- *
- * @param targetName The text to match against
- *
- * @return An array of {@link Need needs} whose names contains the given text, may be empty
- *
- * @throws IOException if an issue with underlying storage
- */
- Need[] findNeeds(String targetName) throws IOException;
-
- /**
* Retrieves a {@linkplain Need need} with the given name
*
* @param id The ID of the {@link Need need} to get
@@ -38,8 +25,6 @@ public interface CupboardDAO {
* @return a {@link Need need} object with the matching name
* <br>
* null if no {@link Need need} with a matching name is found
- *
- * @throws IOException if an issue with underlying storage
*/
Need getNeed(int id) throws IOException;
@@ -54,7 +39,7 @@ public interface CupboardDAO {
*
* @throws IOException if an issue with underlying storage
*/
- Need createNeed(Need need) throws IOException;
+ Need addNeed(Need need) throws IOException;
/**
* Updates and saves a {@linkplain Need need}
diff --git a/ufund-api/src/main/java/com/ufund/api/ufundapi/persistence/CupboardFileDao.java b/ufund-api/src/main/java/com/ufund/api/ufundapi/persistence/CupboardFileDAO.java
index 81ee7c0..521acae 100644
--- a/ufund-api/src/main/java/com/ufund/api/ufundapi/persistence/CupboardFileDao.java
+++ b/ufund-api/src/main/java/com/ufund/api/ufundapi/persistence/CupboardFileDAO.java
@@ -11,18 +11,18 @@ import java.util.Map;
import java.util.TreeMap;
@Component
-public class CupboardFileDao implements CupboardDAO {
+public class CupboardFileDAO implements CupboardDAO {
private final Map<Integer, Need> needs; // cache
private final ObjectMapper objectMapper;
private static int nextId;
private final String filename;
- public CupboardFileDao(@Value("${cupboard.file}") String filename, ObjectMapper objectMapper) throws IOException {
+ public CupboardFileDAO(@Value("${cupboard.file}") String filename, ObjectMapper objectMapper) throws IOException {
this.filename = filename;
this.objectMapper = objectMapper;
needs = new TreeMap<>();
- load(); // load the heroes from the file
+ load();
}
private synchronized static int nextId() {
@@ -61,18 +61,6 @@ public class CupboardFileDao implements CupboardDAO {
}
/**
- * Returns an array of needs filtered by a search
- *
- * @param search The search substring
- * @return The requested array
- */
- private Need[] getNeedsArray(String search) {
- return needs.values().stream()
- .filter(i -> i.getName().toLowerCase().contains(search.toLowerCase()))
- .toArray(Need[]::new);
- }
-
- /**
* Saves the needs to json
*
* @return True if the save was successful, false otherwise
@@ -93,13 +81,6 @@ public class CupboardFileDao implements CupboardDAO {
}
@Override
- public Need[] findNeeds(String targetName) {
- synchronized (needs) {
- return getNeedsArray(targetName);
- }
- }
-
- @Override
public Need getNeed(int id) {
synchronized (needs) {
return needs.getOrDefault(id, null);
@@ -107,7 +88,7 @@ public class CupboardFileDao implements CupboardDAO {
}
@Override
- public Need createNeed(Need need) throws IOException {
+ public Need addNeed(Need need) throws IOException {
synchronized (needs) {
Need newNeed = new Need(need);
newNeed.setID(nextId());
diff --git a/ufund-api/src/main/java/com/ufund/api/ufundapi/persistence/UserAuthDAO.java b/ufund-api/src/main/java/com/ufund/api/ufundapi/persistence/UserAuthDAO.java
new file mode 100644
index 0000000..355aae4
--- /dev/null
+++ b/ufund-api/src/main/java/com/ufund/api/ufundapi/persistence/UserAuthDAO.java
@@ -0,0 +1,32 @@
+package com.ufund.api.ufundapi.persistence;
+
+import com.ufund.api.ufundapi.model.UserAuth;
+
+import java.io.IOException;
+
+public interface UserAuthDAO {
+
+ /**
+ * Get a user authentication profile
+ *
+ * @param key The auth key
+ * @return The authentication profile or null if there was none
+ */
+ UserAuth getUserAuth(String key) throws IOException;
+
+ /**
+ * Add a user authentication profile
+ *
+ * @param userAuth The user auth profile to add
+ * @throws IOException Thrown on any file writing error
+ */
+ void addUserAuth(UserAuth userAuth) throws IOException;
+
+ /**
+ * Remove a user authentication profile
+ *
+ * @param key The key of the user auth profile to remove
+ * @throws IOException Thrown on any file writing error
+ */
+ void removeUserAuth(String key) throws IOException;
+}
diff --git a/ufund-api/src/main/java/com/ufund/api/ufundapi/persistence/UserAuthFIleDAO.java b/ufund-api/src/main/java/com/ufund/api/ufundapi/persistence/UserAuthFIleDAO.java
new file mode 100644
index 0000000..1fc1e92
--- /dev/null
+++ b/ufund-api/src/main/java/com/ufund/api/ufundapi/persistence/UserAuthFIleDAO.java
@@ -0,0 +1,73 @@
+package com.ufund.api.ufundapi.persistence;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.ufund.api.ufundapi.model.UserAuth;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.stereotype.Component;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.Map;
+
+@Component
+public class UserAuthFIleDAO implements UserAuthDAO {
+
+ private final Map<String, UserAuth> userAuthMap;
+ private final ObjectMapper objectMapper;
+ private final String filename;
+
+ public UserAuthFIleDAO(ObjectMapper objectMapper, @Value("${authKeys.file}") String filename) throws IOException {
+ this.userAuthMap = new HashMap<>();
+ this.objectMapper = objectMapper;
+ this.filename = filename;
+ load();
+ }
+
+ /**
+ * Loads the data from the file into the map
+ *
+ * @throws IOException Thrown if there was an issue reading the file
+ */
+ private void load() throws IOException {
+ userAuthMap.clear();
+
+ UserAuth[] userAuthKeysArray = objectMapper.readValue(new File(filename), UserAuth[].class);
+
+ for (UserAuth userAuth : userAuthKeysArray) {
+ userAuthMap.put(userAuth.getKey(), userAuth);
+ }
+ }
+
+ /**
+ * Saves the data from the map into the json file
+ *
+ * @throws IOException Thrown on any problem writing the file
+ */
+ private void save() throws IOException {
+ objectMapper.writeValue(new File(filename), userAuthMap.values());
+ }
+
+ @Override
+ public UserAuth getUserAuth(String key) {
+ synchronized (userAuthMap) {
+ return userAuthMap.get(key);
+ }
+ }
+
+ @Override
+ public void addUserAuth(UserAuth userAuth) throws IOException {
+ synchronized (userAuthMap) {
+ userAuthMap.put(userAuth.getKey(), userAuth);
+ save();
+ }
+ }
+
+ @Override
+ public void removeUserAuth(String key) throws IOException {
+ synchronized (userAuthMap) {
+ userAuthMap.remove(key);
+ save();
+ }
+ }
+}
diff --git a/ufund-api/src/main/java/com/ufund/api/ufundapi/persistence/UserDAO.java b/ufund-api/src/main/java/com/ufund/api/ufundapi/persistence/UserDAO.java
index d456abc..29d46cf 100644
--- a/ufund-api/src/main/java/com/ufund/api/ufundapi/persistence/UserDAO.java
+++ b/ufund-api/src/main/java/com/ufund/api/ufundapi/persistence/UserDAO.java
@@ -21,17 +21,17 @@ public interface UserDAO {
User[] getUsers() throws IOException;
/**
- * Retrieves a {@linkplain User user} with the given name
+ * Retrieves a {@linkplain User user} with the given username
*
- * @param id The ID of the {@link User user} to get
+ * @param username The ID of the {@link User user} to get
*
- * @return a {@link User user} object with the matching name
+ * @return a {@link User user} object with the matching username
* <br>
- * null if no {@link User user} with a matching name is found
+ * null if no {@link User user} with a matching username is found
*
* @throws IOException if an issue with underlying storage
*/
- User getUser(String name) throws IOException;
+ User getUser(String username) throws IOException;
/**
* Creates and saves a {@linkplain User user}
@@ -44,25 +44,24 @@ public interface UserDAO {
*
* @throws IOException if an issue with underlying storage
*/
- User createUser(User user) throws IOException;
+ User addUser(User user) throws IOException;
/**
* Updates and saves a {@linkplain User user}
*
- * @param newUser {@link User user} object to be updated and saved
- * @param name {@link String name} name of object to be updated
+ * @param user {@link User user} object to be updated and saved
*
* @return updated {@link User user} if successful, null if
* {@link User user} could not be found
*
* @throws IOException if underlying storage cannot be accessed
*/
- User updateUser(User newUser, String name) throws IOException;
+ User updateUser(User user) throws IOException;
/**
* Deletes a {@linkplain User user} with the given id
*
- * @param id The id of the {@link User user}
+ * @param username The id of the {@link User user}
*
* @return true if the {@link User user} was deleted
* <br>
@@ -70,5 +69,5 @@ public interface UserDAO {
*
* @throws IOException if underlying storage cannot be accessed
*/
- boolean deleteUser(String name) throws IOException;
+ boolean deleteUser(String username) throws IOException;
}
diff --git a/ufund-api/src/main/java/com/ufund/api/ufundapi/persistence/UserFileDAO.java b/ufund-api/src/main/java/com/ufund/api/ufundapi/persistence/UserFileDAO.java
index 18eec18..f17f8f2 100644
--- a/ufund-api/src/main/java/com/ufund/api/ufundapi/persistence/UserFileDAO.java
+++ b/ufund-api/src/main/java/com/ufund/api/ufundapi/persistence/UserFileDAO.java
@@ -2,8 +2,8 @@ package com.ufund.api.ufundapi.persistence;
import java.io.File;
import java.io.IOException;
+import java.util.HashMap;
import java.util.Map;
-import java.util.TreeMap;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
@@ -21,7 +21,7 @@ public class UserFileDAO implements UserDAO {
public UserFileDAO(@Value("${users.file}") String filename, ObjectMapper objectMapper) throws IOException {
this.filename = filename;
this.objectMapper = objectMapper;
- users = new TreeMap<>();
+ users = new HashMap<>();
load(); // load the users from the file
}
@@ -36,7 +36,7 @@ public class UserFileDAO implements UserDAO {
User[] usersArray = objectMapper.readValue(new File(filename), User[].class);
for (User user : usersArray) {
- users.put(user.getName(), user);
+ users.put(user.getUsername(), user);
}
}
@@ -47,100 +47,54 @@ public class UserFileDAO implements UserDAO {
* @throws IOException If there was an IO issue saving the file
*/
private boolean save() throws IOException {
- User[] userArray = getUserArray();
-
- objectMapper.writeValue(new File(filename), userArray);
+ objectMapper.writeValue(new File(filename), users.values());
return true;
}
- /**
- * Return an array of the needs
- *
- * @return An array of all the needs
- */
- private User[] getUserArray() {
- return users.values().toArray(User[]::new);
- }
-
@Override
- public User[] getUsers() throws IOException {
+ public User[] getUsers() {
synchronized (users) {
- return getUserArray();
+ return users.values().toArray(User[]::new);
}
}
- /**
- * Return the user with the String name name or null otherwise
- *
- * @param name Name of desired user
- *
- * @return Desired user, null otherwise
- * @throws IOException If there was an IO issue saving the file
- */
@Override
- public User getUser(String name) throws IOException {
+ public User getUser(String username) {
synchronized (users) {
- return users.getOrDefault(name, null);
+ return users.getOrDefault(username, null);
}
}
- /**
- * Create a User user
- *
- * @param user User to create
- *
- * @return Desired created user
- * @throws IOException If there was an IO issue saving the file
- */
@Override
- public User createUser(User user) throws IOException {
+ public User addUser(User user) throws IOException {
synchronized (users) {
- if (getUser(user.getName()) == null) {
- User newUser = new User(user);
- users.put(newUser.getName(), newUser);
- save();
- return newUser;
- } else {
- return null;
+ var res = users.putIfAbsent(user.getUsername(), user);
+ save();
+ if (res == null) {
+ return user;
}
+ return res;
}
}
- /**
- * Update a user that matches the supplied name
- *
- * @param name The name of the user
- * @param newUser New user data
- *
- * @return Desired user, null otherwise
- * @throws IOException If there was an IO issue saving the file
- */
@Override
- public User updateUser(User newUser, String name) throws IOException {
+ public User updateUser(User user) throws IOException {
synchronized (users) {
- if (users.containsKey(name)) {
- users.put(name, newUser);
+ if (users.containsKey(user.getUsername())) {
+ users.put(user.getUsername(), user);
save();
- return newUser;
+ return user;
} else {
return null;
}
}
}
- /**
- * Delete a user matching the name
- *
- * @param name The name of the user
- *
- * @return True if deleted, false otherwise
- * @throws IOException If there was an IO issue saving the file
- */
@Override
- public boolean deleteUser(String name) throws IOException {
+ public boolean deleteUser(String username) throws IOException {
synchronized (users) {
- if (users.containsKey(name)) {
- users.remove(name);
+ if (users.containsKey(username)) {
+ users.remove(username);
return save();
} else {
return false;
diff --git a/ufund-api/src/main/java/com/ufund/api/ufundapi/service/AuthService.java b/ufund-api/src/main/java/com/ufund/api/ufundapi/service/AuthService.java
new file mode 100644
index 0000000..87a16a6
--- /dev/null
+++ b/ufund-api/src/main/java/com/ufund/api/ufundapi/service/AuthService.java
@@ -0,0 +1,70 @@
+package com.ufund.api.ufundapi.service;
+
+import com.ufund.api.ufundapi.model.User;
+import com.ufund.api.ufundapi.model.UserAuth;
+import com.ufund.api.ufundapi.persistence.UserAuthDAO;
+import org.springframework.stereotype.Component;
+
+import java.io.IOException;
+
+@Component
+public class AuthService {
+
+ private final UserAuthDAO userAuthDAO;
+ private final UserService userService;
+
+ public AuthService(UserAuthDAO userAuthDAO, UserService userService) {
+ this.userAuthDAO = userAuthDAO;
+ this.userService = userService;
+ }
+
+ /**
+ * Check if the provided key has access to the provided user.
+ *
+ * @param targetUsername The targetUsername of the user trying to be accessed.
+ * @param key The api key obtained by the client from logging in.
+ * @throws IllegalAccessException Thrown if access was denied to the user.
+ */
+ public void authenticate(String targetUsername, String key) throws IllegalAccessException, IOException {
+ var userAuth = userAuthDAO.getUserAuth(key);
+ if (userAuth == null) {
+ throw new IllegalAccessException("Unauthenticated");
+ }
+//
+// var username = userAuth.getUsername();
+// var userType = userService.getUser(username).getType();
+// if (!username.equals(targetUsername) && userType != User.UserType.MANAGER) {
+// throw new IllegalAccessException("Unauthorized");
+// }
+ }
+
+ /**
+ * Attempt to log in with the provided credentials
+ *
+ * @param username The username of the user
+ * @param password The password of the user
+ * @return An API key if the authentication was successful.
+ * @throws IllegalAccessException Thrown if the username or password was incorrect
+ * @throws IOException If there was an issue saving the authentication
+ */
+ public String login(String username, String password) throws IllegalAccessException, IOException {
+ var usr = userService.getUser(username);
+ if (usr == null || !usr.verifyPassword(password)) {
+ throw new IllegalAccessException("Unauthorized");
+ }
+ var userAuth = UserAuth.generate(username);
+ userAuthDAO.addUserAuth(userAuth);
+ return userAuth.getKey();
+ }
+
+ /**
+ * Logs out the current user
+ *
+ * @param key The API key to of the client
+ * @throws IOException Thrown if there was an error saving the authentication
+ */
+ public void logout(String key) throws IOException {
+ userAuthDAO.removeUserAuth(key);
+ }
+
+}
diff --git a/ufund-api/src/main/java/com/ufund/api/ufundapi/service/CupboardService.java b/ufund-api/src/main/java/com/ufund/api/ufundapi/service/CupboardService.java
new file mode 100644
index 0000000..2398745
--- /dev/null
+++ b/ufund-api/src/main/java/com/ufund/api/ufundapi/service/CupboardService.java
@@ -0,0 +1,109 @@
+package com.ufund.api.ufundapi.service;
+
+import java.io.IOException;
+import java.util.Arrays;
+
+import org.springframework.stereotype.Component;
+
+import com.ufund.api.ufundapi.DuplicateKeyException;
+import com.ufund.api.ufundapi.model.Need;
+import com.ufund.api.ufundapi.persistence.CupboardDAO;
+
+@Component
+public class CupboardService {
+
+ private final CupboardDAO cupboardDAO;
+
+ public CupboardService(CupboardDAO cupboardDAO) {
+ this.cupboardDAO = cupboardDAO;
+ }
+
+ /**
+ * Creates a new Need
+ *
+ * @param name The name of the need to create
+ * @param maxGoal The max goal of the new need
+ * @param goalType The goal type of the new need
+ * @return The need that was created
+ * @throws IOException Thrown if there was any issue saving the data
+ * @throws DuplicateKeyException If there already exists a need with the same name
+ */
+ public Need createNeed(String name, double maxGoal, Need.GoalType goalType) throws IOException, DuplicateKeyException {
+
+ if (maxGoal <= 0) {
+ throw new IllegalArgumentException("Max Goal must be greater than zero");
+ }
+
+ for (Need searchNeed : cupboardDAO.getNeeds()) {
+ if (searchNeed.getName().equalsIgnoreCase(name)) {
+ throw new DuplicateKeyException("Duplicate names are not allowed");
+ }
+ }
+
+ Need need = new Need(name, goalType, maxGoal);
+ return cupboardDAO.addNeed(need);
+
+ }
+
+ /**
+ * Get all the needs in the cupboard
+ *
+ * @return An array containing all needs
+ * @throws IOException Thrown if there was any issue saving the data
+ */
+ public Need[] getNeeds() throws IOException {
+ return cupboardDAO.getNeeds();
+ }
+
+ /**
+ * Returns an array of needs filtered by a search
+ *
+ * @param search The search substring
+ * @return The requested array
+ * @throws IOException Thrown if there was any issue saving the data
+ */
+ public Need[] searchNeeds(String search) throws IOException {
+ return Arrays.stream(cupboardDAO.getNeeds())
+ .filter(i -> i.getName().toLowerCase().contains(search.toLowerCase()))
+ .toArray(Need[]::new);
+ }
+
+ /**
+ * Gets a need with the specified ID
+ *
+ * @param id the ID of the need
+ * @return The resulting Need or null if the need was not found
+ */
+ public Need getNeed(int id) throws IOException {
+ return cupboardDAO.getNeed(id);
+ }
+
+ /**
+ * Updates a need
+ *
+ * @param id The ID of the need to update
+ * @param need The need object to set (note: the ID is ignored)
+ * @return The updated need object
+ * @throws IOException Thrown if there was an issue saving the changes
+ */
+ public Need updateNeed(Need need, int id) throws IOException {
+ if (need.getId() != id) {
+ throw new IllegalArgumentException("ID in URL and body must match");
+ }
+ if (need.getMaxGoal() <= 0) {
+ throw new IllegalArgumentException("Goal must be greater than 0");
+ }
+ return cupboardDAO.updateNeed(need);
+ }
+
+ /**
+ * Delete a need from the cupboard
+ *
+ * @param id the ID of the need
+ * @return True if the need was deleted
+ * @throws IOException Thrown on any problem removing the need
+ */
+ public boolean deleteNeed(int id) throws IOException {
+ return cupboardDAO.deleteNeed(id);
+ }
+}
diff --git a/ufund-api/src/main/java/com/ufund/api/ufundapi/service/UserService.java b/ufund-api/src/main/java/com/ufund/api/ufundapi/service/UserService.java
new file mode 100644
index 0000000..935ee72
--- /dev/null
+++ b/ufund-api/src/main/java/com/ufund/api/ufundapi/service/UserService.java
@@ -0,0 +1,72 @@
+package com.ufund.api.ufundapi.service;
+
+import java.io.IOException;
+
+import com.ufund.api.ufundapi.DuplicateKeyException;
+import com.ufund.api.ufundapi.model.User;
+import com.ufund.api.ufundapi.persistence.UserDAO;
+import org.springframework.stereotype.Component;
+
+@Component
+public class UserService {
+
+ private final UserDAO userDAO;
+
+ public UserService(UserDAO userDao) {
+ this.userDAO = userDao;
+ }
+
+ /**
+ * Creates a new user
+ *
+ * @param username The username of the user
+ * @param password The password of the user
+ * @return The created user object
+ * @throws IOException Thrown on any problem saving the file
+ */
+ public User createUser(String username, String password) throws IOException, DuplicateKeyException {
+ if (userDAO.getUser(username) != null) {
+ throw new DuplicateKeyException("A user with this name already exists");
+ }
+ User user = User.create(username, password);
+ return userDAO.addUser(user);
+ }
+
+ /**
+ * Gets a user with the given username
+ *
+ * @param username The username of the user
+ * @return The user object with that username
+ * @throws IOException If there was any problem saving the file
+ */
+ public User getUser(String username) throws IOException {
+ return userDAO.getUser(username);
+ }
+
+ /**
+ * Updates a user
+ *
+ * @param user The ID of the user to update
+ * @param username The user object to set (note: the ID is ignored)
+ * @return The updated user object
+ * @throws IOException Thrown if there was any issue saving the data
+ */
+ public User updateUser(User user, String username) throws IOException {
+ if (!user.getUsername().equals(username)) {
+ throw new IllegalArgumentException("ID in URL and body must match");
+ }
+ return userDAO.updateUser(user);
+ }
+
+ /**
+ * Deletes a user
+ *
+ * @param username The username of the user to delete
+ * @return True if the user was deleted
+ * @throws IOException Thrown if there was any issue saving the data
+ */
+ public boolean deleteUser(String username) throws IOException {
+ return userDAO.deleteUser(username);
+ }
+
+}
diff --git a/ufund-api/src/main/resources/application.properties b/ufund-api/src/main/resources/application.properties
index a866f98..c742063 100644
--- a/ufund-api/src/main/resources/application.properties
+++ b/ufund-api/src/main/resources/application.properties
@@ -1,8 +1,8 @@
-# rename to application.properties
server.error.include-message=always
cupboard.file=data/cupboard.json
users.file=data/users.json
+authKeys.file=data/userAuths.json
spring.jackson.mapper.auto-detect-getters=false
spring.jackson.mapper.auto-detect-setters=false
diff --git a/ufund-api/src/test/java/com/ufund/api/ufundapi/controller/AuthControllerTest.java b/ufund-api/src/test/java/com/ufund/api/ufundapi/controller/AuthControllerTest.java
new file mode 100644
index 0000000..3d4637d
--- /dev/null
+++ b/ufund-api/src/test/java/com/ufund/api/ufundapi/controller/AuthControllerTest.java
@@ -0,0 +1,104 @@
+package com.ufund.api.ufundapi.controller;
+
+import java.io.IOException;
+import java.util.Map;
+import static java.util.Map.entry;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import static org.mockito.ArgumentMatchers.any;
+import org.mockito.Mockito;
+import static org.mockito.Mockito.doThrow;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.ResponseEntity;
+import org.springframework.web.bind.annotation.RequestMapping;
+
+import com.ufund.api.ufundapi.service.AuthService;
+
+@RequestMapping("auth")
+public class AuthControllerTest {
+
+ private AuthController authController;
+ private AuthService mockAuthService;
+ private Map<String, String> authMap;
+
+ @BeforeEach
+ private void setupAuthController() {
+ mockAuthService = mock(AuthService.class);
+ authController = new AuthController(mockAuthService);
+
+ authMap = Map.ofEntries(
+ entry("Bob", "123")
+ );
+ }
+
+ @Test
+ public void testLogin() throws IllegalAccessException, IOException {
+ // Setup
+ String key = "123";
+
+ // Mock
+ when(mockAuthService.login(any(), any())).thenReturn(key);
+
+ // Invoke
+ ResponseEntity<String> response = authController.login(authMap);
+
+ // Analyze
+ assertEquals(HttpStatus.OK, response.getStatusCode());
+ assertEquals(key, response.getBody());
+ }
+
+ @Test
+ public void testLoginUnauthorized() throws IllegalAccessException, IOException {
+ // Mock
+ when(mockAuthService.login(any(), any())).thenThrow(IllegalAccessException.class);
+
+ // Invoke
+ ResponseEntity<String> response = authController.login(authMap);
+
+ // Analyze
+ assertEquals(HttpStatus.UNAUTHORIZED, response.getStatusCode());
+ }
+
+ @Test
+ public void testLoginIOException() throws IllegalAccessException, IOException {
+ // Mock
+ when(mockAuthService.login(any(), any())).thenThrow(IOException.class);
+
+ // Invoke
+ ResponseEntity<String> response = authController.login(authMap);
+
+ // Analyze
+ assertEquals(HttpStatus.INTERNAL_SERVER_ERROR, response.getStatusCode());
+ }
+
+ @Test
+ public void testLogout() throws IllegalAccessException, IOException {
+ // Setup
+ String key = "123";
+
+ // Invoke
+ ResponseEntity<Object> response = authController.logout(key);
+
+ // Analyze
+ assertEquals(HttpStatus.OK, response.getStatusCode());
+ }
+
+ @Test
+ public void testLogoutIOException() throws IllegalAccessException, IOException {
+ // Setup
+ String key = "123";
+
+ // Mock
+ doThrow(new IOException()).when(mockAuthService).logout(key);
+
+ // Invoke
+ ResponseEntity<Object> response = authController.logout(key);
+
+ // Analyze
+ assertEquals(HttpStatus.INTERNAL_SERVER_ERROR, response.getStatusCode());
+ }
+}
diff --git a/ufund-api/src/test/java/com/ufund/api/ufundapi/controller/CupboardControllerTest.java b/ufund-api/src/test/java/com/ufund/api/ufundapi/controller/CupboardControllerTest.java
index 839c518..94f93cb 100644
--- a/ufund-api/src/test/java/com/ufund/api/ufundapi/controller/CupboardControllerTest.java
+++ b/ufund-api/src/test/java/com/ufund/api/ufundapi/controller/CupboardControllerTest.java
@@ -1,6 +1,8 @@
package com.ufund.api.ufundapi.controller;
import java.io.IOException;
+import java.util.Map;
+import static java.util.Map.entry;
import static org.junit.jupiter.api.Assertions.assertArrayEquals;
import static org.junit.jupiter.api.Assertions.assertEquals;
@@ -11,54 +13,74 @@ import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import org.springframework.http.HttpStatus;
+import com.ufund.api.ufundapi.DuplicateKeyException;
import com.ufund.api.ufundapi.model.Need;
-import com.ufund.api.ufundapi.persistence.CupboardFileDao;
+import com.ufund.api.ufundapi.model.Need.GoalType;
+import com.ufund.api.ufundapi.service.CupboardService;
public class CupboardControllerTest {
private CupboardController cupboardController;
- private CupboardFileDao mockCupboardDAO;
+ private CupboardService mockCupboardService;
@BeforeEach
public void setupCupboardDAO() {
- mockCupboardDAO = mock(CupboardFileDao.class);
- cupboardController = new CupboardController(mockCupboardDAO);
+ mockCupboardService = mock(CupboardService.class);
+ cupboardController = new CupboardController(mockCupboardService);
}
@Test
- public void createNeed() throws IOException {
- var need = new Need("Name", 1, 100, Need.GoalType.MONETARY);
- when(mockCupboardDAO.createNeed(need)).thenReturn(need);
+ public void createNeed() throws IOException, DuplicateKeyException {
+ String name = "Test";
+ int maxGoal = 100;
+ GoalType type = Need.GoalType.MONETARY;
+ var need = new Need(name, type, maxGoal);
+ when(mockCupboardService.createNeed(name, maxGoal, type)).thenReturn(need);
+
- var res = cupboardController.createNeed(need);
+ Map<String, Object> needMap = Map.ofEntries(
+ entry("name", "Test"),
+ entry("maxGoal", 100),
+ entry("type", "MONETARY")
+ );
+
+ var res = cupboardController.createNeed(needMap);
assertEquals(HttpStatus.OK, res.getStatusCode());
assertEquals(need, res.getBody());
}
@Test
- public void createNeedBadMaxGoal() throws IOException {
- var need = new Need("Name", 1, -100, Need.GoalType.MONETARY);
- when(mockCupboardDAO.createNeed(need)).thenReturn(need);
+ public void createNeedBadMaxGoal() throws IOException, DuplicateKeyException {
+ when(mockCupboardService.createNeed("Name", -100, Need.GoalType.MONETARY)).thenThrow(new IllegalArgumentException());
+
+ Map<String, Object> needMap = Map.ofEntries(
+ entry("name", "Name"),
+ entry("maxGoal", -100),
+ entry("type", "MONETARY"));
- var res = cupboardController.createNeed(need);
+ var res = cupboardController.createNeed(needMap);
- assertEquals(HttpStatus.BAD_REQUEST, res.getStatusCode());
+ assertEquals(HttpStatus.UNPROCESSABLE_ENTITY, res.getStatusCode());
}
@Test
- public void createNeedIOException() throws IOException {
- var need = new Need("Name", 1, 100, Need.GoalType.MONETARY);
- when(mockCupboardDAO.createNeed(need)).thenThrow(new IOException());
+ public void createNeedIOException() throws IOException, DuplicateKeyException {
+ when(mockCupboardService.createNeed("Name", 100, Need.GoalType.MONETARY)).thenThrow(new IOException());
+
+ Map<String, Object> needMap = Map.ofEntries(
+ entry("name", "Name"),
+ entry("maxGoal", 100),
+ entry("type", "MONETARY"));
- var res = cupboardController.createNeed(need);
+ var res = cupboardController.createNeed(needMap);
assertEquals(HttpStatus.INTERNAL_SERVER_ERROR, res.getStatusCode());
}
@Test
- public void getNeeds() {
+ public void getNeeds() throws IOException {
var need = new Need("Name", 1, 100, Need.GoalType.MONETARY);
- when(mockCupboardDAO.getNeeds()).thenReturn(new Need[]{need});
+ when(mockCupboardService.getNeeds()).thenReturn(new Need[]{need});
var res = cupboardController.getNeeds();
@@ -67,9 +89,8 @@ public class CupboardControllerTest {
}
@Test
- public void getNeedsIOException() {
- var need = new Need("Name", 1, 100, Need.GoalType.MONETARY);
- when(mockCupboardDAO.getNeeds()).thenThrow(new IOException());
+ public void getNeedsIOException() throws IOException {
+ when(mockCupboardService.getNeeds()).thenThrow(new IOException());
var res = cupboardController.getNeeds();
@@ -77,8 +98,8 @@ public class CupboardControllerTest {
}
@Test
- public void getNeedsEmpty() {
- when(mockCupboardDAO.getNeeds()).thenReturn(new Need[]{});
+ public void getNeedsEmpty() throws IOException {
+ when(mockCupboardService.getNeeds()).thenReturn(new Need[]{});
var res = cupboardController.getNeeds();
@@ -87,9 +108,9 @@ public class CupboardControllerTest {
}
@Test
- public void searchNeeds() {
+ public void searchNeeds() throws IOException {
var need = new Need("Name", 1, 100, Need.GoalType.MONETARY);
- when(mockCupboardDAO.findNeeds("Na")).thenReturn(new Need[]{need});
+ when(mockCupboardService.searchNeeds("Na")).thenReturn(new Need[]{need});
var res = cupboardController.searchNeeds("Na");
@@ -98,9 +119,8 @@ public class CupboardControllerTest {
}
@Test
- public void searchNeedsIOException() {
- var need = new Need("Name", 1, 100, Need.GoalType.MONETARY);
- when(mockCupboardDAO.findNeeds("Na")).thenThrow(new IOException());
+ public void searchNeedsIOException() throws IOException {
+ when(mockCupboardService.searchNeeds("Na")).thenThrow(new IOException());
var res = cupboardController.searchNeeds("Na");
@@ -108,8 +128,8 @@ public class CupboardControllerTest {
}
@Test
- public void searchNeedsEmpty() {
- when(mockCupboardDAO.findNeeds("Na")).thenReturn(new Need[]{});
+ public void searchNeedsEmpty() throws IOException {
+ when(mockCupboardService.searchNeeds("Na")).thenReturn(new Need[]{});
var res = cupboardController.searchNeeds("Na");
@@ -118,9 +138,9 @@ public class CupboardControllerTest {
}
@Test
- public void getNeed() {
+ public void getNeed() throws IOException {
var need = new Need("Name", 1, 100, Need.GoalType.MONETARY);
- when(mockCupboardDAO.getNeed(need.getId())).thenReturn(need);
+ when(mockCupboardService.getNeed(need.getId())).thenReturn(need);
var res = cupboardController.getNeed(need.getId());
@@ -129,9 +149,9 @@ public class CupboardControllerTest {
}
@Test
- public void getNeedIOException() {
+ public void getNeedIOException() throws IOException {
var need = new Need("Name", 1, 100, Need.GoalType.MONETARY);
- when(mockCupboardDAO.getNeed(need.getId())).thenThrow(new IOException());
+ when(mockCupboardService.getNeed(need.getId())).thenThrow(new IOException());
var res = cupboardController.getNeed(need.getId());
@@ -139,9 +159,9 @@ public class CupboardControllerTest {
}
@Test
- public void getNeedFail() {
+ public void getNeedFail() throws IOException {
var need = new Need("Name", 1, 100, Need.GoalType.MONETARY);
- when(mockCupboardDAO.getNeed(need.getId())).thenReturn(null);
+ when(mockCupboardService.getNeed(need.getId())).thenReturn(null);
var res = cupboardController.getNeed(need.getId());
@@ -152,9 +172,9 @@ public class CupboardControllerTest {
@Test
public void updateNeeds() throws IOException {
var need = new Need("Name", 1, 100, Need.GoalType.MONETARY);
- when(mockCupboardDAO.updateNeed(need)).thenReturn(need);
+ when(mockCupboardService.updateNeed(need, 1)).thenReturn(need);
- var res = cupboardController.updateNeed(need);
+ var res = cupboardController.updateNeed(need, 1);
assertEquals(HttpStatus.OK, res.getStatusCode());
assertEquals(need, res.getBody());
@@ -163,9 +183,9 @@ public class CupboardControllerTest {
@Test
public void updateNeedsIOException() throws IOException {
var need = new Need("Name", 1, 100, Need.GoalType.MONETARY);
- when(mockCupboardDAO.updateNeed(need)).thenThrow(new IOException());
+ when(mockCupboardService.updateNeed(need, 1)).thenThrow(new IOException());
- var res = cupboardController.updateNeed(need);
+ var res = cupboardController.updateNeed(need, 1);
assertEquals(HttpStatus.INTERNAL_SERVER_ERROR, res.getStatusCode());
}
@@ -173,8 +193,8 @@ public class CupboardControllerTest {
@Test
public void deleteNeed() throws IOException {
var need = new Need("Name", 1, 100, Need.GoalType.MONETARY);
- when(mockCupboardDAO.getNeed(1)).thenReturn(need);
- when(mockCupboardDAO.deleteNeed(1)).thenReturn(true);
+ when(mockCupboardService.getNeed(1)).thenReturn(need);
+ when(mockCupboardService.deleteNeed(1)).thenReturn(true);
var res = cupboardController.deleteNeed(1);
@@ -183,8 +203,8 @@ public class CupboardControllerTest {
@Test
public void deleteNeedFail() throws IOException {
- when(mockCupboardDAO.getNeed(1)).thenReturn(null);
- when(mockCupboardDAO.deleteNeed(1)).thenReturn(false);
+ when(mockCupboardService.getNeed(1)).thenReturn(null);
+ when(mockCupboardService.deleteNeed(1)).thenReturn(false);
var res = cupboardController.deleteNeed(1);
@@ -194,8 +214,8 @@ public class CupboardControllerTest {
@Test
public void deleteNeedIOException() throws IOException {
var need = new Need("Name", 1, 100, Need.GoalType.MONETARY);
- when(mockCupboardDAO.getNeed(1)).thenReturn(need);
- when(mockCupboardDAO.deleteNeed(1)).thenThrow(new IOException());
+ when(mockCupboardService.getNeed(1)).thenReturn(need);
+ when(mockCupboardService.deleteNeed(1)).thenThrow(new IOException());
var res = cupboardController.deleteNeed(1);
diff --git a/ufund-api/src/test/java/com/ufund/api/ufundapi/controller/UserControllerTest.java b/ufund-api/src/test/java/com/ufund/api/ufundapi/controller/UserControllerTest.java
index 681f47c..b6367ad 100644
--- a/ufund-api/src/test/java/com/ufund/api/ufundapi/controller/UserControllerTest.java
+++ b/ufund-api/src/test/java/com/ufund/api/ufundapi/controller/UserControllerTest.java
@@ -1,8 +1,12 @@
package com.ufund.api.ufundapi.controller;
import java.io.IOException;
+import java.security.InvalidParameterException;
+import java.util.Map;
+import static java.util.Map.entry;
import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Tag;
import org.junit.jupiter.api.Test;
@@ -12,82 +16,111 @@ import static org.mockito.Mockito.when;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
+import com.ufund.api.ufundapi.DuplicateKeyException;
import com.ufund.api.ufundapi.model.User;
-import com.ufund.api.ufundapi.persistence.UserFileDAO;
+import com.ufund.api.ufundapi.model.UserAuth;
+import com.ufund.api.ufundapi.service.AuthService;
+import com.ufund.api.ufundapi.service.UserService;
@Tag("Controller-tier")
public class UserControllerTest {
private UserController userController;
- private UserFileDAO mockUserDAO;
+ private AuthService mockAuthService;
+ private UserService mockUserService;
+ private Map<String, String> userMap;
@BeforeEach
public void setupUserController() {
- mockUserDAO = mock(UserFileDAO.class);
- userController = new UserController(mockUserDAO);
-
+ mockUserService = mock(UserService.class);
+ mockAuthService = mock(AuthService.class);
+ userController = new UserController(mockUserService, mockAuthService);
+ userMap = Map.ofEntries(
+ entry("username", "Test"),
+ entry("password", "Pass")
+ );
}
@Test
public void testGetUser() throws IOException { // getUser may throw IOException
// Setup
String username = "Test";
- User user = new User(username);
+ User user = User.create(username, "pass");
+ String key = UserAuth.generate(username).getKey( );
// When the same id is passed in, our mock User DAO will return the User object
- when(mockUserDAO.getUser(username)).thenReturn(user);
+ when(mockUserService.getUser(username)).thenReturn(user);
+
// Invoke
- ResponseEntity<User> response = userController.getUser(username);
+ ResponseEntity<User> response = userController.getUser(username, key);
// Analyze
assertEquals(HttpStatus.OK, response.getStatusCode());
- assertEquals(user, response.getBody());
+ assertNotNull(response.getBody());
+ assertEquals(user.getUsername(), response.getBody().getUsername());
}
@Test
public void testGetUserNotFound() throws Exception { // createUser may throw IOException
// Setup
String username = "Test";
- // When the same id is passed in, our mock User DAO will return null, simulating
+ String key = UserAuth.generate(username).getKey();
+ // When the same id is passed in, our mock User service will return null, simulating
// no User found
- when(mockUserDAO.getUser(username)).thenReturn(null);
+ when(mockUserService.getUser(username)).thenReturn(null);
+
// Invoke
- ResponseEntity<User> response = userController.getUser(username);
+ ResponseEntity<User> response = userController.getUser(username, key);
// Analyze
assertEquals(HttpStatus.NOT_FOUND, response.getStatusCode());
}
@Test
+ public void testGetUserUnauthorized() throws Exception { // createUser may throw IOException
+ // Setup
+ String username = "Test";
+ String key = UserAuth.generate(username).getKey();
+ // When getUser is called on the Mock User service, throw an IOException
+ // doThrow(new IllegalAccessException()).when(mockUserService).getUser(username);
+ doThrow(new IllegalAccessException()).when(mockAuthService).authenticate(username, key);
+
+ // Invoke
+ ResponseEntity<User> response = userController.getUser(username, key);
+
+ // Analyze
+ assertEquals(HttpStatus.UNAUTHORIZED, response.getStatusCode());
+ }
+
+ @Test
public void testGetUserHandleException() throws Exception { // createUser may throw IOException
// Setup
String username = "Test";
- // When getUser is called on the Mock User DAO, throw an IOException
- doThrow(new IOException()).when(mockUserDAO).getUser(username);
+ String key = UserAuth.generate(username).getKey();
+ // When getUser is called on the Mock User service, throw an IOException
+ doThrow(new IOException()).when(mockUserService).getUser(username);
// Invoke
- ResponseEntity<User> response = userController.getUser(username);
+ ResponseEntity<User> response = userController.getUser(username, key);
// Analyze
assertEquals(HttpStatus.INTERNAL_SERVER_ERROR, response.getStatusCode());
}
- /*****************************************************************
- * The following tests will fail until all userController methods
- * are implemented.
- ****************************************************************/
-
@Test
- public void testCreateUser() throws IOException { // createUser may throw IOException
+ public void testCreateUser() throws IOException, DuplicateKeyException { // createUser may throw IOException
// Setup
String username = "Test";
- User user = new User(username);
+ String password = "Pass";
+ User user = User.create(username, "pass");
// when createUser is called, return true simulating successful
// creation and save
- when(mockUserDAO.createUser(user)).thenReturn(user);
+ when(mockUserService.createUser(username, password)).thenReturn(user);
+
+
// Invoke
- ResponseEntity<User> response = userController.createUser(user);
+ ResponseEntity<User> response = userController.createUser(userMap);
// Analyze
assertEquals(HttpStatus.CREATED, response.getStatusCode());
@@ -95,32 +128,52 @@ public class UserControllerTest {
}
@Test
- public void testCreateUserFailed() throws IOException { // createUser may throw IOException
+ public void testCreateUserFailed() throws IOException, DuplicateKeyException { // createUser may throw IOException
// Setup
String username = "Test";
- User user = new User(username);
+ String password = "Pass";
// when createUser is called, return false simulating failed
// creation and save
- when(mockUserDAO.createUser(user)).thenReturn(null);
+ when(mockUserService.createUser(username, password)).thenReturn(null);
+
+
// Invoke
- ResponseEntity<User> response = userController.createUser(user);
+ ResponseEntity<User> response = userController.createUser(userMap);
// Analyze
assertEquals(HttpStatus.CONFLICT, response.getStatusCode());
}
@Test
- public void testCreateUserHandleException() throws IOException { // createUser may throw IOException
+ public void testCreateUserDuplicate() throws IOException, DuplicateKeyException { // createUser may throw IOException
// Setup
String username = "Test";
- User user = new User(username);
+ String password = "Pass";
+ // when createUser is called, return false simulating failed
+ // creation and save
+ when(mockUserService.createUser(username, password)).thenThrow(DuplicateKeyException.class);
- // When createUser is called on the Mock User DAO, throw an IOException
- doThrow(new IOException()).when(mockUserDAO).createUser(user);
+ // Invoke
+ ResponseEntity<User> response = userController.createUser(userMap);
+
+ // Analyze
+ assertEquals(HttpStatus.CONFLICT, response.getStatusCode());
+ }
+
+ @Test
+ public void testCreateUserHandleException() throws IOException, DuplicateKeyException { // createUser may throw IOException
+ // Setup
+ String username = "Test";
+ String password = "Pass";
+
+ // When createUser is called on the Mock User service, throw an IOException
+ doThrow(new IOException()).when(mockUserService).createUser(username, password);
+
+
// Invoke
- ResponseEntity<User> response = userController.createUser(user);
+ ResponseEntity<User> response = userController.createUser(userMap);
// Analyze
assertEquals(HttpStatus.INTERNAL_SERVER_ERROR, response.getStatusCode());
@@ -130,13 +183,14 @@ public class UserControllerTest {
public void testUpdateUser() throws IOException { // updateUser may throw IOException
// Setup
String username = "Test";
- User user = new User("Bob");
+ User user = User.create(username, "pass");
+ String key = UserAuth.generate(username).getKey();
// when updateUser is called, return true simulating successful
// update and save
- when(mockUserDAO.updateUser(user, username)).thenReturn(user);
+ when(mockUserService.updateUser(user, username)).thenReturn(user);
// Invoke
- ResponseEntity<User> response = userController.updateUser(user, username);
+ ResponseEntity<User> response = userController.updateUser(user, username, key);
// Analyze
assertEquals(HttpStatus.OK, response.getStatusCode());
@@ -147,42 +201,62 @@ public class UserControllerTest {
public void testUpdateUserFailed() throws IOException { // updateUser may throw IOException
// Setup
String username = "Test";
- User user = new User("Bob");
+ User user = User.create(username, "pass");
+ String key = UserAuth.generate(username).getKey();
// when updateUser is called, return true simulating successful
// update and save
- when(mockUserDAO.updateUser(user, username)).thenReturn(null);
+ when(mockUserService.updateUser(user, username)).thenReturn(null);
// Invoke
- ResponseEntity<User> response = userController.updateUser(user, username);
+ ResponseEntity<User> response = userController.updateUser(user, username, key);
// Analyze
assertEquals(HttpStatus.NOT_FOUND, response.getStatusCode());
}
@Test
- public void testUpdateUserHandleException() throws IOException { // updateUser may throw IOException
+ public void testUpdateUserInvalidParameter() throws IOException { // updateUser may throw IOException
// Setup
String username = "Test";
- User user = new User("Bob");
+ User user = User.create(username, "pass");
+ String key = UserAuth.generate(username).getKey();
// When updateUser is called on the Mock User DAO, throw an IOException
- doThrow(new IOException()).when(mockUserDAO).updateUser(user, username);
+ doThrow(new IOException()).when(mockUserService).updateUser(user, username);
// Invoke
- ResponseEntity<User> response = userController.updateUser(user, username);
+ ResponseEntity<User> response = userController.updateUser(user, username, key);
// Analyze
assertEquals(HttpStatus.INTERNAL_SERVER_ERROR, response.getStatusCode());
}
@Test
+ public void testUpdateUserUnauthorized() throws IOException, IllegalAccessException { // updateUser may throw IOException
+ // Setup
+ String username = "Test";
+ User user = User.create(username, "pass");
+ String key = UserAuth.generate(username).getKey();
+ // When updateUser is called on the Mock User service, throw a Invalid Parameter exception
+ // exception
+ doThrow(new IllegalAccessException()).when(mockAuthService).authenticate(username, key);
+
+ // Invoke
+ ResponseEntity<User> response = userController.updateUser(user, username, key);
+
+ // Analyze
+ assertEquals(HttpStatus.UNAUTHORIZED, response.getStatusCode());
+ }
+
+ @Test
public void testDeleteUser() throws IOException { // deleteUser may throw IOException
// Setup
String username = "Test";
+ String key = UserAuth.generate(username).getKey();
// when deleteUser is called return true, simulating successful deletion
- when(mockUserDAO.deleteUser(username)).thenReturn(true);
+ when(mockUserService.deleteUser(username)).thenReturn(true);
// Invoke
- ResponseEntity<User> response = userController.deleteUser(username);
+ ResponseEntity<Boolean> response = userController.deleteUser(username, key);
// Analyze
assertEquals(HttpStatus.OK, response.getStatusCode());
@@ -192,11 +266,12 @@ public class UserControllerTest {
public void testDeleteUserNotFound() throws IOException { // deleteUser may throw IOException
// Setup
String username = "Test";
+ String key = UserAuth.generate(username).getKey();
// when deleteUser is called return false, simulating failed deletion
- when(mockUserDAO.deleteUser(username)).thenReturn(false);
+ when(mockUserService.deleteUser(username)).thenReturn(false);
// Invoke
- ResponseEntity<User> response = userController.deleteUser(username);
+ ResponseEntity<Boolean> response = userController.deleteUser(username, key);
// Analyze
assertEquals(HttpStatus.NOT_FOUND, response.getStatusCode());
@@ -206,14 +281,30 @@ public class UserControllerTest {
public void testDeleteUserHandleException() throws IOException { // deleteUser may throw IOException
// Setup
String username = "Test";
- // When deleteUser is called on the Mock User DAO, throw an IOException
- doThrow(new IOException()).when(mockUserDAO).deleteUser(username);
+ String key = UserAuth.generate(username).getKey();
+ // When deleteUser is called on the Mock User service, throw an IOException
+ doThrow(new IOException()).when(mockUserService).deleteUser(username);
// Invoke
- ResponseEntity<User> response = userController.deleteUser(username);
+ ResponseEntity<Boolean> response = userController.deleteUser(username, key);
// Analyze
assertEquals(HttpStatus.INTERNAL_SERVER_ERROR, response.getStatusCode());
}
+ @Test
+ public void testDeleteUserUnauthorized() throws IOException, IllegalAccessException { // deleteUser may throw IOException
+ // Setup
+ String username = "Test";
+ String key = UserAuth.generate(username).getKey();
+ // When deleteUser is called on the Mock User service, throw an IOException
+ doThrow(new IllegalAccessException()).when(mockAuthService).authenticate(username, key);
+
+ // Invoke
+ ResponseEntity<Boolean> response = userController.deleteUser(username, key);
+
+ // Analyze
+ assertEquals(HttpStatus.UNAUTHORIZED, response.getStatusCode());
+ }
+
}
diff --git a/ufund-api/src/test/java/com/ufund/api/ufundapi/model/NeedTest.java b/ufund-api/src/test/java/com/ufund/api/ufundapi/model/NeedTest.java
index ffcd808..6b4ddfc 100644
--- a/ufund-api/src/test/java/com/ufund/api/ufundapi/model/NeedTest.java
+++ b/ufund-api/src/test/java/com/ufund/api/ufundapi/model/NeedTest.java
@@ -14,13 +14,10 @@ public class NeedTest {
public void createNeed() {
String name = "Jellyfish";
- int id = 0;
double maxGoal = 100.00;
GoalType type = GoalType.MONETARY;
- Need need = new Need(name, id, maxGoal, type);
-
+ Need need = new Need(name, type, maxGoal);
assertNotNull(need);
-
}
@Test
@@ -29,7 +26,7 @@ public class NeedTest {
int id = 0;
double maxGoal = 100.00;
GoalType type = GoalType.MONETARY;
- Need need = new Need(name, id, maxGoal, type);
+ Need need = new Need(name, type, maxGoal);
assertEquals(name, need.getName());
@@ -41,22 +38,21 @@ public class NeedTest {
@Test
public void testCurrentGoal() {
String name = "Jellyfish";
- int id = 0;
double maxGoal = 100.00;
GoalType type = GoalType.MONETARY;
- Need need = new Need(name, id, maxGoal, type);
+ Need need = new Need(name, type, maxGoal);
double current = 0.00;
need.setCurrent(current);
- assertEquals(need.getCurrent(), current);
+ assertEquals(current, need.getCurrent());
current = 100.00;
need.setCurrent(current);
- assertEquals(need.getCurrent(), current);
+ assertEquals(current, need.getCurrent());
current = -100.00;
need.setCurrent(current);
- assertEquals(need.getCurrent(), current);
+ assertEquals(current, need.getCurrent());
}
@@ -64,10 +60,9 @@ public class NeedTest {
public void testFilterAttributes() {
String name = "Jellyfish";
- int id = 0;
double maxGoal = 100.00;
GoalType type = GoalType.MONETARY;
- Need need = new Need(name, id, maxGoal, type);
+ Need need = new Need(name, type, maxGoal);
String[] filterAttributes = {"seaweed", "divers", "pacific", "plankton"};
@@ -80,10 +75,9 @@ public class NeedTest {
public void testSetMaxGoal() {
String name = "Jellyfish";
- int id = 0;
double maxGoal = 100.00;
GoalType type = GoalType.MONETARY;
- Need need = new Need(name, id, maxGoal, type);
+ Need need = new Need(name, type, maxGoal);
double newGoal = 200.00;
need.setMaxGoal(newGoal);
@@ -96,10 +90,9 @@ public class NeedTest {
public void testSetName() {
String name = "Jellyfish";
- int id = 0;
double maxGoal = 100.00;
GoalType type = GoalType.MONETARY;
- Need need = new Need(name, id, maxGoal, type);
+ Need need = new Need(name, type, maxGoal);
String newName = "TESTINGFUN";
need.setName(newName);
diff --git a/ufund-api/src/test/java/com/ufund/api/ufundapi/model/UserTest.java b/ufund-api/src/test/java/com/ufund/api/ufundapi/model/UserTest.java
index 716fbfd..54aa4d1 100644
--- a/ufund-api/src/test/java/com/ufund/api/ufundapi/model/UserTest.java
+++ b/ufund-api/src/test/java/com/ufund/api/ufundapi/model/UserTest.java
@@ -1,19 +1,34 @@
package com.ufund.api.ufundapi.model;
-import static org.junit.jupiter.api.Assertions.assertEquals;
-import static org.junit.jupiter.api.Assertions.assertNotNull;
import org.junit.jupiter.api.Tag;
import org.junit.jupiter.api.Test;
+import static org.junit.jupiter.api.Assertions.*;
+import static org.mockito.Mockito.mock;
+
+import java.io.IOException;
+
+import org.junit.jupiter.api.BeforeEach;
+import static org.mockito.Mockito.when;
+
+import com.ufund.api.ufundapi.service.CupboardService;
+
@Tag("Model-tier")
public class UserTest {
+ private CupboardService cupboardService;
+
+ @BeforeEach
+ public void setup() {
+ cupboardService = mock(CupboardService.class);
+ }
+
@Test
public void createUser() {
String name = "Bob";
- User user = new User(name);
+ User user = User.create(name, "pass");
assertNotNull(user);
@@ -23,42 +38,51 @@ public class UserTest {
public void testUsername() {
String expectedName = "Bob";
+ String password = "password";
- User user = new User(expectedName);
+ User user = User.create(expectedName, password);
- assertEquals(expectedName, user.getName());
+ assertEquals(expectedName, user.getUsername());
}
@Test
- public void addNeedToBasket() {
+ public void addNeedToBasket() throws IOException {
String expectedName = "Bob";
- User user = new User(expectedName);
+ User user = User.create(expectedName, "pass");
Need need = new Need("Test", 0, 100, Need.GoalType.MONETARY);
Need[] needs = { need };
+ when(cupboardService.getNeed(0)).thenReturn(need);
+
user.addToBasket(need);
- assertEquals(needs[0], user.getBasketNeeds()[0]);
+ Need getNeed = cupboardService.getNeed(user.getBasketNeeds()[0]);
+
+ assertEquals(needs[0], getNeed);
}
@Test
- public void testRemoveBasketNeed() {
+ public void testRemoveBasketNeed() throws IOException {
String expectedName = "Bob";
- User user = new User(expectedName);
+ User user = User.create(expectedName, "pass");
Need need = new Need("Test", 0, 100, Need.GoalType.MONETARY);
Need need2 = new Need("Test2", 0, 100, Need.GoalType.MONETARY);
+ when(cupboardService.getNeed(0)).thenReturn(need2);
+
user.addToBasket(need);
user.removeBasketNeed(need);
user.addToBasket(need2);
- assertEquals(need2, user.getBasketNeeds()[0]);
+ Need getNeed = cupboardService.getNeed(user.getBasketNeeds()[0]);
+
+ assertEquals(need2, getNeed);
}
@@ -67,9 +91,9 @@ public class UserTest {
String expectedName = "Bob";
- User user = new User(expectedName);
+ User user = User.create(expectedName, "pass");
- assertEquals(false, user.verifyPassword(expectedName));
+ assertFalse(user.verifyPassword(expectedName));
}
diff --git a/ufund-api/src/test/java/com/ufund/api/ufundapi/persistence/CupboardFileDaoTest.java b/ufund-api/src/test/java/com/ufund/api/ufundapi/persistence/CupboardFileDAOTest.java
index 8aa6fe0..f786a8c 100644
--- a/ufund-api/src/test/java/com/ufund/api/ufundapi/persistence/CupboardFileDaoTest.java
+++ b/ufund-api/src/test/java/com/ufund/api/ufundapi/persistence/CupboardFileDAOTest.java
@@ -1,79 +1,65 @@
package com.ufund.api.ufundapi.persistence;
+import java.io.File;
+import java.io.IOException;
+
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertTrue;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Tag;
+import org.junit.jupiter.api.Test;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
-import java.io.File;
-import java.io.IOException;
-
import com.fasterxml.jackson.databind.ObjectMapper;
import com.ufund.api.ufundapi.model.Need;
-import com.ufund.api.ufundapi.model.User;
-
-import org.junit.jupiter.api.BeforeEach;
-import org.junit.jupiter.api.Tag;
-import org.junit.jupiter.api.Test;
-
import com.ufund.api.ufundapi.model.Need.GoalType;
@Tag("Persistence-tier")
-public class CupboardFileDaoTest {
- CupboardFileDao cupboardFileDao;
- Need[] testNeeds;
- ObjectMapper mockObjectMapper;
+public class CupboardFileDAOTest {
+ private CupboardFileDAO cupboardFileDao;
+ private Need[] testNeeds;
+ private ObjectMapper mockObjectMapper;
@BeforeEach
public void setupCupboardFileDao() throws IOException {
mockObjectMapper = mock(ObjectMapper.class);
- testNeeds = new Need[3];
- testNeeds[0] = new Need("one", 0, 100, Need.GoalType.MONETARY);
- testNeeds[1] = new Need("two", 1, 100, Need.GoalType.MONETARY);
- testNeeds[2] = new Need("three", 2, 100, Need.GoalType.MONETARY);
+ testNeeds = new Need[]{
+ new Need("one", 0, 100, Need.GoalType.MONETARY),
+ new Need("two", 1, 100, Need.GoalType.MONETARY),
+ new Need("three", 2, 100, Need.GoalType.MONETARY)
+ };
// When the object mapper is supposed to read from the file
// the mock object mapper will return the hero array above
when(mockObjectMapper
.readValue(new File("doesnt_matter.txt"),Need[].class))
.thenReturn(testNeeds);
- cupboardFileDao = new CupboardFileDao("doesnt_matter.txt",mockObjectMapper);
+ cupboardFileDao = new CupboardFileDAO("doesnt_matter.txt",mockObjectMapper);
}
@Test
- public void GetNeedsTest() throws IOException {
+ public void getNeedsTest() {
Need[] needs = cupboardFileDao.getNeeds();
assertEquals(needs.length,testNeeds.length);
assertEquals(needs[0].getName(), testNeeds[0].getName());
}
@Test
- public void GetNeedTest() throws IOException {
+ public void getNeedTest() {
Need need1 = cupboardFileDao.getNeed(0);
assertEquals(testNeeds[0], need1);
}
@Test
- public void Fet() throws IOException {
- String targetName1 = "one";
- String targetName2 = "two";
-
- Need need1 = cupboardFileDao.findNeeds(targetName1)[0];
- Need need2 = cupboardFileDao.findNeeds(targetName2)[0];
-
- assertEquals(testNeeds[0], need1);
- assertEquals(testNeeds[1], need2);
- }
-
- @Test
- public void CreateNeedTest() throws IOException {
+ public void createNeedTest() throws IOException {
Need newNeed = new Need("sea urchin hats", 3, 100, GoalType.PHYSICAL);
- Need actualNeed = cupboardFileDao.createNeed(newNeed);
+ Need actualNeed = cupboardFileDao.addNeed(newNeed);
assertNotNull(actualNeed);
@@ -81,7 +67,7 @@ public class CupboardFileDaoTest {
}
@Test
- public void DeleteNeedTest() throws IOException {
+ public void deleteNeedTest() throws IOException {
Need undeletedNeed = cupboardFileDao.getNeed(0);
assertNotNull(undeletedNeed);
@@ -93,7 +79,7 @@ public class CupboardFileDaoTest {
}
@Test
- public void UpdateNeedTest() throws IOException {
+ public void updateNeedTest() throws IOException {
Need[] needs = cupboardFileDao.getNeeds();
Need unupdatedNeed = needs[needs.length - 1];
assertNotNull(unupdatedNeed);
diff --git a/ufund-api/src/test/java/com/ufund/api/ufundapi/persistence/UserAuthFileDAOTest.java b/ufund-api/src/test/java/com/ufund/api/ufundapi/persistence/UserAuthFileDAOTest.java
new file mode 100644
index 0000000..f7db747
--- /dev/null
+++ b/ufund-api/src/test/java/com/ufund/api/ufundapi/persistence/UserAuthFileDAOTest.java
@@ -0,0 +1,63 @@
+package com.ufund.api.ufundapi.persistence;
+
+import java.io.File;
+import java.io.IOException;
+
+import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Tag;
+import org.junit.jupiter.api.Test;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.ufund.api.ufundapi.model.UserAuth;
+
+@Tag("Persistence-tier")
+public class UserAuthFileDAOTest {
+
+ private UserAuthFIleDAO userAuthFIleDAO;
+ private ObjectMapper mockObjectMapper;
+ private UserAuth[] userAuths;
+
+ @BeforeEach
+ public void setupUserAuthFileDAO() throws IOException {
+
+ mockObjectMapper = mock(ObjectMapper.class);
+ userAuths = new UserAuth[]{
+ new UserAuth("123", "Phil", null),
+ new UserAuth("456", "Bob", null),
+ new UserAuth("789", "Steve", null)
+ };
+ // When the object mapper is supposed to read from the file
+ // the mock object mapper will return the hero array above
+ when(mockObjectMapper
+ .readValue(new File("doesnt_matter.txt"),UserAuth[].class))
+ .thenReturn(userAuths);
+ userAuthFIleDAO = new UserAuthFIleDAO(mockObjectMapper, "doesnt_matter.txt");
+ }
+
+ @Test
+ public void getUserAuthTest() {
+ String key = "123";
+ UserAuth auth = userAuthFIleDAO.getUserAuth(key);
+
+ assertEquals(auth, userAuths[0]);
+ }
+
+ @Test
+ public void addUserAuthTest() throws IOException {
+ UserAuth auth = new UserAuth("999", "Fish", null);
+
+ assertDoesNotThrow(() -> userAuthFIleDAO.addUserAuth(auth));
+ }
+
+ @Test
+ public void removeUserAuthTest() throws IOException {
+ String key = "123";
+
+ assertDoesNotThrow(() -> userAuthFIleDAO.removeUserAuth(key));
+ }
+
+}
diff --git a/ufund-api/src/test/java/com/ufund/api/ufundapi/persistence/UserFileDAOTest.java b/ufund-api/src/test/java/com/ufund/api/ufundapi/persistence/UserFileDAOTest.java
index dfe9b10..9361188 100644
--- a/ufund-api/src/test/java/com/ufund/api/ufundapi/persistence/UserFileDAOTest.java
+++ b/ufund-api/src/test/java/com/ufund/api/ufundapi/persistence/UserFileDAOTest.java
@@ -1,24 +1,22 @@
package com.ufund.api.ufundapi.persistence;
+import java.io.File;
+import java.io.IOException;
+
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertTrue;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Tag;
+import org.junit.jupiter.api.Test;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
-import java.io.File;
-import java.io.IOException;
-
import com.fasterxml.jackson.databind.ObjectMapper;
-
import com.ufund.api.ufundapi.model.User;
-import org.junit.jupiter.api.BeforeEach;
-import org.junit.jupiter.api.Tag;
-import org.junit.jupiter.api.Test;
-
@Tag("Persistence-tier")
public class UserFileDAOTest {
UserFileDAO userFileDAO;
@@ -29,9 +27,9 @@ public class UserFileDAOTest {
public void setupHeroFileDAO() throws IOException {
mockObjectMapper = mock(ObjectMapper.class);
testUsers = new User[3];
- testUsers[0] = new User("bob");
- testUsers[1] = new User("admin");
- testUsers[2] = new User("jelly12");
+ testUsers[0] = User.create("bob", "pass");
+ testUsers[1] = User.create("admin", "pass");
+ testUsers[2] = User.create("jelly12", "pass");
// When the object mapper is supposed to read from the file
// the mock object mapper will return the hero array above
@@ -42,7 +40,7 @@ public class UserFileDAOTest {
}
@Test
- public void GetUsersTest() throws IOException {
+ public void getUsersTest() {
User[] users = userFileDAO.getUsers();
assertEquals(users.length,testUsers.length);
@@ -50,16 +48,17 @@ public class UserFileDAOTest {
for (int i = 0; i < testUsers.length;++i) {
boolean isInArray = false;
for (User user : testUsers) {
- if (users[i].getName().equals(user.getName())) {
- isInArray = true;
- }
+ if (users[i].getUsername().equals(user.getUsername())) {
+ isInArray = true;
+ break;
+ }
}
assertTrue(isInArray);
}
}
@Test
- public void FindUsersTest() throws IOException {
+ public void findUsersTest() {
User realUser1 = userFileDAO.getUser("bob");
User realUser2 = userFileDAO.getUser("admin");
@@ -68,25 +67,25 @@ public class UserFileDAOTest {
}
@Test
- public void FindUsersNullTest() throws IOException {
+ public void findUsersNullTest() {
User fakeUser = userFileDAO.getUser("phil.n.thropist");
assertNull(fakeUser);
}
@Test
- public void CreateUserTest() throws IOException {
- User newUser = new User("keshey");
- userFileDAO.createUser(newUser);
+ public void createUserTest() throws IOException {
+ User newUser = User.create("keshey", "pass");
+ userFileDAO.addUser(newUser);
User actualUser = userFileDAO.getUser("keshey");
assertNotNull(actualUser);
- assertEquals(actualUser.getName(), newUser.getName());
+ assertEquals(actualUser.getUsername(), newUser.getUsername());
}
@Test
- public void DeleteUserTest() throws IOException {
+ public void deleteUserTest() throws IOException {
User notDeletedUser = userFileDAO.getUser("jelly12");
assertNotNull(notDeletedUser);
@@ -98,15 +97,15 @@ public class UserFileDAOTest {
}
@Test
- public void UpdateUserTest() throws IOException {
+ public void updateUserTest() throws IOException {
User toBeUpdatedUser = userFileDAO.getUser("admin");
assertNotNull(toBeUpdatedUser);
- User updatedUser = new User("jellinadmin");
+ User updatedUser = User.create("admin", "newPass");
- updatedUser = userFileDAO.updateUser(updatedUser, "admin");
+ updatedUser = userFileDAO.updateUser(updatedUser);
assertNotEquals(toBeUpdatedUser, updatedUser);
- assertEquals("jellinadmin", updatedUser.getName());
+ assertEquals("admin", updatedUser.getUsername());
}
}
diff --git a/ufund-api/src/test/java/com/ufund/api/ufundapi/service/AuthServiceTest.java b/ufund-api/src/test/java/com/ufund/api/ufundapi/service/AuthServiceTest.java
new file mode 100644
index 0000000..55cf7a9
--- /dev/null
+++ b/ufund-api/src/test/java/com/ufund/api/ufundapi/service/AuthServiceTest.java
@@ -0,0 +1,110 @@
+package com.ufund.api.ufundapi.service;
+
+import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import java.io.IOException;
+
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Tag;
+import org.junit.jupiter.api.Test;
+
+import com.ufund.api.ufundapi.DuplicateKeyException;
+import com.ufund.api.ufundapi.model.User;
+import com.ufund.api.ufundapi.model.UserAuth;
+import com.ufund.api.ufundapi.persistence.UserAuthDAO;
+
+@Tag("Service-tier")
+public class AuthServiceTest {
+
+ private UserAuthDAO mockAuthDAO;
+ private UserService mockUserService;
+ private AuthService authService;
+ private String username;
+ private String key;
+ private String password;
+ private User user;
+
+ @BeforeEach
+ public void setupAuthService() {
+ mockAuthDAO = mock(UserAuthDAO.class);
+ mockUserService = mock(UserService.class);
+ authService = new AuthService(mockAuthDAO, mockUserService);
+
+ username = "Fish";
+ password = "sticks";
+ key = UserAuth.generate(username).getKey();
+ user = User.create(username, password);
+
+ }
+
+ @Test
+ public void testAuthenticate() throws IOException {
+ // Mock
+ when(mockAuthDAO.getUserAuth(key)).thenReturn(new UserAuth(key, username, null));
+ when(mockUserService.getUser(username)).thenReturn(user);
+
+ // Analyze
+ assertDoesNotThrow(() -> authService.authenticate(username, key));
+
+ }
+
+// @Test
+// public void testAuthenticateMismatchName() throws IOException {
+// // Mock
+// when(mockAuthDAO.getUserAuth(key)).thenReturn(new UserAuth(key, "EvilFish", null));
+// when(mockUserService.getUser("EvilFish")).thenReturn(user);
+//
+// // Analyze
+// assertThrows(IllegalAccessException.class, () -> authService.authenticate(username, key));
+//
+// }
+
+ @Test
+ public void testAuthenticateMissingUserAuth() throws IOException {
+ // Mock
+ when(mockAuthDAO.getUserAuth(key)).thenReturn(null);
+
+ // Analyze
+ assertThrows(IllegalAccessException.class, () -> authService.authenticate(username, key));
+
+ }
+
+ @Test
+ public void testLogin() throws IOException, DuplicateKeyException, IllegalAccessException {
+ // Mock
+ when(mockUserService.getUser(username)).thenReturn(user);
+
+
+ // Analyze
+ assertDoesNotThrow(() -> authService.login(username, password));
+ }
+
+ @Test
+ public void testLoginNullUser() throws IOException, DuplicateKeyException, IllegalAccessException {
+ // Mock
+ when(mockUserService.getUser(username)).thenReturn(null);
+
+ // Analyze
+ assertThrows(IllegalAccessException.class, () -> authService.login(username, password));
+ }
+
+ @Test
+ public void testLoginMismatchPasswords() throws IOException, DuplicateKeyException, IllegalAccessException {
+ // Mock
+ when(mockUserService.getUser(username)).thenReturn(User.create(username, "fries"));
+
+ // Analyze
+ assertThrows(IllegalAccessException.class, () -> authService.login(username, password));
+ }
+
+ @Test
+ public void testLogout() throws IOException, DuplicateKeyException, IllegalAccessException {
+ // Analyze
+ assertDoesNotThrow(() -> authService.logout(key));
+
+ }
+
+}
diff --git a/ufund-api/src/test/java/com/ufund/api/ufundapi/service/CupboardServiceTest.java b/ufund-api/src/test/java/com/ufund/api/ufundapi/service/CupboardServiceTest.java
new file mode 100644
index 0000000..99ca23c
--- /dev/null
+++ b/ufund-api/src/test/java/com/ufund/api/ufundapi/service/CupboardServiceTest.java
@@ -0,0 +1,197 @@
+package com.ufund.api.ufundapi.service;
+
+import java.io.IOException;
+
+import static org.junit.jupiter.api.Assertions.assertArrayEquals;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.mockito.Mockito.*;
+
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Tag;
+import org.junit.jupiter.api.Test;
+
+import com.ufund.api.ufundapi.DuplicateKeyException;
+import com.ufund.api.ufundapi.model.Need;
+import com.ufund.api.ufundapi.model.Need.GoalType;
+import com.ufund.api.ufundapi.persistence.CupboardDAO;
+
+@Tag("Service-tier")
+public class CupboardServiceTest {
+
+ private CupboardDAO mockCupboardDAO;
+ private CupboardService cupboardService;
+
+ @BeforeEach
+ public void setupCupboardService() {
+ mockCupboardDAO = mock(CupboardDAO.class);
+ cupboardService = new CupboardService(mockCupboardDAO);
+
+ }
+
+ @Test
+ public void testCreateNeed() throws IOException, DuplicateKeyException {
+ // Setup
+ String name = "Jellyfish";
+ double maxGoal = 100.00;
+ GoalType type = GoalType.MONETARY;
+ Need need = new Need(name, type, maxGoal);
+
+ // When the same id is passed in, our mock User DAO will return the User object
+ when(mockCupboardDAO.addNeed(any())).thenReturn(need);
+ when(mockCupboardDAO.getNeeds()).thenReturn(new Need[0]);
+
+ // Invoke
+ Need response = cupboardService.createNeed(name, maxGoal, type);
+
+ // Analyze
+ assertNotNull(response);
+ assertEquals(need, response);
+ }
+
+ @Test
+ public void testCreateNeedBadGoal() throws IOException, DuplicateKeyException {
+ // Setup
+ String name = "Jellyfish";
+ double maxGoal = -100.00;
+ GoalType type = GoalType.MONETARY;
+ Need need = new Need(name, type, maxGoal);
+
+ // When the same id is passed in, our mock User DAO will return the User object
+ when(mockCupboardDAO.addNeed(any())).thenReturn(need);
+ when(mockCupboardDAO.getNeeds()).thenReturn(new Need[0]);
+
+ // Invoke
+ // Need response = cupboardService.createNeed(name, maxGoal, type);
+
+ // Analyze
+ assertThrows(IllegalArgumentException.class, () -> {
+ cupboardService.createNeed(name, maxGoal, type);
+ });
+ }
+
+ @Test
+ public void testCreateNeedDuplicate() throws IOException, DuplicateKeyException {
+ // Setup
+ String name = "Jellyfish";
+ double maxGoal = 100.00;
+ GoalType type = GoalType.MONETARY;
+ Need need = new Need(name, type, maxGoal);
+ Need[] needs = { need };
+
+ // When the same id is passed in, our mock User DAO will return the User object
+ when(mockCupboardDAO.addNeed(any())).thenReturn(need);
+ when(mockCupboardDAO.getNeeds()).thenReturn(needs);
+
+ // Invoke
+ // Need response = cupboardService.createNeed(name, maxGoal, type);
+
+ // Analyze
+ assertThrows(DuplicateKeyException.class, () -> {
+ cupboardService.createNeed(name, maxGoal, type);
+ });
+ }
+
+ @Test
+ public void testSearchNeeds() throws IOException, DuplicateKeyException {
+ // Setup
+ String name = "Jellyfish";
+ double maxGoal = 100.00;
+ GoalType type = GoalType.MONETARY;
+ Need need = new Need(name, type, maxGoal);
+ Need[] needs = { need };
+
+ // When the same id is passed in, our mock User DAO will return the User object
+ when(mockCupboardDAO.getNeeds()).thenReturn(needs);
+
+ // Invoke
+ Need[] response = cupboardService.searchNeeds("Jelly");
+
+ // Analyze
+ assertEquals(need, response[0]);
+ assertEquals(need.getName(), response[0].getName());
+ }
+
+ @Test
+ public void testSearchNeedsFail() throws IOException, DuplicateKeyException {
+ // Setup
+ String name = "Jellyfish";
+ double maxGoal = 100.00;
+ GoalType type = GoalType.MONETARY;
+ Need need = new Need(name, type, maxGoal);
+ Need[] needs = { need };
+
+ // When the same id is passed in, our mock User DAO will return the User object
+ when(mockCupboardDAO.getNeeds()).thenReturn(needs);
+
+ // Invoke
+ Need[] response = cupboardService.searchNeeds("Octopus");
+
+ // Analyze
+ assertArrayEquals(new Need[0], response);
+ }
+
+ @Test
+ public void testGetNeed() throws IOException, DuplicateKeyException {
+ // Setup
+ String name = "Jellyfish";
+ double maxGoal = 100.00;
+ int id = 0;
+ GoalType type = GoalType.MONETARY;
+ Need need = new Need(name, type, maxGoal);
+
+ // When the same id is passed in, our mock User DAO will return the User object
+ when(mockCupboardDAO.getNeed(id)).thenReturn(need);
+
+ // Invoke
+ Need response = cupboardService.getNeed(id);
+
+ // Analyze
+ assertEquals(need, response);
+ }
+
+ @Test
+ public void testUpdateNeed() throws IOException, DuplicateKeyException {
+ // Setup
+ String name = "Jellyfish";
+ double maxGoal = 100.00;
+ int id = 0;
+ GoalType type = GoalType.MONETARY;
+ Need need = new Need(name, type, maxGoal);
+ Need newNeed = new Need("Octopus", type, maxGoal);
+
+ // When the same id is passed in, our mock User DAO will return the User object
+ when(mockCupboardDAO.updateNeed(any())).thenReturn(newNeed);
+
+ // Invoke
+ Need response = cupboardService.updateNeed(newNeed, id);
+
+ // Analyze
+ assertEquals(newNeed, response);
+ }
+
+ @Test
+ public void testDeleteNeed() throws IOException, DuplicateKeyException {
+ // Setup
+ String name = "Jellyfish";
+ double maxGoal = 100.00;
+ int id = 0;
+ GoalType type = GoalType.MONETARY;
+ Need need = new Need(name, type, maxGoal);
+
+ // When the same id is passed in, our mock User DAO will return the User object
+ when(mockCupboardDAO.deleteNeed(id)).thenReturn(true);
+ when(mockCupboardDAO.getNeeds()).thenReturn(new Need[0]);
+
+ // Invoke
+ boolean response = cupboardService.deleteNeed(id);
+ Need[] responseNeeds = cupboardService.getNeeds();
+
+ // Analyze
+ assertTrue(response);
+ assertArrayEquals(new Need[0], responseNeeds);
+ }
+
+} \ No newline at end of file
diff --git a/ufund-api/src/test/java/com/ufund/api/ufundapi/service/UserServiceTest.java b/ufund-api/src/test/java/com/ufund/api/ufundapi/service/UserServiceTest.java
new file mode 100644
index 0000000..0a0cb71
--- /dev/null
+++ b/ufund-api/src/test/java/com/ufund/api/ufundapi/service/UserServiceTest.java
@@ -0,0 +1,126 @@
+package com.ufund.api.ufundapi.service;
+
+import java.io.IOException;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import com.ufund.api.ufundapi.DuplicateKeyException;
+import com.ufund.api.ufundapi.model.User;
+import com.ufund.api.ufundapi.persistence.UserDAO;
+
+public class UserServiceTest {
+
+ private UserService userService;
+ private UserDAO mockUserDAO;
+
+
+ @BeforeEach
+ public void setupUserService() {
+ mockUserDAO = mock(UserDAO.class);
+ userService = new UserService(mockUserDAO);
+ }
+
+ @Test
+ public void testCreateUser() throws IOException, DuplicateKeyException {
+ // Setup
+ String username = "Jelly";
+ String password = "Fish";
+ User user = User.create(username, password);
+
+ // Mock
+ when(mockUserDAO.getUser(username)).thenReturn(null);
+ when(mockUserDAO.addUser(any())).thenReturn(user);
+
+ // Invoke
+
+ // Analyze
+ assertEquals(user, userService.createUser(username, password));
+ }
+
+ @Test
+ public void testCreateUserDuplicate() throws IOException, DuplicateKeyException {
+ // Setup
+ String username = "Jelly";
+ String password = "Fish";
+ User user = User.create(username, password);
+
+ // Mock
+ when(mockUserDAO.getUser(username)).thenReturn(User.create("Phil", "Phil"));
+ when(mockUserDAO.addUser(any())).thenReturn(user);
+
+ // Analyze
+ assertThrows(DuplicateKeyException.class, () -> userService.createUser(username, password));
+ }
+
+ @Test
+ public void testGetUser() throws IOException, DuplicateKeyException {
+ // Setup
+ String username = "Jelly";
+ String password = "Fish";
+ User user = User.create(username, password);
+
+ // Mock
+ when(mockUserDAO.getUser(username)).thenReturn(user);
+
+ // Analyze
+ assertEquals(user, userService.getUser(username));
+ }
+
+ @Test
+ public void testUpdateUser() throws IOException, DuplicateKeyException {
+ // Setup
+ String username = "Jelly";
+ String password = "Fish";
+ User oldUser = User.create(username, password);
+
+ String newUsername = "Jelly";
+ String newPassword = "Dog";
+ User newUser = User.create(newUsername, newPassword);
+
+ // Mock
+ when(mockUserDAO.updateUser(newUser)).thenReturn(newUser);
+
+ // Analyze
+ assertEquals(newUser, userService.updateUser(newUser, oldUser.getUsername()));
+ }
+
+ @Test
+ public void testUpdateUserDifferentUsernames() throws IOException, DuplicateKeyException {
+ // Setup
+ String username = "Jelly";
+ String password = "Fish";
+ User oldUser = User.create(username, password);
+
+ String newUsername = "Cat";
+ String newPassword = "Fish";
+ User newUser = User.create(newUsername, newPassword);
+
+ // Mock
+ when(mockUserDAO.updateUser(newUser)).thenReturn(newUser);
+
+ // Analyze
+ assertThrows(IllegalArgumentException.class, () -> userService.updateUser(newUser, oldUser.getUsername()));
+ }
+
+ @Test
+ public void testDeleteUser() throws IOException, DuplicateKeyException {
+ // Setup
+ String username = "Jelly";
+ String password = "Fish";
+ User user = User.create(username, password);
+
+ // Mock
+ when(mockUserDAO.deleteUser(username)).thenReturn(true);
+
+ // Analyze
+ assertTrue(userService.deleteUser(username));
+ }
+
+}
diff --git a/ufund-ui/src/app/app-routing.module.ts b/ufund-ui/src/app/app-routing.module.ts
index d4f14da..4b76654 100644
--- a/ufund-ui/src/app/app-routing.module.ts
+++ b/ufund-ui/src/app/app-routing.module.ts
@@ -12,7 +12,7 @@ const routes: Routes = [
{path: 'login', component: LoginComponent},
{path: 'cupboard', component: CupboardComponent},
{path: 'dashboard', component: DashboardComponent},
- {path: 'funding-basket', component: FundingBasketComponent},
+ {path: 'basket', component: FundingBasketComponent},
{path: 'need/:id', component: NeedPageComponent}
];
diff --git a/ufund-ui/src/app/app.component.html b/ufund-ui/src/app/app.component.html
index cfebc2b..6b9338c 100644
--- a/ufund-ui/src/app/app.component.html
+++ b/ufund-ui/src/app/app.component.html
@@ -1,4 +1,6 @@
-<h1>jelly solutions:</h1>
+<h1>jelly solutions</h1>
+<span>{{currentUser$ | async}}</span>
+<hr>
<router-outlet />
diff --git a/ufund-ui/src/app/app.component.ts b/ufund-ui/src/app/app.component.ts
index 2dbf33c..6f4e1f5 100644
--- a/ufund-ui/src/app/app.component.ts
+++ b/ufund-ui/src/app/app.component.ts
@@ -1,4 +1,6 @@
-import { Component } from '@angular/core';
+import {Component, OnInit} from '@angular/core';
+import {UsersService} from './services/users.service';
+import {BehaviorSubject} from 'rxjs';
@Component({
selector: 'app-root',
@@ -6,6 +8,21 @@ import { Component } from '@angular/core';
standalone: false,
styleUrl: './app.component.css'
})
-export class AppComponent {
- title = 'ufund-ui';
+export class AppComponent implements OnInit {
+ // title = 'ufund-ui';
+ currentUser$: BehaviorSubject<string> = new BehaviorSubject<string>("Logged out.");
+
+ constructor(
+ private userService: UsersService
+ ) {}
+
+ ngOnInit() {
+ this.userService.getCurrentUserSubject().subscribe(r => {
+ this.currentUser$?.next(r
+ ? "Logged in as " + r.username
+ : "Logged out."
+ )
+ })
+ }
+
}
diff --git a/ufund-ui/src/app/app.module.ts b/ufund-ui/src/app/app.module.ts
index d818841..9f525fe 100644
--- a/ufund-ui/src/app/app.module.ts
+++ b/ufund-ui/src/app/app.module.ts
@@ -9,6 +9,11 @@ import {FundingBasketComponent} from './components/funding-basket/funding-basket
import {CupboardComponent} from './components/cupboard/cupboard.component';
import {NeedListComponent} from './components/need-list/need-list.component';
import {HttpClientModule} from '@angular/common/http';
+import {FormsModule} from '@angular/forms';
+import {RouterLink, RouterLinkActive, RouterOutlet} from '@angular/router';
+import {DashboardComponent} from './components/dashboard/dashboard.component';
+import {CommonModule} from '@angular/common';
+import {LoginComponent} from './components/login/login.component';
@NgModule({
declarations: [
@@ -17,11 +22,18 @@ import {HttpClientModule} from '@angular/common/http';
HomePageComponent,
FundingBasketComponent,
CupboardComponent,
- NeedListComponent
+ NeedListComponent,
+ DashboardComponent,
+ LoginComponent,
],
imports: [
BrowserModule,
AppRoutingModule,
+ FormsModule,
+ RouterLink,
+ RouterLinkActive,
+ RouterOutlet,
+ CommonModule,
HttpClientModule,
],
providers: [],
diff --git a/ufund-ui/src/app/components/cupboard/cupboard.component.css b/ufund-ui/src/app/components/cupboard/cupboard.component.css
index e69de29..fe4971a 100644
--- a/ufund-ui/src/app/components/cupboard/cupboard.component.css
+++ b/ufund-ui/src/app/components/cupboard/cupboard.component.css
@@ -0,0 +1,21 @@
+:host {
+ display: block;
+ border: 2px solid #000;
+ border-radius: 5px;
+ padding: 10px 20px;
+}
+
+#menu, #create-form, #delete-form, #update-form {
+ background-color: #d9d9d9;
+ padding: 10px 20px 20px 20px;
+ border: 2px solid #000;
+ border-radius: 5px;
+ width: 20%;
+ visibility: visible;
+
+}
+
+#create-button {
+ padding: 10px 20px;
+
+} \ No newline at end of file
diff --git a/ufund-ui/src/app/components/cupboard/cupboard.component.html b/ufund-ui/src/app/components/cupboard/cupboard.component.html
index ad8e60c..172360d 100644
--- a/ufund-ui/src/app/components/cupboard/cupboard.component.html
+++ b/ufund-ui/src/app/components/cupboard/cupboard.component.html
@@ -1,17 +1,50 @@
<h1> Cupboard </h1>
-<form>
- <label for="name">Name:</label><br>
- <input #name type="text" name="name"><br>
- <label for="id">Id:</label><br>
- <input #id type="number" name="id"><br>
- <label for="max-goal">Max Goal:</label><br>
- <input #maxgoal type="number" name="max-goal"><br>
- <label>Type</label><br>
- <input id="monetary" type="radio" name="type" value="MONETARY">
- <label for="monetary">Monetary</label><br>
- <input #physical type="radio" name="type" value="PHYSICAL">
- <label for="physical">Physical</label><br>
-</form>
-<button (click)="submit(name.value, id.valueAsNumber, maxgoal.valueAsNumber, physical.value)">Submit</button>
+<h2 *ngIf="isManager()" > Admin View </h2>
+<div id="menu" *ngIf="isManager()">
+ <button (click)="opencreate()">Create new Need</button>
+ <button (click)="openupdate()">Update existing Need</button>
+</div>
+<div id="create-form">
+ <h1> Create a new need </h1>
+ <form #cupboardForm="ngForm" (ngSubmit)="submit(cupboardForm.value)">
+ <label>Name:</label><br>
+ <input type="text" name="name" ngModel><br>
+ <label>Max Goal:</label><br>
+ <input type="number" name="maxGoal" ngModel><br>
+ <label>Type</label><br>
+ <input type="radio" name="type" value="MONETARY" ngModel>
+ <label>Monetary</label><br>
+ <input type="radio" name="type" value="PHYSICAL" ngModel>
+ <label>Physical</label><br>
+ <input type="submit" value="Submit">
+ </form>
+ <button (click)="back()">Close</button>
+ <span *ngIf="statusText">{{statusText | async}}</span>
-<app-need-list></app-need-list>
+</div>
+<div id="update-form">
+ <h1> Update a need </h1>
+ <label>Needs:</label><br>
+ <form #updateForm="ngForm" (ngSubmit)="update(updateForm.value)">
+ <div *ngFor="let need of needs">
+
+ <input type="radio" name="id" [value]=need.id [(ngModel)]="selectedNeedId" (change)="populateForm(need)">
+ <label name="template">{{need.name}}</label><br>
+ </div>
+ <label>Name:</label><br>
+ <input type="text" name="name" [(ngModel)]="selectedNeed.name"><br>
+ <label>Max Goal:</label><br>
+ <input type="number" name="maxGoal" [(ngModel)]="selectedNeed.maxGoal"><br>
+ <label>Type</label><br>
+ <input type="radio" name="type" value="MONETARY" [(ngModel)]="selectedNeed.type">
+ <label>Monetary</label><br>
+ <input type="radio" name="type" value="PHYSICAL" [(ngModel)]="selectedNeed.type">
+ <label>Physical</label><br>
+ <input type="submit" value="Submit">
+ </form>
+ <button (click)="back()">Close</button>
+ <span *ngIf="statusText">{{statusText | async}}</span>
+
+</div>
+<hr>
+<app-need-list #needList></app-need-list>
diff --git a/ufund-ui/src/app/components/cupboard/cupboard.component.ts b/ufund-ui/src/app/components/cupboard/cupboard.component.ts
index 53dad8a..9574de3 100644
--- a/ufund-ui/src/app/components/cupboard/cupboard.component.ts
+++ b/ufund-ui/src/app/components/cupboard/cupboard.component.ts
@@ -1,8 +1,10 @@
-import { Component, OnInit } from '@angular/core';
+import { Component, OnInit, ViewChild } from '@angular/core';
import { CupboardService } from '../../services/cupboard.service';
-import { NeedListComponent } from '../need-list/need-list.component';
-
+import { UsersService } from '../../services/users.service';
import { Need, GoalType } from '../../models/Need';
+import { userType } from '../../models/User';
+import { BehaviorSubject, catchError, of } from 'rxjs';
+import {NeedListComponent} from '../need-list/need-list.component';
@Component({
selector: 'app-cupboard',
@@ -10,22 +12,190 @@ import { Need, GoalType } from '../../models/Need';
templateUrl: './cupboard.component.html',
styleUrl: './cupboard.component.css'
})
-export class CupboardComponent implements
- OnInit {
-
- constructor(private cupboardService: CupboardService){}
- ngOnInit() {
-
-
- }
- need!: Need;
- submit(name: string, id: number, maxGoal: number, type: string) {
- if (this.need) {
- this.need.name = name;
- this.need.id = id;
- this.need.maxGoal = maxGoal;
- console.log(type);
- this.cupboardService.createNeed(this.need);
+
+export class CupboardComponent implements OnInit {
+
+ protected statusText = new BehaviorSubject("")
+
+ needs: any;
+ @ViewChild("needList") needList?: NeedListComponent
+
+ constructor(private cupboardService: CupboardService, private usersService: UsersService) { }
+
+ ngOnInit(): void {
+ this.cupboardService.getNeeds().subscribe(n => this.needs = n);
+ this.close();
+ this.openmenu();
+
+ if (this.isManager()) {
+ console.log("Admin view of Cupboard");
+ } else {
+ console.log("Limited helper view of Cupboard");
}
}
+
+ selectedNeed: any = {
+ name: '',
+ id: null,
+ maxGoal: null,
+ type: ''
+ };
+ selectedNeedId: number | null = null;
+
+ private hideElement(element: any) {
+ if (element){
+ element.style.visibility = 'hidden';
+ element.style.position = 'absolute';
+ }
+ }
+
+ private showElement(element: any) {
+ if (element){
+ element.style.visibility = 'visible';
+ element.style.position = 'relative';
+ }
+ }
+
+ openmenu() {
+ const menuElement = document.getElementById('menu');
+ this.showElement(menuElement);
+ }
+
+ opencreate() {
+ this.close();
+ this.showElement(document.getElementById('create-form'));
+ }
+
+ openupdate() {
+ this.close();
+ this.showElement(document.getElementById('update-form'));
+ }
+
+ back() {
+ this.close();
+ this.openmenu();
+ }
+
+ close() {
+ this.hideElement(document.getElementById('create-form'));
+ this.hideElement(document.getElementById('destroy-form'));
+ this.hideElement(document.getElementById('menu'));
+ this.hideElement(document.getElementById('update-form'));
+ }
+
+ populateForm(need: any): void {
+ this.selectedNeed = { ...need };
+ }
+
+ isManager() {
+ const type = this.usersService.getCurrentUser()?.type;
+ return type === ("MANAGER" as unknown as userType);
+ }
+
+ update(form: any) {
+ console.log(form);
+ const need: Need = {
+ name: form.name,
+ id: form.id, //system will control this
+ maxGoal: form.maxGoal,
+ type: GoalType[form.type as keyof typeof GoalType],
+ filterAttributes: [],
+ current: 0
+ };
+ console.log("need:", need);
+ console.log(need.id, need, "need updated");
+ this.cupboardService.updateNeed(need.id, need)
+ .pipe(catchError((ex, r) => {
+ this.statusText.next("Max goal must be greater than 0 " + friendlyHttpStatus[ex.status])
+ return of()
+ }))
+ .subscribe(
+ (result) => {
+ if (result) {
+ console.log("need updated successfully");
+ this.needList?.refresh()
+ } else {
+ console.log("need update failed");
+ }
+ }
+
+ );
+ }
+
+ submit(form: any) {
+ const need: Need = {
+ name: form.name,
+ id: 0,
+ maxGoal: form.maxGoal,
+ type: form.type,
+ filterAttributes: [],
+ current: 0
+ };
+ console.log("need:", need);
+ console.log("form submitted. creating need: ", need);
+ this.cupboardService.createNeed(need)
+ .pipe(catchError((ex, r) => {
+ this.statusText.next("Max goal must be greater than 0 " + friendlyHttpStatus[ex.status])
+ return of()
+ }))
+ .subscribe(
+ (result) => {
+ if (result) {
+ console.log("need created successfully");
+ this.needList?.refresh()
+ } else {
+ console.log("need creation failed");
+ }
+ }
+
+ );
+ }
+
+ destroy() {
+
+ }
}
+
+let friendlyHttpStatus: {[key: number]: string} = {
+ 200: 'OK',
+ 201: 'Created',
+ 202: 'Accepted',
+ 203: 'Non-Authoritative Information',
+ 204: 'No Content',
+ 205: 'Reset Content',
+ 206: 'Partial Content',
+ 300: 'Multiple Choices',
+ 301: 'Moved Permanently',
+ 302: 'Found',
+ 303: 'See Other',
+ 304: 'Not Modified',
+ 305: 'Use Proxy',
+ 306: 'Unused',
+ 307: 'Temporary Redirect',
+ 400: 'Bad Request',
+ 401: 'Unauthorized',
+ 402: 'Payment Required',
+ 403: 'Forbidden',
+ 404: 'Not Found',
+ 405: 'Method Not Allowed',
+ 406: 'Not Acceptable',
+ 407: 'Proxy Authentication Required',
+ 408: 'Request Timeout',
+ 409: 'Conflict',
+ 410: 'Gone',
+ 411: 'Length Required',
+ 412: 'Precondition Required',
+ 413: 'Request Entry Too Large',
+ 414: 'Request-URI Too Long',
+ 415: 'Unsupported Media Type',
+ 416: 'Requested Range Not Satisfiable',
+ 417: 'Expectation Failed',
+ 418: 'I\'m a teapot',
+ 429: 'Too Many Requests',
+ 500: 'Internal Server Error',
+ 501: 'Not Implemented',
+ 502: 'Bad Gateway',
+ 503: 'Service Unavailable',
+ 504: 'Gateway Timeout',
+ 505: 'HTTP Version Not Supported',
+};
diff --git a/ufund-ui/src/app/components/dashboard/dashboard.component.html b/ufund-ui/src/app/components/dashboard/dashboard.component.html
index 9c5fce9..fc8baf0 100644
--- a/ufund-ui/src/app/components/dashboard/dashboard.component.html
+++ b/ufund-ui/src/app/components/dashboard/dashboard.component.html
@@ -1 +1,5 @@
-<p>dashboard works!</p>
+
+<h1>Dashboard</h1>
+<input type="button" value="Back to Login" (click)="back()">
+<app-cupboard></app-cupboard>
+<app-funding-basket></app-funding-basket> \ No newline at end of file
diff --git a/ufund-ui/src/app/components/dashboard/dashboard.component.ts b/ufund-ui/src/app/components/dashboard/dashboard.component.ts
index 6da4013..48c5894 100644
--- a/ufund-ui/src/app/components/dashboard/dashboard.component.ts
+++ b/ufund-ui/src/app/components/dashboard/dashboard.component.ts
@@ -7,5 +7,9 @@ import { Component } from '@angular/core';
styleUrl: './dashboard.component.css'
})
export class DashboardComponent {
+ constructor() {}
+ back() {
+ window.history.back();
+ }
}
diff --git a/ufund-ui/src/app/components/funding-basket/funding-basket.component.ts b/ufund-ui/src/app/components/funding-basket/funding-basket.component.ts
index 1dcebc5..31f2982 100644
--- a/ufund-ui/src/app/components/funding-basket/funding-basket.component.ts
+++ b/ufund-ui/src/app/components/funding-basket/funding-basket.component.ts
@@ -22,7 +22,7 @@ export class FundingBasketComponent implements
private router: Router,
private usersService: UsersService
) {}
-
+
ngOnInit(): void {
if (!this.usersService.getCurrentUser()) {
this.router.navigate(['/login'], {queryParams: {redir: this.router.url}});
@@ -47,7 +47,7 @@ export class FundingBasketComponent implements
isInBasket(need: Need): boolean {
return this.basket.some(n => n.id == need.id);
}
-
+
addNeed(need: Need, quantity: number=1): void {
if (this.user && !this.isInBasket(need)) {
this.basket.push(need);
@@ -57,7 +57,7 @@ export class FundingBasketComponent implements
}
this.needCount++;
}
-
+
removeNeed(need: Need, quantity:number=1): void {
if (this.user && this.isInBasket(need)) {
this.need_quantity[need.id] -= quantity;
diff --git a/ufund-ui/src/app/components/home-page/home-page.component.html b/ufund-ui/src/app/components/home-page/home-page.component.html
index e13c539..d41e670 100644
--- a/ufund-ui/src/app/components/home-page/home-page.component.html
+++ b/ufund-ui/src/app/components/home-page/home-page.component.html
@@ -1,4 +1,3 @@
<a routerLink="/login">
Login/Sign Up
-</a>
-<p>home-page works!</p>
+</a> \ No newline at end of file
diff --git a/ufund-ui/src/app/components/login/login.component.css b/ufund-ui/src/app/components/login/login.component.css
index afd4bf1..435cc87 100644
--- a/ufund-ui/src/app/components/login/login.component.css
+++ b/ufund-ui/src/app/components/login/login.component.css
@@ -1,6 +1,16 @@
-:host {
+:host, .border {
display: flex;
flex-direction: column;
max-width: 300px;
gap: 5px
}
+
+.border {
+ border-style: solid;
+ border-width: 1px;
+ padding: 10px;
+ margin: 10px;
+ position: absolute;
+ background-color: white;
+ box-shadow: 0 0 10px 10px black;
+}
diff --git a/ufund-ui/src/app/components/login/login.component.html b/ufund-ui/src/app/components/login/login.component.html
index 41427ae..2cdb6d0 100644
--- a/ufund-ui/src/app/components/login/login.component.html
+++ b/ufund-ui/src/app/components/login/login.component.html
@@ -1,5 +1,7 @@
+<span *ngIf="next" style="color: red">You must be logged in to view this page</span>
<p>Login:</p>
-<input placeholder="Username" type="text">
-<input placeholder="Password" type="password">
-<button>Login</button>
-<button>Create Account...</button>
+<input placeholder="Username" type="text" #username>
+<input placeholder="Password" type="password" #password>
+<button type="button" (click)="login(username.value, password.value)">Login</button>
+<button type="button" (click)="signup(username.value, password.value)">Create Account</button>
+<span *ngIf="statusText">{{statusText | async}}</span>
diff --git a/ufund-ui/src/app/components/login/login.component.ts b/ufund-ui/src/app/components/login/login.component.ts
index efb8a58..9d806f5 100644
--- a/ufund-ui/src/app/components/login/login.component.ts
+++ b/ufund-ui/src/app/components/login/login.component.ts
@@ -1,4 +1,7 @@
-import { Component } from '@angular/core';
+import {Component, OnInit} from '@angular/core'
+import {UsersService} from '../../services/users.service';
+import {ActivatedRoute, Router} from '@angular/router';
+import {BehaviorSubject} from 'rxjs';
@Component({
selector: 'app-login',
@@ -6,6 +9,92 @@ import { Component } from '@angular/core';
templateUrl: './login.component.html',
styleUrl: './login.component.css'
})
-export class LoginComponent {
+export class LoginComponent implements OnInit {
+ protected next?: string | null;
+ protected statusText = new BehaviorSubject("")
+
+ constructor(
+ protected usersService: UsersService,
+ protected router: Router,
+ private route: ActivatedRoute
+ ) {}
+
+ ngOnInit() {
+ this.next = this.route.snapshot.queryParamMap.get('redir')
+ }
+
+ login(username: string | null, password: string | null) {
+ let next = this.next || '/dashboard'
+ console.log(`attempting to log in with ${username} ${password}`)
+ if (!username || !password) {
+ return;
+ }
+
+ this.usersService.login(username, password).then(() => {
+ this.router.navigate([next]);
+ }).catch(ex => {
+ this.statusText.next("Unable to login: " + friendlyHttpStatus[ex.status])
+ console.log(ex)
+ })
+ }
+
+ signup(username: string | null, password: string | null) {
+ console.log(`attempting to sign up with ${username} ${password}`)
+ if (!username || !password) {
+ return;
+ }
+
+ this.usersService.createUser(username, password).then(() => {
+ this.statusText.next("Account created, click login.")
+ }).catch(ex => {
+ this.statusText.next("Unable to create account: " + friendlyHttpStatus[ex.status])
+ console.log(ex)
+ })
+ }
}
+
+// temporary
+let friendlyHttpStatus: {[key: number]: string} = {
+ 200: 'OK',
+ 201: 'Created',
+ 202: 'Accepted',
+ 203: 'Non-Authoritative Information',
+ 204: 'No Content',
+ 205: 'Reset Content',
+ 206: 'Partial Content',
+ 300: 'Multiple Choices',
+ 301: 'Moved Permanently',
+ 302: 'Found',
+ 303: 'See Other',
+ 304: 'Not Modified',
+ 305: 'Use Proxy',
+ 306: 'Unused',
+ 307: 'Temporary Redirect',
+ 400: 'Bad Request',
+ 401: 'Unauthorized',
+ 402: 'Payment Required',
+ 403: 'Forbidden',
+ 404: 'Not Found',
+ 405: 'Method Not Allowed',
+ 406: 'Not Acceptable',
+ 407: 'Proxy Authentication Required',
+ 408: 'Request Timeout',
+ 409: 'Conflict',
+ 410: 'Gone',
+ 411: 'Length Required',
+ 412: 'Precondition Required',
+ 413: 'Request Entry Too Large',
+ 414: 'Request-URI Too Long',
+ 415: 'Unsupported Media Type',
+ 416: 'Requested Range Not Satisfiable',
+ 417: 'Expectation Failed',
+ 418: 'I\'m a teapot',
+ 429: 'Too Many Requests',
+ 500: 'Internal Server Error',
+ 501: 'Not Implemented',
+ 502: 'Bad Gateway',
+ 503: 'Service Unavailable',
+ 504: 'Gateway Timeout',
+ 505: 'HTTP Version Not Supported',
+};
diff --git a/ufund-ui/src/app/components/need-list/need-list.component.css b/ufund-ui/src/app/components/need-list/need-list.component.css
index e69de29..bbc3f2c 100644
--- a/ufund-ui/src/app/components/need-list/need-list.component.css
+++ b/ufund-ui/src/app/components/need-list/need-list.component.css
@@ -0,0 +1,24 @@
+:host {
+ list-style-type:circle;
+ border: 2px solid #000;
+ display: block;
+ width: 30%;
+ border-radius: 5px;
+
+}
+
+li, div {
+ border: 2px solid #000;
+ border-radius: 5px;
+ padding: 5px;
+ margin: 5px;
+
+}
+
+#search-form {
+ background-color: #d9d9d9;
+ padding: 10px 20px 20px 20px;
+ border: 2px solid #000;
+ border-radius: 5px;
+ visibility: visible;
+ } \ No newline at end of file
diff --git a/ufund-ui/src/app/components/need-list/need-list.component.html b/ufund-ui/src/app/components/need-list/need-list.component.html
index 6e48d96..07f6735 100644
--- a/ufund-ui/src/app/components/need-list/need-list.component.html
+++ b/ufund-ui/src/app/components/need-list/need-list.component.html
@@ -1,6 +1,26 @@
<h1>Needs List</h1>
+<input id="search-button" type="button" value="Search" (click)="open()">
+<div id="search-form">
+ <form #searchForm="ngForm">
+ <label>Search:</label><br>
+ <input type="text" name="search" (input)="search(searchForm.value)" ngModel>
+ <input type="button" value="Clear" (click)="searchForm.reset()"> <br>
+ </form>
+ <button (click)="close()">Close</button>
+ <div>
+ <h2 id="search-status">Search Results:</h2>
+ <div *ngFor="let need of searchResults">
+ <a routerLink="/need/{{need.id}}">
+ {{need.name}}
+ </a>
+ <button (click)="delete(need.id)" *ngIf="isManager()">Delete</button>
+ </div>
+ </div>
+</div>
+
<li *ngFor="let need of needs">
<a routerLink="/need/{{need.id}}">
{{need.name}}
</a>
+ <button (click)="delete(need.id)" *ngIf="isManager()">Delete</button>
</li>
diff --git a/ufund-ui/src/app/components/need-list/need-list.component.ts b/ufund-ui/src/app/components/need-list/need-list.component.ts
index a3eb072..b21979f 100644
--- a/ufund-ui/src/app/components/need-list/need-list.component.ts
+++ b/ufund-ui/src/app/components/need-list/need-list.component.ts
@@ -1,7 +1,8 @@
import { Component } from '@angular/core';
import {Need} from '../../models/Need';
import {CupboardService} from '../../services/cupboard.service';
-
+import { UsersService } from '../../services/users.service';
+import { userType } from '../../models/User';
@Component({
selector: 'app-need-list',
standalone: false,
@@ -10,12 +11,92 @@ import {CupboardService} from '../../services/cupboard.service';
})
export class NeedListComponent {
needs: Need[] = [];
+ searchResults: Need[] = [];
constructor(
- private cupboardService: CupboardService
+ private cupboardService: CupboardService,
+ private usersService: UsersService
) {}
+ refresh() {
+ this.cupboardService.getNeeds().subscribe(n => this.needs = n)
+ }
+
ngOnInit(): void {
- this.cupboardService.getNeeds().subscribe(n => this.needs = n)
+ this.refresh()
+ // this.close();
+ }
+
+ private showElement(element: any) {
+ if (element){
+ element.style.visibility = 'visible';
+ element.style.position = 'relative';
+ }
+ }
+
+ private hideElement(element: any) {
+ if (element){
+ element.style.visibility = 'hidden';
+ element.style.position = 'absolute';
+ }
+ }
+
+ private updateSearchStatus(text: string) {
+ let element = document.getElementById('search-status');
+ if (element) {
+ element.innerHTML = text;
+ }
+ }
+
+ open() {
+ this.hideElement(document.getElementById('search-button'));
+ this.showElement(document.getElementById('search-form'));
+ }
+
+ close() {
+ this.hideElement(document.getElementById('search-form'));
+ this.showElement(document.getElementById('search-button'));
+ }
+
+ private searchDelay: any;
+
+ async search(form: any) {
+ //wait .25 seconds before searching but cancel if another search is made during the wait to prevent too many api calls
+
+ //remove previous search if it exists
+ if (this.searchDelay) {
+ clearTimeout(this.searchDelay);
+ }
+
+ this.searchDelay = setTimeout(() => {
+ const currentSearchValue = form.search; //latest value of the search
+ this.cupboardService.searchNeeds(currentSearchValue).subscribe((n) => {
+ this.searchResults = n;
+ console.log(currentSearchValue, this.searchResults);
+ if (this.searchResults.length === this.needs.length) {
+ this.updateSearchStatus("Please refine your search");
+ this.searchResults = [];
+ } else if (this.searchResults.length === 0) {
+ this.updateSearchStatus("No results found");
+ } else {
+ this.updateSearchStatus("Search results:");
+ }
+ });
+ }, 250);
+ }
+
+ delete(id : number) {
+ this.cupboardService.deleteNeed(id).subscribe(() => {
+ this.needs = this.needs.filter(n => n.id !== id)
+ })
+ }
+
+ isManager() {
+ const type = this.usersService.getCurrentUser()?.type;
+ return type === ("MANAGER" as unknown as userType);
+ }
+
+ back() {
+ this.searchResults = [];
}
}
diff --git a/ufund-ui/src/app/components/need-page/need-page.component.html b/ufund-ui/src/app/components/need-page/need-page.component.html
index 0bc4746..8234ac7 100644
--- a/ufund-ui/src/app/components/need-page/need-page.component.html
+++ b/ufund-ui/src/app/components/need-page/need-page.component.html
@@ -1,7 +1,21 @@
-<h1>Need page</h1>
-<p>id: {{need?.id}}</p>
-<p>name: {{need?.name}}</p>
-<p>filterAttributes: {{need?.filterAttributes}}</p>
-<p>type: {{need?.type}}</p>
-<p>max goal: {{need?.maxGoal}}</p>
-<p>current: {{need?.maxGoal}}</p>
+<h1>Viewing Need: {{need?.name}}</h1>
+<a>internal id: {{need?.id}}</a>
+<div style="display: flex; column-gap: 6px;">
+ <h3>Looking for</h3>
+ <h3><u>{{need?.type}}</u></h3>
+ <h3>Donations.</h3>
+</div>
+<div *ngIf="need?.filterAttributes != null">
+ <p>Tags:</p>
+ <ul style="display: flex; column-gap: 24px;">
+ <li *ngFor="let tag of need?.filterAttributes">
+ <p>{{tag}}</p>
+ </li>
+ </ul>
+</div>
+
+<hr>
+
+<p>Goal: {{need?.maxGoal}}</p>
+<p>Current: {{need?.current}}</p>
+<p>This goal is <strong>{{((need?.current ?? 0)*100) / (need?.maxGoal ?? 0)}}%</strong> complete!</p> \ No newline at end of file
diff --git a/ufund-ui/src/app/components/need-page/need-page.component.ts b/ufund-ui/src/app/components/need-page/need-page.component.ts
index 15c1e87..597d0e0 100644
--- a/ufund-ui/src/app/components/need-page/need-page.component.ts
+++ b/ufund-ui/src/app/components/need-page/need-page.component.ts
@@ -1,7 +1,8 @@
import {Component, Input} from '@angular/core';
-import {Need} from '../../models/Need';
+import {GoalType, Need} from '../../models/Need';
import {ActivatedRoute} from "@angular/router";
import {CupboardService} from "../../services/cupboard.service";
+import { NgFor } from '@angular/common';
@Component({
selector: 'app-need-page',
@@ -15,10 +16,16 @@ export class NeedPageComponent {
private cupboardService: CupboardService,
) {}
+ public GoalType = GoalType;
+
@Input() need?: Need;
ngOnInit(): void {
const id = Number(this.route.snapshot.paramMap.get('id'));
this.cupboardService.getNeed(id).subscribe(n => this.need = n);
}
-}
+
+ back() {
+ window.history.back();
+ }
+} \ No newline at end of file
diff --git a/ufund-ui/src/app/models/Need.ts b/ufund-ui/src/app/models/Need.ts
index c0425ec..9e97fd4 100644
--- a/ufund-ui/src/app/models/Need.ts
+++ b/ufund-ui/src/app/models/Need.ts
@@ -1,7 +1,7 @@
export interface Need {
name: string,
id: number,
- filterAttributes: String[],
+ filterAttributes: string[],
type: GoalType;
maxGoal: number;
current: number;
diff --git a/ufund-ui/src/app/models/User.ts b/ufund-ui/src/app/models/User.ts
index 46fe4a1..b640e04 100644
--- a/ufund-ui/src/app/models/User.ts
+++ b/ufund-ui/src/app/models/User.ts
@@ -1,6 +1,12 @@
import {Need} from './Need';
+export enum userType {
+ HELPER,
+ MANAGER
+}
+
export interface User {
- username: string;
- cupboard: Need[];
+ username: string;
+ basket: Need[];
+ type: userType
}
diff --git a/ufund-ui/src/app/services/cupboard.service.ts b/ufund-ui/src/app/services/cupboard.service.ts
index c123841..9e14106 100644
--- a/ufund-ui/src/app/services/cupboard.service.ts
+++ b/ufund-ui/src/app/services/cupboard.service.ts
@@ -18,8 +18,7 @@ export class CupboardService {
) {}
createNeed(need: Need): Observable<boolean> {
- return this.http.post<boolean>(
- this.url, need, this.httpOptions)
+ return this.http.post<boolean>(this.url, need, this.httpOptions)
}
getNeeds(): Observable<Need[]> {
@@ -39,6 +38,6 @@ export class CupboardService {
}
deleteNeed(id: number): Observable<boolean> {
- return this.http.put<boolean>(`${this.url}/${id}`, this.httpOptions)
+ return this.http.delete<boolean>(`${this.url}/${id}`, this.httpOptions)
}
}
diff --git a/ufund-ui/src/app/services/users.service.ts b/ufund-ui/src/app/services/users.service.ts
index 571c004..c570ccf 100644
--- a/ufund-ui/src/app/services/users.service.ts
+++ b/ufund-ui/src/app/services/users.service.ts
@@ -1,6 +1,6 @@
import { Injectable } from '@angular/core';
import {HttpClient, HttpHeaders} from '@angular/common/http';
-import {Observable} from 'rxjs';
+import {BehaviorSubject, firstValueFrom, Observable} from 'rxjs';
import {User} from '../models/User';
@Injectable({
@@ -8,22 +8,35 @@ import {User} from '../models/User';
})
export class UsersService {
- private currentUserID? : number
+ private currentUser : BehaviorSubject<User | null> = new BehaviorSubject<User | null>(null);
+ private apiKey: string = "";
private url = "http://localhost:8080/users"
+ private authUrl = "http://localhost:8080/auth"
private httpOptions = {
- headers: new HttpHeaders({'Content-Type': 'application/json'})
+ headers: new HttpHeaders({
+ 'Content-Type': 'application/json',
+ "jelly-api-key": this.apiKey
+ })
+ };
+ private httpOptions2 = {
+ headers: new HttpHeaders({
+ 'Content-Type': 'application/json',
+ "jelly-api-key": this.apiKey
+ }),
+ responseType: "text" as "json" // don't ask me how or why this works, bc i have no clue...
+ // see the relevant angular bug report https://github.com/angular/angular/issues/18586
};
constructor(
private http: HttpClient
) {}
- createUser(data: User): Observable<User> {
- return this.http.post<User>(this.url, data, this.httpOptions)
+ async createUser(username:string, password:string) {
+ await firstValueFrom(this.http.post<User>(this.url, {username: username, password: password}, this.httpOptions))
}
- getUser(id: number): Observable<User> {
+ getUser(id: string): Observable<User> {
return this.http.get<User>(`${this.url}/${id}`, this.httpOptions)
}
@@ -35,7 +48,26 @@ export class UsersService {
return this.http.delete<boolean>(`${this.url}/${id}`, this.httpOptions)
}
- getCurrentUser(): Observable<User> | undefined {
- return this.currentUserID ? this.getUser(this.currentUserID) : undefined
+ getCurrentUserSubject() {
+ return this.currentUser;
+ }
+
+ getCurrentUser() {
+ return this.currentUser.getValue()
+ }
+
+ async login(username: string, password: string) {
+ let res = this.http.post<string>(this.authUrl, {username: username, password: password}, this.httpOptions2);
+ this.apiKey = await firstValueFrom(res);
+ console.log("apikey: "+this.apiKey)
+ let res2 = this.http.get<User>(`${this.url}/${username}`, {
+ headers: new HttpHeaders({
+ 'Content-Type': 'application/json',
+ "jelly-api-key": this.apiKey
+ })
+ })
+ let currentU = await firstValueFrom(res2);
+ this.currentUser.next(currentU);
+ // this.currentUser.subscribe(r => console.log("currentUser: "+r.username))
}
}