diff options
Diffstat (limited to '')
35 files changed, 733 insertions, 545 deletions
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 index b46d4ee..aa99a90 100644 --- 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 @@ -2,6 +2,8 @@ package com.ufund.api.ufundapi.controller;  import java.io.IOException;  import java.util.Map; +import java.util.logging.Level; +import java.util.logging.Logger;  import org.springframework.http.HttpStatus;  import org.springframework.http.ResponseEntity; @@ -17,6 +19,7 @@ import com.ufund.api.ufundapi.service.AuthService;  @RestController  @RequestMapping("auth")  public class AuthController { +    private static final Logger LOG = Logger.getLogger(AuthController.class.getName());      private final AuthService authService;      public AuthController(AuthService authService) { @@ -32,14 +35,17 @@ public class AuthController {       */      @PostMapping("")      public ResponseEntity<String> login(@RequestBody Map<String, String> params) { +        LOG.log(Level.INFO, "POST /auth body={0}", 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) { +        } catch (IllegalAccessException ex) { +            LOG.log(Level.WARNING, ex.getLocalizedMessage());              return new ResponseEntity<>(HttpStatus.UNAUTHORIZED);          } catch (IOException ex) { +            LOG.log(Level.SEVERE, ex.getLocalizedMessage());              return new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR);          }      } @@ -52,10 +58,12 @@ public class AuthController {       */      @DeleteMapping("")      public ResponseEntity<Object> logout(@RequestHeader("jelly-api-key") String key) { +        LOG.log(Level.INFO, "DELETE /auth key={0}", key);          try {              authService.logout(key);              return new ResponseEntity<>(HttpStatus.OK); -        } catch (IOException e) { +        } catch (IOException ex) { +            LOG.log(Level.WARNING, ex.getLocalizedMessage());              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 36ae341..55ee457 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 @@ -5,6 +5,7 @@ import java.util.Map;  import java.util.logging.Level;  import java.util.logging.Logger; +import com.ufund.api.ufundapi.service.AuthService;  import org.springframework.http.HttpStatus;  import org.springframework.http.ResponseEntity;  import org.springframework.web.bind.annotation.DeleteMapping; @@ -13,6 +14,7 @@ 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.RequestParam;  import org.springframework.web.bind.annotation.RestController; @@ -27,14 +29,16 @@ import com.ufund.api.ufundapi.service.CupboardService;  public class CupboardController {      private static final Logger LOG = Logger.getLogger(CupboardController.class.getName());      private final CupboardService cupboardService; +    private final AuthService authService;      /**       * Create a cupboard controller to receive REST signals       *       * @param cupboardService The Data Access Object       */ -    public CupboardController(CupboardService cupboardService) { +    public CupboardController(CupboardService cupboardService, AuthService authService) {          this.cupboardService = cupboardService; +        this.authService = authService;      }      /** @@ -47,20 +51,28 @@ public class CupboardController {       *         INTERNAL_SERVER_ERROR otherwise       */      @PostMapping("") -    public ResponseEntity<Need> createNeed(@RequestBody Map<String, Object> params) { -        System.out.println(params); +    public ResponseEntity<Need> createNeed(@RequestBody Map<String, Object> params, @RequestHeader("jelly-api-key") String key) { +        LOG.log(Level.INFO, "POST /cupboard body={0}", params); +          String name = (String) params.get("name"); -        double maxGoal = (double) params.get("maxGoal"); +        double maxGoal = ((Number) params.get("maxGoal")).doubleValue();          Need.GoalType goalType = GoalType.valueOf((String) params.get("type"));          try { +            authService.keyHasAccessToCupboard(key);              Need need = cupboardService.createNeed(name, maxGoal, goalType);              return new ResponseEntity<>(need, HttpStatus.OK);          } catch (DuplicateKeyException ex) { +            LOG.log(Level.WARNING, ex.getLocalizedMessage());              return new ResponseEntity<>(HttpStatus.CONFLICT);          } catch (IllegalArgumentException ex) { +            LOG.log(Level.WARNING, ex.getLocalizedMessage());              return new ResponseEntity<>(HttpStatus.BAD_REQUEST); +        } catch (IllegalAccessException ex) { +            LOG.log(Level.WARNING, ex.getLocalizedMessage()); +            return new ResponseEntity<>(HttpStatus.UNAUTHORIZED);          } catch (IOException ex) { +            LOG.log(Level.SEVERE, ex.getLocalizedMessage());              return new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR);          }      } @@ -75,7 +87,7 @@ public class CupboardController {       */      @GetMapping("")      public ResponseEntity<Need[]> getNeeds() { -        LOG.info("GET /needs"); +        LOG.info("GET /cupboard");          try {              Need[] needs = cupboardService.getNeeds(); @@ -87,19 +99,21 @@ public class CupboardController {      }       /** -     * Responds to the GET request for all {@linkplain Need need} whose name contains -     * the text in name -     *  -     * @param name The name parameter which contains the text used to find the {@link Need need} -     *  -     * @return ResponseEntity with array of {@link Need need} objects (may be empty) and -     * HTTP status of OK<br> -     * ResponseEntity with HTTP status of INTERNAL_SERVER_ERROR otherwise -     * <p> -     */ +      * Responds to the GET request for all {@linkplain Need need} whose name contains +      * the text in name +      * +      * @param name The name parameter which contains the text used to find the {@link Need need} +      * +      * @deprecated Searching should now be done client side in the future +      * +      * @return ResponseEntity with array of {@link Need need} objects (may be empty) and +      * HTTP status of OK<br> +      * ResponseEntity with HTTP status of INTERNAL_SERVER_ERROR otherwise +      * <p> +      */      @GetMapping("/")      public ResponseEntity<Need[]> searchNeeds(@RequestParam String name) { -        LOG.info("GET /need/?name="+name); +        LOG.info("GET /cupboard/?name="+name);          try {              Need[] needs = cupboardService.searchNeeds(name); @@ -120,7 +134,7 @@ public class CupboardController {       */      @GetMapping("/{id}")      public ResponseEntity<Need> getNeed(@PathVariable int id) { -        LOG.log(Level.INFO, "GET /need/{0}", id); +        LOG.log(Level.INFO, "GET /cupboard/{0}", id);          try {              Need need = cupboardService.getNeed(id); @@ -143,9 +157,10 @@ public class CupboardController {       * @return OK response and the need if it was successful, or INTERNAL_SERVER_ERROR if there was an issue       */      @PutMapping("/{id}") -    public ResponseEntity<Need> updateNeed(@RequestBody Need need, @PathVariable int id) { -        LOG.log(Level.INFO, "Updating need: " + need); +    public ResponseEntity<Need> updateNeed(@RequestBody Need need, @PathVariable int id, @RequestHeader("jelly-api-key") String key) { +        LOG.log(Level.INFO, "PUT /cupboard/{0} body={1}", of(id, need));          try { +            authService.keyHasAccessToCupboard(key);              Need updatedNeed = cupboardService.updateNeed(need, id);              if (updatedNeed != null) {                  return new ResponseEntity<>(need, HttpStatus.OK); @@ -153,10 +168,40 @@ public class CupboardController {                  return new ResponseEntity<>(HttpStatus.NOT_FOUND);              }          } catch (IllegalArgumentException ex) { -            ex.printStackTrace(); +            LOG.log(Level.WARNING, ex.getLocalizedMessage()); +            return new ResponseEntity<>(HttpStatus.BAD_REQUEST); +        } catch (IllegalAccessException ex) { +            LOG.log(Level.WARNING, ex.getLocalizedMessage()); +            return new ResponseEntity<>(HttpStatus.UNAUTHORIZED); +        } catch (IOException ex) { +            LOG.log(Level.SEVERE, ex.getLocalizedMessage()); +            return new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR); +        } +    } + +    /** +     * Checks out a need by checkoutAmount +     *  +     * @param data JSON object with paramters needID and amount +     * @param key  Key used to authenticate user +     * @return OK if successful, other statuses if failure +     */ +    @PutMapping("/checkout") +    public ResponseEntity<Object> checkoutNeeds(@RequestBody Map<String, Integer> data, @RequestHeader("jelly-api-key") String key) { +        int needID = data.get("needID"); +        int checkoutAmount = data.get("amount"); +        LOG.log(Level.INFO, "PUT /need/checkout body={0}", data); +        try { +            cupboardService.checkoutNeed(needID, checkoutAmount, key); +            return new ResponseEntity<>(HttpStatus.OK); +        } catch (IllegalArgumentException ex) { +            LOG.log(Level.WARNING, ex.getLocalizedMessage());              return new ResponseEntity<>(HttpStatus.BAD_REQUEST); +        } catch (IllegalAccessException ex) { +            LOG.log(Level.WARNING, ex.getLocalizedMessage()); +            return new ResponseEntity<>(HttpStatus.UNAUTHORIZED);          } catch (IOException ex) { -            ex.printStackTrace(); +            LOG.log(Level.SEVERE, ex.getLocalizedMessage());              return new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR);          }      } @@ -168,17 +213,27 @@ public class CupboardController {       * @return OK if the need was deleted, NOT_FOUND if the need was not found, or INTERNAL_SERVER_ERROR if an error occurred      */      @DeleteMapping("/{id}") -    public ResponseEntity<Need> deleteNeed(@PathVariable int id) { +    public ResponseEntity<Need> deleteNeed(@PathVariable int id, @RequestHeader("jelly-api-key") String key) { +        LOG.log(Level.INFO, "DELETE /cupboard/{0}", id);          try { +            authService.keyHasAccessToCupboard(key);              Need need = cupboardService.getNeed(id);              if (cupboardService.deleteNeed(id)) {                  return new ResponseEntity<>(need, HttpStatus.OK);              } else {                  return new ResponseEntity<>(HttpStatus.NOT_FOUND); -            }  -        } catch (IOException e) { +            } +        } catch (IllegalAccessException ex) { +            LOG.log(Level.WARNING, ex.getLocalizedMessage()); +            return new ResponseEntity<>(HttpStatus.UNAUTHORIZED); +        } catch (IOException ex) { +            LOG.log(Level.SEVERE, ex.getLocalizedMessage());              return new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR);          }      } +    private Object[] of(Object ...params) { +        return params; +    } +  } 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 dfaad3a..33d2e4f 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,7 +1,6 @@  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; @@ -43,6 +42,7 @@ public class UserController {       */      @PostMapping("")      public ResponseEntity<User> createUser(@RequestBody Map<String, String> params) { +        LOG.log(Level.INFO, "POST /users body={0}", params);          String username = params.get("username");          String password = params.get("password"); @@ -54,8 +54,10 @@ public class UserController {                  return new ResponseEntity<>(HttpStatus.CONFLICT);              }          } catch (DuplicateKeyException ex) { +            LOG.log(Level.WARNING, ex.getLocalizedMessage());              return new ResponseEntity<>(HttpStatus.CONFLICT);          } catch (IOException ex) { +            LOG.log(Level.SEVERE, ex.getLocalizedMessage());              return new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR);          }      } @@ -72,10 +74,10 @@ public class UserController {       */      @GetMapping("/{username}")      public ResponseEntity<User> getUser(@PathVariable String username, @RequestHeader("jelly-api-key") String key) { -        LOG.log(Level.INFO, "GET /user/{0}", username); +        LOG.log(Level.INFO, "GET /user/{0} key={1}", of(username, key));          try { -            authService.authenticate(username, key); +            authService.keyHasAccessToUser(username, key);              User user = userService.getUser(username);              if (user != null) {                  return new ResponseEntity<>(user.withoutPasswordHash(), HttpStatus.OK); @@ -83,9 +85,10 @@ public class UserController {                  return new ResponseEntity<>(HttpStatus.NOT_FOUND);              }          } catch (IllegalAccessException ex) { +            LOG.log(Level.WARNING, ex.getLocalizedMessage());              return new ResponseEntity<>(HttpStatus.UNAUTHORIZED); -        } catch (IOException e) { -            LOG.log(Level.SEVERE, e.getLocalizedMessage()); +        } catch (IOException ex) { +            LOG.log(Level.SEVERE, ex.getLocalizedMessage());              return new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR);          } @@ -102,23 +105,25 @@ public class UserController {       */      @PutMapping("/{username}")      public ResponseEntity<User> updateUser(@RequestBody User user, @PathVariable String username, @RequestHeader("jelly-api-key") String key) { -        LOG.log(Level.INFO,"PUT: " + user + " " + username + " " + key.toString()); +        LOG.log(Level.INFO,"PUT /users/{0} body={1} key={2}", of(username, user, key));          try { -            //authService.authenticate(username, key); +            authService.keyHasAccessToUser(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) { +        } catch (IllegalArgumentException ex) { +            LOG.log(Level.WARNING, ex.getLocalizedMessage());              return new ResponseEntity<>(HttpStatus.BAD_REQUEST); -        } catch (IOException e) { +        } catch (IllegalAccessException ex) { +            LOG.log(Level.WARNING, ex.getLocalizedMessage()); +            return new ResponseEntity<>(HttpStatus.UNAUTHORIZED); +        } catch (IOException ex) { +            LOG.log(Level.SEVERE, ex.getLocalizedMessage());              return new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR); -        }  -        // catch (IllegalAccessException e) { -        //     return new ResponseEntity<>(HttpStatus.UNAUTHORIZED); -        // } +        }      }      /** @@ -131,19 +136,26 @@ public class UserController {       */      @DeleteMapping("/{username}")      public ResponseEntity<Boolean> deleteUser(@PathVariable String username, @RequestHeader("jelly-api-key") String key) { +        LOG.log(Level.INFO, "DELETE /users/{0} id={1}", of(username, key));          try { -            authService.authenticate(username, key); +            authService.keyHasAccessToUser(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) { +        } catch (IllegalAccessException ex) { +            LOG.log(Level.WARNING, ex.getLocalizedMessage());              return new ResponseEntity<>(HttpStatus.UNAUTHORIZED); +        } catch (IOException ex) { +            LOG.log(Level.SEVERE, ex.getLocalizedMessage()); +            return new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR);          }      } +    private Object[] of(Object ...params) { +        return params; +    } +  } 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 c0e9214..22e86e3 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 @@ -17,14 +17,14 @@ public class Need {      @JsonProperty("current") private double current;      /** -     * Create a new need +     * Create a new need, used by the controller       *       * @param name The name of the need       * @param id The unique ID of the need       * @param maxGoal The maximum goal for this need       * @param type The type of need (monetary, physical)       */ -    public Need(@JsonProperty("name") String name, @JsonProperty("id") int id, @JsonProperty("maxGoal") double maxGoal, GoalType type) { +    public Need(@JsonProperty("name") String name, @JsonProperty("id") int id, @JsonProperty("maxGoal") double maxGoal, @JsonProperty("type") GoalType type) {          this.id = id;          this.name = name;          this.maxGoal = maxGoal; @@ -86,6 +86,10 @@ public class Need {          this.current = current;      } +    public void incrementCurrent(double incrementAmount) { +        this.current += incrementAmount; +    } +      public void setFilterAttributes(String[] filterAttributes) {          this.filterAttributes = filterAttributes;      } 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 6de1a8a..58b62df 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 @@ -12,18 +12,23 @@ public class User {          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; +    @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 username   The name of the user -     * @param basket A basket to copy from +     * @param username The name of the user +     * @param basket   A basket to copy from       */ -    public User(@JsonProperty("username") String username, @JsonProperty("passwordHash") int passwordHash, @JsonProperty("basket") List<Integer> basket, @JsonProperty("type") UserType userType) { +    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; @@ -35,30 +40,46 @@ public class User {                  username,                  password.hashCode(),                  new ArrayList<>(), -                UserType.HELPER -        ); +                UserType.HELPER);      }      public String getUsername() {          return username;      } +    /** +     * Verifies if the provided password's hash is the same as the user's actual +     * hash +     *  +     * @param password The password to check if valid +     * @return True or false depending on if it's equal +     */      public boolean verifyPassword(String password) {          return password.hashCode() == passwordHash;      } +    /** +     * Adds a need's ID to a user's basket +     *  +     * @param need The need to add +     */      public void addToBasket(Need need) {          basket.add(need.getId());      } -    public Integer[] getNeeds() { +    public Integer[] getBasket() {          return basket.toArray(Integer[]::new);      } -    public boolean removeBasketNeed(Integer needID) { -        return basket.remove(needID); +    public void removeBasketNeed(Integer needID) { +        basket.remove(needID);      } +    /** +     * Returns a user without a password hash for security purposes +     *  +     * @return new User with empty password hash +     */      public User withoutPasswordHash() {          return new User(this.username, 0, this.basket, this.type);      } @@ -71,6 +92,7 @@ public class User {          this.passwordHash = other.passwordHash;      } +    @Override      public String toString() {          return this.username + "; basket: " + this.basket + "; type:" + this.type + "; hash: " + this.passwordHash;      } 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 index 1c11a28..78dccec 100644 --- 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 @@ -1,10 +1,10 @@  package com.ufund.api.ufundapi.model; -import com.fasterxml.jackson.annotation.JsonProperty; -  import java.time.LocalDateTime;  import java.util.UUID; +import com.fasterxml.jackson.annotation.JsonProperty; +  public class UserAuth {      @JsonProperty("key") String key;      @JsonProperty("username") String username; @@ -12,12 +12,13 @@ public class UserAuth {      public UserAuth(@JsonProperty("key") String key, @JsonProperty("username") String username, @JsonProperty("expiration") LocalDateTime expiration) {          this.key = key; -        this.expiration = expiration;          this.username = username; +        this.expiration = expiration;      }      /**       * Generate a new user authentication profile +     *        * @param username the username the key will belong to       * @return The new user authentication profile       */ 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 521acae..7efda83 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 @@ -1,15 +1,16 @@  package com.ufund.api.ufundapi.persistence; -import com.fasterxml.jackson.databind.ObjectMapper; -import com.ufund.api.ufundapi.model.Need; -import org.springframework.beans.factory.annotation.Value; -import org.springframework.stereotype.Component; -  import java.io.File;  import java.io.IOException;  import java.util.Map;  import java.util.TreeMap; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Component; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.ufund.api.ufundapi.model.Need; +  @Component  public class CupboardFileDAO implements CupboardDAO { @@ -52,31 +53,20 @@ public class CupboardFileDAO implements CupboardDAO {      }      /** -     * Return an array of the needs -     * -     * @return An array of all the needs -     */ -    private Need[] getNeedsArray() { -        return needs.values().toArray(Need[]::new); -    } - -    /**       * Saves the needs to json       * -     * @return True if the save was successful, false otherwise       * @throws IOException If there was an IO issue saving the file       */ -    private boolean save() throws IOException { -        Need[] needArray = getNeedsArray(); +    private void save() throws IOException { +        Need[] needArray = needs.values().toArray(Need[]::new);          objectMapper.writeValue(new File(filename), needArray); -        return true;      }      @Override      public Need[] getNeeds() {          synchronized (needs) { -            return getNeedsArray(); +            return needs.values().toArray(Need[]::new);          }      } @@ -117,7 +107,8 @@ public class CupboardFileDAO implements CupboardDAO {          synchronized (needs) {              if (needs.containsKey(id)) {                  needs.remove(id); -                return save(); +                save(); +                return true;              } else {                  return false;              } 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 index 1fc1e92..24a426b 100644 --- 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 @@ -1,15 +1,17 @@  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.time.LocalDateTime;  import java.util.HashMap;  import java.util.Map; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Component; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.ufund.api.ufundapi.model.UserAuth; +  @Component  public class UserAuthFIleDAO implements UserAuthDAO { @@ -35,7 +37,9 @@ public class UserAuthFIleDAO implements UserAuthDAO {          UserAuth[] userAuthKeysArray = objectMapper.readValue(new File(filename), UserAuth[].class);          for (UserAuth userAuth : userAuthKeysArray) { -            userAuthMap.put(userAuth.getKey(), userAuth); +            if (userAuth.getExpiration().isAfter(LocalDateTime.now())) { +                userAuthMap.put(userAuth.getKey(), userAuth); +            }          }      } 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 6e900aa..ec94da8 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,9 +2,9 @@ package com.ufund.api.ufundapi.persistence;  import java.io.File;  import java.io.IOException; -import java.util.ArrayList;  import java.util.HashMap;  import java.util.Map; +import java.util.Objects;  import org.springframework.beans.factory.annotation.Value;  import org.springframework.stereotype.Component; @@ -44,12 +44,10 @@ public class UserFileDAO implements UserDAO {      /**       * Saves the needs to json       * -     * @return True if the save was successful, false otherwise       * @throws IOException If there was an IO issue saving the file       */ -    private boolean save() throws IOException { +    private void save() throws IOException {          objectMapper.writeValue(new File(filename), users.values()); -        return true;      }      @Override @@ -82,17 +80,8 @@ public class UserFileDAO implements UserDAO {      public User updateUser(User user) throws IOException {          synchronized (users) {              if (users.containsKey(user.getUsername())) { -                // var old = users.put(user.getUsername(), user); -                // user.copyPassword(old); -                if (user.getNeeds() == null || user.getType() == null) { -                    User oldData = users.get(user.getUsername()); -                    User crutch = new User(oldData.getUsername(), 0, new ArrayList<Integer>(), oldData.getType()); -                    crutch.copyPassword(oldData); -                    users.put(user.getUsername(), crutch); -                } else { -                    var old = users.put(user.getUsername(), user); -                    user.copyPassword(old); -                } +                var old = users.put(user.getUsername(), user); +                user.copyPassword(Objects.requireNonNull(old));                  save();                  return user;              } else { @@ -106,7 +95,8 @@ public class UserFileDAO implements UserDAO {          synchronized (users) {              if (users.containsKey(username)) {                  users.remove(username); -                return save(); +                save(); +                return true;              } 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 index 87a16a6..cdce80d 100644 --- 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 @@ -1,11 +1,12 @@  package com.ufund.api.ufundapi.service; +import java.io.IOException; + +import org.springframework.stereotype.Component; +  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 { @@ -24,18 +25,51 @@ public class AuthService {       * @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. +     * @throws IOException Thrown on a file writing issue +     */ +    public void keyHasAccessToUser(String targetUsername, String key) throws IllegalAccessException, IOException { +        var userAuth = userAuthDAO.getUserAuth(key); +        if (userAuth == null) { +            throw new IllegalAccessException("Invalid authentication key"); +        } + +       var username = userAuth.getUsername(); +       var userType = userService.getUser(username).getType(); +       if (!username.equals(targetUsername) && userType != User.UserType.MANAGER) { +           throw new IllegalAccessException("Provided key does not grant access to perform the requested operation"); +       } +    } + +    /** +     * Check if the provided key is valid +     * @param key The api key obtained by the client from logging in. +     * @throws IllegalAccessException Thrown if access was denied to the user. +     * @throws IOException Thrown on a file writing issue +     */ +    public void keyIsValid(String key) throws IOException, IllegalAccessException { +        var userAuth = userAuthDAO.getUserAuth(key); +        if (userAuth == null) { +            throw new IllegalAccessException("Invalid authentication key"); +        } +    } + +    /** +     * Check if the provided key has access to edit the cupboard +     * @param key The api key obtained by the client from logging in. +     * @throws IllegalAccessException Thrown if access was denied to the user. +     * @throws IOException Thrown on a file writing issue       */ -    public void authenticate(String targetUsername, String key) throws IllegalAccessException, IOException { +    public void keyHasAccessToCupboard(String key) throws IOException, IllegalAccessException {          var userAuth = userAuthDAO.getUserAuth(key);          if (userAuth == null) { -            throw new IllegalAccessException("Unauthenticated"); +            throw new IllegalAccessException("Invalid authentication key"); +        } + +        var username = userAuth.getUsername(); +        var userType = userService.getUser(username).getType(); +        if (userType != User.UserType.MANAGER) { +            throw new IllegalAccessException("Provided key does not grant access to perform the requested operation");          } -// -//        var username = userAuth.getUsername(); -//        var userType = userService.getUser(username).getType(); -//        if (!username.equals(targetUsername) && userType != User.UserType.MANAGER) { -//            throw new IllegalAccessException("Unauthorized"); -//        }      }      /** @@ -50,7 +84,7 @@ public class AuthService {      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"); +            throw new IllegalAccessException("Incorrect username or password");          }          var userAuth = UserAuth.generate(username);          userAuthDAO.addUserAuth(userAuth); 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 index 2398745..aaa8cb8 100644 --- 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 @@ -3,6 +3,7 @@ package com.ufund.api.ufundapi.service;  import java.io.IOException;  import java.util.Arrays; +import org.springframework.context.annotation.Lazy;  import org.springframework.stereotype.Component;  import com.ufund.api.ufundapi.DuplicateKeyException; @@ -13,8 +14,10 @@ import com.ufund.api.ufundapi.persistence.CupboardDAO;  public class CupboardService {      private final CupboardDAO cupboardDAO; +    final AuthService authService; -    public CupboardService(CupboardDAO cupboardDAO) { +    public CupboardService(@Lazy AuthService authService, CupboardDAO cupboardDAO) { +        this.authService = authService;          this.cupboardDAO = cupboardDAO;      } @@ -97,6 +100,23 @@ public class CupboardService {      }      /** +     * Checks out a need with the desired amount +     *  +     * @param id The ID of the need to update +     * @param checkoutAmount The amount to update the need by +     * @throws IOException If there is an error reading the file +     * @throws IllegalAccessException If the user has insufficient permission +    */ +    public void checkoutNeed(int id, double checkoutAmount, String key) throws IOException, IllegalAccessException { +        if (checkoutAmount <= 0) { +            throw new IllegalArgumentException("Amount must be greater than 0"); +        } +        authService.keyIsValid(key); +        Need need = cupboardDAO.getNeed(id); +        need.incrementCurrent(checkoutAmount); +    } + +    /**       * Delete a need from the cupboard       *       * @param id the ID of the need @@ -104,6 +124,7 @@ public class CupboardService {       * @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 index caf9f4c..6e27f50 100644 --- 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 @@ -12,7 +12,7 @@ import com.ufund.api.ufundapi.persistence.UserDAO;  public class UserService {      private final UserDAO userDAO; -    private final CupboardService cupboardService; +    final CupboardService cupboardService;      public UserService(UserDAO userDao, CupboardService cupboardService) {          this.userDAO = userDao; @@ -44,7 +44,10 @@ public class UserService {       */      public User getUser(String username) throws IOException {          User user = userDAO.getUser(username); -        for (int needId : user.getNeeds()) { +        if (user == null) { +            return null; +        } +        for (int needId : user.getBasket()) {              if (cupboardService.getNeed(needId) == null) {                  user.removeBasketNeed(needId);              } @@ -55,7 +58,7 @@ public class UserService {      /**       * Updates a user       * -     * @param user The ID of the user to update +     * @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 @@ -77,5 +80,5 @@ public class UserService {      public boolean deleteUser(String username) throws IOException {          return userDAO.deleteUser(username);      } -     +  } 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 index 3d4637d..f4b5980 100644 --- 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 @@ -8,7 +8,6 @@ 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; @@ -26,7 +25,7 @@ public class AuthControllerTest {      private Map<String, String> authMap;      @BeforeEach -    private void setupAuthController() { +    public void setupAuthController() {          mockAuthService = mock(AuthService.class);          authController = new AuthController(mockAuthService); @@ -76,7 +75,7 @@ public class AuthControllerTest {      }      @Test -    public void testLogout() throws IllegalAccessException, IOException { +    public void testLogout() {          // Setup          String key = "123"; @@ -88,7 +87,7 @@ public class AuthControllerTest {      }      @Test -    public void testLogoutIOException() throws IllegalAccessException, IOException { +    public void testLogoutIOException() throws IOException {          // Setup          String key = "123"; 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 6ef6710..89697bf 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 @@ -7,10 +7,11 @@ import static java.util.Map.entry;  import static org.junit.jupiter.api.Assertions.assertArrayEquals;  import static org.junit.jupiter.api.Assertions.assertEquals;  import static org.junit.jupiter.api.Assertions.assertNull; +import static org.mockito.Mockito.*; + +import com.ufund.api.ufundapi.service.AuthService;  import org.junit.jupiter.api.BeforeEach;  import org.junit.jupiter.api.Test; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when;  import org.springframework.http.HttpStatus;  import com.ufund.api.ufundapi.DuplicateKeyException; @@ -21,11 +22,17 @@ import com.ufund.api.ufundapi.service.CupboardService;  public class CupboardControllerTest {      private CupboardController cupboardController;      private CupboardService mockCupboardService; +    private final String key = "dummyKey";      @BeforeEach      public void setupCupboardDAO() { +        AuthService mockAuthService = mock(AuthService.class);          mockCupboardService = mock(CupboardService.class); -        cupboardController = new CupboardController(mockCupboardService); +        cupboardController = new CupboardController(mockCupboardService, mockAuthService); + +        try { +            doThrow().when(mockAuthService).keyHasAccessToCupboard(key); +        } catch (Exception ignored) {}      }      @Test @@ -43,7 +50,7 @@ public class CupboardControllerTest {                  entry("type", "MONETARY")          ); -        var res = cupboardController.createNeed(needMap); +        var res = cupboardController.createNeed(needMap, key);          assertEquals(HttpStatus.OK, res.getStatusCode());          assertEquals(need, res.getBody()); @@ -58,7 +65,7 @@ public class CupboardControllerTest {                  entry("maxGoal", -100.0),                  entry("type", "MONETARY")); -        var res = cupboardController.createNeed(needMap); +        var res = cupboardController.createNeed(needMap, key);          assertEquals(HttpStatus.BAD_REQUEST, res.getStatusCode());      } @@ -72,7 +79,7 @@ public class CupboardControllerTest {                  entry("maxGoal", 100.0),                  entry("type", "MONETARY")); -        var res = cupboardController.createNeed(needMap); +        var res = cupboardController.createNeed(needMap, key);          assertEquals(HttpStatus.INTERNAL_SERVER_ERROR, res.getStatusCode());      } @@ -174,7 +181,7 @@ public class CupboardControllerTest {          var need = new Need("Name", 1, 100, Need.GoalType.MONETARY);          when(mockCupboardService.updateNeed(need, 1)).thenReturn(need); -        var res = cupboardController.updateNeed(need, 1); +        var res = cupboardController.updateNeed(need, 1, key);          assertEquals(HttpStatus.OK, res.getStatusCode());          assertEquals(need, res.getBody()); @@ -185,7 +192,7 @@ public class CupboardControllerTest {          var need = new Need("Name", 1, 100, Need.GoalType.MONETARY);          when(mockCupboardService.updateNeed(need, 1)).thenThrow(new IOException()); -        var res = cupboardController.updateNeed(need, 1); +        var res = cupboardController.updateNeed(need, 1, key);          assertEquals(HttpStatus.INTERNAL_SERVER_ERROR, res.getStatusCode());      } @@ -196,7 +203,7 @@ public class CupboardControllerTest {          when(mockCupboardService.getNeed(1)).thenReturn(need);          when(mockCupboardService.deleteNeed(1)).thenReturn(true); -        var res = cupboardController.deleteNeed(1); +        var res = cupboardController.deleteNeed(1, key);          assertEquals(HttpStatus.OK, res.getStatusCode());      } @@ -206,7 +213,7 @@ public class CupboardControllerTest {          when(mockCupboardService.getNeed(1)).thenReturn(null);          when(mockCupboardService.deleteNeed(1)).thenReturn(false); -        var res = cupboardController.deleteNeed(1); +        var res = cupboardController.deleteNeed(1, key);          assertEquals(HttpStatus.NOT_FOUND, res.getStatusCode());      } @@ -217,7 +224,7 @@ public class CupboardControllerTest {          when(mockCupboardService.getNeed(1)).thenReturn(need);          when(mockCupboardService.deleteNeed(1)).thenThrow(new IOException()); -        var res = cupboardController.deleteNeed(1); +        var res = cupboardController.deleteNeed(1, key);          assertEquals(HttpStatus.INTERNAL_SERVER_ERROR, res.getStatusCode());      } 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 5542f49..06fb6cd 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 @@ -82,7 +82,7 @@ public class UserControllerTest {          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); +        doThrow(new IllegalAccessException()).when(mockAuthService).keyHasAccessToUser(username, key);          // Invoke          ResponseEntity<User> response = userController.getUser(username, key); @@ -237,14 +237,14 @@ public class UserControllerTest {          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); +        doThrow(new IllegalAccessException()).when(mockAuthService).keyHasAccessToUser(username, key);          // Invoke          ResponseEntity<User> response = userController.updateUser(user, username, key);          // Analyze -        assertEquals(HttpStatus.NOT_FOUND, response.getStatusCode()); +        assertEquals(HttpStatus.UNAUTHORIZED, response.getStatusCode());      }      @Test @@ -298,7 +298,7 @@ public class UserControllerTest {          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); +        doThrow(new IllegalAccessException()).when(mockAuthService).keyHasAccessToUser(username, key);          // Invoke          ResponseEntity<Boolean> response = userController.deleteUser(username, key); 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 55b7f07..517a7e2 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 @@ -59,7 +59,7 @@ public class UserTest {          user.addToBasket(need); -        Need getNeed = cupboardService.getNeed(user.getNeeds()[0]); +        Need getNeed = cupboardService.getNeed(user.getBasket()[0]);          assertEquals(needs[0], getNeed); @@ -80,7 +80,7 @@ public class UserTest {          user.removeBasketNeed(need.getId());          user.addToBasket(need2); -        Need getNeed = cupboardService.getNeed(user.getNeeds()[0]); +        Need getNeed = cupboardService.getNeed(user.getBasket()[0]);          assertEquals(need2, getNeed); 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 f786a8c..d83e825 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 @@ -4,6 +4,7 @@ import java.io.File;  import java.io.IOException;  import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse;  import static org.junit.jupiter.api.Assertions.assertNotEquals;  import static org.junit.jupiter.api.Assertions.assertNotNull;  import static org.junit.jupiter.api.Assertions.assertNull; @@ -20,44 +21,42 @@ import com.ufund.api.ufundapi.model.Need.GoalType;  @Tag("Persistence-tier")  public class CupboardFileDAOTest { -    private CupboardFileDAO cupboardFileDao; -    private Need[] testNeeds; -    private ObjectMapper mockObjectMapper; +	private CupboardFileDAO cupboardFileDao; +	private Need[] testNeeds;      @BeforeEach -    public void setupCupboardFileDao() throws IOException { -        mockObjectMapper = mock(ObjectMapper.class); -        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) +	public void setupCupboardFileDao() throws IOException { +        ObjectMapper mockObjectMapper = mock(ObjectMapper.class); +		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); -    } - -    @Test -    public void getNeedsTest() { -        Need[] needs = cupboardFileDao.getNeeds(); -        assertEquals(needs.length,testNeeds.length); +		// 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); +	} + +	@Test +	public void getNeedsTest() { +		Need[] needs = cupboardFileDao.getNeeds(); +		assertEquals(needs.length, testNeeds.length);  		assertEquals(needs[0].getName(), testNeeds[0].getName()); -    } +	} -    @Test -    public void getNeedTest() { +	@Test +	public void getNeedTest() {  		Need need1 = cupboardFileDao.getNeed(0); -	 +  		assertEquals(testNeeds[0], need1); -    } +	}  	@Test  	public void createNeedTest() throws IOException {  		Need newNeed = new Need("sea urchin hats", 3, 100, GoalType.PHYSICAL); -		  		Need actualNeed = cupboardFileDao.addNeed(newNeed); @@ -79,6 +78,15 @@ public class CupboardFileDAOTest {  	}  	@Test +	public void deleteNeedTestFail() throws IOException { +		Need undeletedNeed = cupboardFileDao.getNeed(0); +		assertNotNull(undeletedNeed); + +		boolean nullNeed = cupboardFileDao.deleteNeed(20); +		assertFalse(nullNeed); +	} + +	@Test  	public void updateNeedTest() throws IOException {  		Need[] needs = cupboardFileDao.getNeeds();  		Need unupdatedNeed = needs[needs.length - 1]; @@ -91,4 +99,16 @@ public class CupboardFileDAOTest {  		assertNotEquals(actualNeed, unupdatedNeed);  	} +	@Test +	public void updateNeedTestFail() throws IOException { +		Need[] needs = cupboardFileDao.getNeeds(); +		Need unupdatedNeed = needs[needs.length - 1]; +		assertNotNull(unupdatedNeed); + +		Need updatedNeed = new Need("sequin sea urchin hats", 20, 100, GoalType.PHYSICAL); + +		Need actualNeed = cupboardFileDao.updateNeed(updatedNeed); +		assertNull(actualNeed); +	} +  } 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 index f7db747..a4842c5 100644 --- 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 @@ -2,6 +2,7 @@ package com.ufund.api.ufundapi.persistence;  import java.io.File;  import java.io.IOException; +import java.time.LocalDateTime;  import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;  import static org.junit.jupiter.api.Assertions.assertEquals; @@ -18,22 +19,21 @@ import com.ufund.api.ufundapi.model.UserAuth;  public class UserAuthFileDAOTest {      private UserAuthFIleDAO userAuthFIleDAO; -    private ObjectMapper mockObjectMapper;      private UserAuth[] userAuths;      @BeforeEach      public void setupUserAuthFileDAO() throws IOException { -        mockObjectMapper = mock(ObjectMapper.class); +        ObjectMapper mockObjectMapper = mock(ObjectMapper.class);          userAuths = new UserAuth[]{ -        	new UserAuth("123", "Phil", null), -            new UserAuth("456", "Bob", null), -            new UserAuth("789", "Steve", null) +        	new UserAuth("123", "Phil", LocalDateTime.MAX), +            new UserAuth("456", "Bob", LocalDateTime.MAX), +            new UserAuth("789", "Steve", LocalDateTime.MAX)  		};          // 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)) +                .readValue(new File("doesnt_matter.txt"),UserAuth[].class))                  .thenReturn(userAuths);          userAuthFIleDAO = new UserAuthFIleDAO(mockObjectMapper, "doesnt_matter.txt");      } @@ -43,18 +43,18 @@ public class UserAuthFileDAOTest {          String key = "123";          UserAuth auth = userAuthFIleDAO.getUserAuth(key); -        assertEquals(auth, userAuths[0]); +        assertEquals(userAuths[0], auth);      }      @Test -    public void addUserAuthTest() throws IOException { +    public void addUserAuthTest() {          UserAuth auth = new UserAuth("999", "Fish", null);          assertDoesNotThrow(() -> userAuthFIleDAO.addUserAuth(auth));      }      @Test -    public void removeUserAuthTest() throws IOException { +    public void removeUserAuthTest() {          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 9361188..2ee0fc0 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 @@ -39,6 +39,24 @@ public class UserFileDAOTest {          userFileDAO = new UserFileDAO("doesnt_matter.txt",mockObjectMapper);      } +	@Test +	public void addUsersTest() throws IOException { +		User user = User.create("Name", "Pass"); + +		User addedUser = userFileDAO.addUser(user); + +		assertEquals(addedUser, user); +	} + +	@Test +	public void addUsersTestFail() throws IOException { +		User user = User.create("bob", "test"); + +		User existingUser = userFileDAO.addUser(user); + +		assertEquals(existingUser, testUsers[0]); +	} +      @Test      public void getUsersTest() {          User[] users = userFileDAO.getUsers(); 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 index 55cf7a9..4f58b12 100644 --- 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 @@ -11,7 +11,6 @@ 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; @@ -41,39 +40,39 @@ public class AuthServiceTest {      }      @Test -    public void testAuthenticate() throws IOException { +    public void testKeyIsValid() 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)); +        assertDoesNotThrow(() -> authService.keyHasAccessToUser(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 testKeyIsValidMismatchName() throws IOException { +        // Mock +        when(mockAuthDAO.getUserAuth(key)).thenReturn(new UserAuth(key, "EvilFish", null)); +        when(mockUserService.getUser("EvilFish")).thenReturn(user); + +        // Analyze +        assertThrows(IllegalAccessException.class, () -> authService.keyHasAccessToUser(username, key)); + +    }      @Test -    public void testAuthenticateMissingUserAuth() throws IOException { +    public void testKeyIsValidMissingUserAuth() throws IOException {          // Mock          when(mockAuthDAO.getUserAuth(key)).thenReturn(null);          // Analyze -        assertThrows(IllegalAccessException.class, () -> authService.authenticate(username, key)); +        assertThrows(IllegalAccessException.class, () -> authService.keyHasAccessToUser(username, key));      }      @Test -    public void testLogin() throws IOException, DuplicateKeyException, IllegalAccessException { +    public void testLogin() throws IOException {          // Mock          when(mockUserService.getUser(username)).thenReturn(user); @@ -83,7 +82,7 @@ public class AuthServiceTest {      }      @Test -    public void testLoginNullUser() throws IOException, DuplicateKeyException, IllegalAccessException { +    public void testLoginNullUser() throws IOException {          // Mock          when(mockUserService.getUser(username)).thenReturn(null); @@ -92,7 +91,7 @@ public class AuthServiceTest {      }      @Test -    public void testLoginMismatchPasswords() throws IOException, DuplicateKeyException, IllegalAccessException { +    public void testLoginMismatchPasswords() throws IOException {          // Mock          when(mockUserService.getUser(username)).thenReturn(User.create(username, "fries")); @@ -101,7 +100,7 @@ public class AuthServiceTest {      }      @Test -    public void testLogout() throws IOException, DuplicateKeyException, IllegalAccessException { +    public void testLogout() {          // 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 index 99ca23c..05ea2e8 100644 --- 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 @@ -27,7 +27,8 @@ public class CupboardServiceTest {      @BeforeEach      public void setupCupboardService() {          mockCupboardDAO = mock(CupboardDAO.class); -        cupboardService = new CupboardService(mockCupboardDAO); +        AuthService mockAuthService = mock(AuthService.class); +        cupboardService = new CupboardService(mockAuthService, mockCupboardDAO);      } @@ -52,7 +53,7 @@ public class CupboardServiceTest {      }      @Test -    public void testCreateNeedBadGoal() throws IOException, DuplicateKeyException { +    public void testCreateNeedBadGoal() throws IOException {          // Setup          String name = "Jellyfish";          double maxGoal = -100.00; @@ -67,13 +68,12 @@ public class CupboardServiceTest {          // Need response = cupboardService.createNeed(name, maxGoal, type);          // Analyze -        assertThrows(IllegalArgumentException.class, () -> { -            cupboardService.createNeed(name, maxGoal, type); -        }); +        assertThrows(IllegalArgumentException.class, () -> +                cupboardService.createNeed(name, maxGoal, type));      }      @Test -    public void testCreateNeedDuplicate() throws IOException, DuplicateKeyException { +    public void testCreateNeedDuplicate() throws IOException {          // Setup          String name = "Jellyfish";          double maxGoal = 100.00; @@ -89,13 +89,12 @@ public class CupboardServiceTest {          // Need response = cupboardService.createNeed(name, maxGoal, type);          // Analyze -        assertThrows(DuplicateKeyException.class, () -> { -            cupboardService.createNeed(name, maxGoal, type); -        }); +        assertThrows(DuplicateKeyException.class, () -> +                cupboardService.createNeed(name, maxGoal, type));      }      @Test -    public void testSearchNeeds() throws IOException, DuplicateKeyException { +    public void testSearchNeeds() throws IOException {          // Setup          String name = "Jellyfish";          double maxGoal = 100.00; @@ -115,7 +114,7 @@ public class CupboardServiceTest {      }      @Test -    public void testSearchNeedsFail() throws IOException, DuplicateKeyException { +    public void testSearchNeedsFail() throws IOException {          // Setup          String name = "Jellyfish";          double maxGoal = 100.00; @@ -134,7 +133,7 @@ public class CupboardServiceTest {      }      @Test -    public void testGetNeed() throws IOException, DuplicateKeyException { +    public void testGetNeed() throws IOException {          // Setup          String name = "Jellyfish";          double maxGoal = 100.00; @@ -153,7 +152,7 @@ public class CupboardServiceTest {      }      @Test -    public void testUpdateNeed() throws IOException, DuplicateKeyException { +    public void testUpdateNeed() throws IOException {          // Setup          String name = "Jellyfish";          double maxGoal = 100.00; @@ -173,7 +172,7 @@ public class CupboardServiceTest {      }      @Test -    public void testDeleteNeed() throws IOException, DuplicateKeyException { +    public void testDeleteNeed() throws IOException {          // Setup          String name = "Jellyfish";          double maxGoal = 100.00; 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 index e57c5a3..5adabf1 100644 --- 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 @@ -19,13 +19,12 @@ public class UserServiceTest {      private UserService userService;      private UserDAO mockUserDAO; -    private CupboardService mockCupboardService;      @BeforeEach      public void setupUserService() {          mockUserDAO = mock(UserDAO.class); -        mockCupboardService = mock(CupboardService.class); +        CupboardService mockCupboardService = mock(CupboardService.class);          userService = new UserService(mockUserDAO, mockCupboardService);      } @@ -47,7 +46,7 @@ public class UserServiceTest {      }      @Test -    public void testCreateUserDuplicate() throws IOException, DuplicateKeyException { +    public void testCreateUserDuplicate() throws IOException {          // Setup          String username = "Jelly";          String password = "Fish"; @@ -62,7 +61,7 @@ public class UserServiceTest {      }      @Test -    public void testGetUser() throws IOException, DuplicateKeyException { +    public void testGetUser() throws IOException {          // Setup          String username = "Jelly";          String password = "Fish"; @@ -76,7 +75,7 @@ public class UserServiceTest {      }      @Test -    public void testUpdateUser() throws IOException, DuplicateKeyException { +    public void testUpdateUser() throws IOException {          // Setup          String username = "Jelly";          String password = "Fish"; @@ -94,7 +93,7 @@ public class UserServiceTest {      }      @Test -    public void testUpdateUserDifferentUsernames() throws IOException, DuplicateKeyException { +    public void testUpdateUserDifferentUsernames() throws IOException {          // Setup          String username = "Jelly";          String password = "Fish"; @@ -112,7 +111,7 @@ public class UserServiceTest {      }      @Test -    public void testDeleteUser() throws IOException, DuplicateKeyException { +    public void testDeleteUser() throws IOException {          // Setup          String username = "Jelly";          String password = "Fish"; diff --git a/ufund-ui/src/app/app.component.ts b/ufund-ui/src/app/app.component.ts index 7dc8ffb..86717c4 100644 --- a/ufund-ui/src/app/app.component.ts +++ b/ufund-ui/src/app/app.component.ts @@ -1,7 +1,7 @@  import {Component, OnInit, Inject} from '@angular/core'; -import {UsersService} from './services/users.service';  import {BehaviorSubject} from 'rxjs';  import { DOCUMENT } from '@angular/common'; +import {AuthService} from './services/auth.service';  @Component({    selector: 'app-root', @@ -14,16 +14,16 @@ export class AppComponent implements OnInit {      currentUser$: BehaviorSubject<string> = new BehaviorSubject<string>("Logged out.");      constructor( -        private userService: UsersService, +        private authService: AuthService,          @Inject(DOCUMENT) private document: Document      ) {}      reloadPage() {          this.document.defaultView?.location.reload(); -      } +    }      ngOnInit() { -        this.userService.getCurrentUserSubject().subscribe(r => { +        this.authService.getCurrentUserSubject().subscribe(r => {              this.currentUser$?.next(r                  ? "Logged in as " + r.username                  : "Logged out." diff --git a/ufund-ui/src/app/components/cupboard/cupboard.component.ts b/ufund-ui/src/app/components/cupboard/cupboard.component.ts index 24b3e2d..a812baf 100644 --- a/ufund-ui/src/app/components/cupboard/cupboard.component.ts +++ b/ufund-ui/src/app/components/cupboard/cupboard.component.ts @@ -1,10 +1,10 @@  import { Component, OnInit, ViewChild } from '@angular/core';  import { CupboardService } from '../../services/cupboard.service'; -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'; +import {AuthService} from '../../services/auth.service';  @Component({      selector: 'app-cupboard', @@ -20,7 +20,10 @@ export class CupboardComponent implements OnInit {      needs: any;      @ViewChild("needList") needList?: NeedListComponent -    constructor(private cupboardService: CupboardService, private usersService: UsersService) { } +    constructor( +        private cupboardService: CupboardService, +        private authService: AuthService +    ) {}      ngOnInit(): void {          this.cupboardService.getNeeds().subscribe(n => this.needs = n); @@ -88,7 +91,7 @@ export class CupboardComponent implements OnInit {      }      isManager() { -        const type = this.usersService.getCurrentUser()?.type; +        const type = this.authService.getCurrentUser()?.type;          return type === ("MANAGER" as unknown as userType);      } @@ -105,7 +108,7 @@ export class CupboardComponent implements OnInit {          console.log("need:", need);          console.log(need.id, need, "need updated");          this.cupboardService.updateNeed(need.id, need) -            .pipe(catchError((ex, r) => { +            .pipe(catchError((ex, _) => {                  if (ex.status == 500) {                      this.statusText.next("Fields cannot be blank");                  } else if (ex.status == 400) { @@ -140,7 +143,7 @@ export class CupboardComponent implements OnInit {          console.log("need:", need);          console.log("form submitted. creating need: ", need);          this.cupboardService.createNeed(need) -            .pipe(catchError((ex, r) => { +            .pipe(catchError((ex, _) => {                  if (ex.status == 500) {                      this.statusText.next("Fields cannot be blank");                  } else if (ex.status == 400) { @@ -167,48 +170,3 @@ export class CupboardComponent implements OnInit {      }  } - -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', -    422: 'Unprocessable Entity', -    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.ts b/ufund-ui/src/app/components/dashboard/dashboard.component.ts index b9faefa..a0ad566 100644 --- a/ufund-ui/src/app/components/dashboard/dashboard.component.ts +++ b/ufund-ui/src/app/components/dashboard/dashboard.component.ts @@ -1,21 +1,21 @@ -import { Component } from '@angular/core'; -import { UsersService } from '../../services/users.service'; -import { userType } from '../../models/User'; +import {Component} from '@angular/core'; +import {userType} from '../../models/User'; +import {AuthService} from '../../services/auth.service';  @Component({ -  selector: 'app-dashboard', -  standalone: false, -  templateUrl: './dashboard.component.html', -  styleUrl: './dashboard.component.css' +    selector: 'app-dashboard', +    standalone: false, +    templateUrl: './dashboard.component.html', +    styleUrl: './dashboard.component.css'  })  export class DashboardComponent {      constructor( -      protected usersService: UsersService, +        protected authService: AuthService,      ) {}      isManager() { -        const type = this.usersService.getCurrentUser()?.type; +        const type = this.authService.getCurrentUser()?.type;          return type === ("MANAGER" as unknown as userType); -      } +    }  } 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 e654711..faa7e0b 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 @@ -1,11 +1,9 @@  import {Component, Input, OnInit, ViewChild} from '@angular/core'; -import {User} from '../../models/User'; -import { UsersService } from '../../services/users.service'; -import { Need } from '../../models/Need'; -import { NeedListComponent } from '../need-list/need-list.component'; -import { Router } from '@angular/router'; -import { CupboardService } from '../../services/cupboard.service'; -import { BehaviorSubject, catchError, firstValueFrom, Observable } from 'rxjs'; +import {UsersService} from '../../services/users.service'; +import {Router} from '@angular/router'; +import {CupboardService} from '../../services/cupboard.service'; +import {catchError, firstValueFrom, Observable} from 'rxjs'; +import {AuthService} from '../../services/auth.service';  @Component({      selector: 'app-funding-basket', @@ -14,67 +12,67 @@ import { BehaviorSubject, catchError, firstValueFrom, Observable } from 'rxjs';      styleUrl: './funding-basket.component.css'  })  export class FundingBasketComponent implements OnInit { -  statusText: any; +    statusText: any; -  constructor( -    private router: Router, -    protected cupboardService: CupboardService,  -    protected usersService: UsersService -  ) {} +    constructor( +        private router: Router, +        protected cupboardService: CupboardService, +        protected usersService: UsersService, +        private authService: AuthService +    ) {} -  @ViewChild("contribution") contribution?: Input; -  @Input() isValid: boolean = true; +    @ViewChild("contribution") contribution?: Input; +    @Input() isValid: boolean = true; -  // this is for login rerouting -  ngOnInit(): void { -    if (!this.usersService.getCurrentUser()) { -      this.router.navigate(['/login'], {queryParams: {redir: this.router.url}}); -      return; -    } - -    this.usersService.refreshBasket(); -    // this.usersService.removeNeed(); <- call this to remove -  } +    // this is for login rerouting +    ngOnInit(): void { +        if (!this.authService.getCurrentUser()) { +            this.router.navigate(['/login'], {queryParams: {redir: this.router.url}}); +            return; +        } -  async checkout() { -    this.isValid = true; -    for (let c of document.getElementById("funding-basket")?.querySelectorAll('.contribution')!) { -      let contribution = c as HTMLInputElement; -      contribution.setAttribute("style",""); -      if ( contribution.value == '' || contribution.valueAsNumber <= 0) { -        this.isValid = false; -        contribution.setAttribute("style","color: #ff0000"); -      } +        this.usersService.refreshBasket(); +        // this.usersService.removeNeed(); <- call this to remove      } -    if (this.isValid) { -      for (let c of document.getElementById("funding-basket")?.querySelectorAll('.contribution')!) { -        let contribution = c as HTMLInputElement; -        let need = await firstValueFrom(this.cupboardService.getNeed(+contribution.id)); -        need.current +=+ contribution.value; -        this.usersService.removeNeed(+need.id); -        this.cupboardService.updateNeed(need.id, need) -            .pipe(catchError((ex, r) => { -                if (ex.status == 500) { -                    this.statusText.next('Fields cannot be blank'); -                } else if (ex.status == 400) { -                    this.statusText.next('Goal must be greater than 0'); -                } else { -                    this.statusText.next('Error on creating need'); -                } -                return new Observable<string>(); -            })) -            .subscribe((result) => { -                if (result) { -                    console.log('need updated successfully'); -                    //this.needList?.refresh() -                } else { -                    console.log('need update failed'); -                } -            }); -      } -    } -  } +    async checkout() { +        this.isValid = true; +        for (let c of document.getElementById("funding-basket")?.querySelectorAll('.contribution')!) { +            let contribution = c as HTMLInputElement; +            contribution.setAttribute("style", ""); +            if (contribution.value == '' || contribution.valueAsNumber <= 0) { +                this.isValid = false; +                contribution.setAttribute("style", "color: #ff0000"); +            } +        } +        if (this.isValid) { +            for (let c of document.getElementById("funding-basket")?.querySelectorAll('.contribution')!) { +                let contribution = c as HTMLInputElement; +                let need = await firstValueFrom(this.cupboardService.getNeed(+contribution.id)); +                need.current += +contribution.value; +                this.usersService.removeNeed(+need.id); +                this.cupboardService.checkoutNeed(need.id, +contribution.value) +                    .pipe(catchError((ex, _) => { +                        if (ex.status == 500) { +                            this.statusText.next('Fields cannot be blank'); +                        } else if (ex.status == 400) { +                            this.statusText.next('Goal must be greater than 0'); +                        } else { +                            this.statusText.next('Error on creating need'); +                        } +                        return new Observable<string>(); +                    })) +                    .subscribe((result) => { +                        if (result) { +                            console.log('need updated successfully'); +                            //this.needList?.refresh() +                        } else { +                            console.log('need update failed'); +                        } +                    }); +            } +        } +    }  } diff --git a/ufund-ui/src/app/components/home-page/home-page.component.ts b/ufund-ui/src/app/components/home-page/home-page.component.ts index 5b2277c..95e8962 100644 --- a/ufund-ui/src/app/components/home-page/home-page.component.ts +++ b/ufund-ui/src/app/components/home-page/home-page.component.ts @@ -1,10 +1,10 @@ -import { Component } from '@angular/core'; +import {Component} from '@angular/core';  @Component({ -  selector: 'app-home-page', -  standalone: false, -  templateUrl: './home-page.component.html', -  styleUrl: './home-page.component.css' +    selector: 'app-home-page', +    standalone: false, +    templateUrl: './home-page.component.html', +    styleUrl: './home-page.component.css'  })  export class HomePageComponent { diff --git a/ufund-ui/src/app/components/login/login.component.ts b/ufund-ui/src/app/components/login/login.component.ts index 9d806f5..f6a2996 100644 --- a/ufund-ui/src/app/components/login/login.component.ts +++ b/ufund-ui/src/app/components/login/login.component.ts @@ -2,12 +2,13 @@ import {Component, OnInit} from '@angular/core'  import {UsersService} from '../../services/users.service';  import {ActivatedRoute, Router} from '@angular/router';  import {BehaviorSubject} from 'rxjs'; +import {AuthService} from '../../services/auth.service';  @Component({ -  selector: 'app-login', -  standalone: false, -  templateUrl: './login.component.html', -  styleUrl: './login.component.css' +    selector: 'app-login', +    standalone: false, +    templateUrl: './login.component.html', +    styleUrl: './login.component.css'  })  export class LoginComponent implements OnInit { @@ -17,7 +18,8 @@ export class LoginComponent implements OnInit {      constructor(          protected usersService: UsersService,          protected router: Router, -        private route: ActivatedRoute +        private route: ActivatedRoute, +        private authService: AuthService      ) {}      ngOnInit() { @@ -31,7 +33,7 @@ export class LoginComponent implements OnInit {              return;          } -        this.usersService.login(username, password).then(() => { +        this.authService.login(username, password).then(() => {              this.router.navigate([next]);          }).catch(ex => {              this.statusText.next("Unable to login: " + friendlyHttpStatus[ex.status]) 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 25f05d6..3a89a20 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,132 +1,138 @@ -import { Component } from '@angular/core'; +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'; +import {UsersService} from '../../services/users.service'; +import {userType} from '../../models/User'; +import {catchError, of} from 'rxjs'; +import {AuthService} from '../../services/auth.service'; +  @Component({ -  selector: 'app-need-list', -  standalone: false, -  templateUrl: './need-list.component.html', -  styleUrl: './need-list.component.css' +    selector: 'app-need-list', +    standalone: false, +    templateUrl: './need-list.component.html', +    styleUrl: './need-list.component.css'  })  export class NeedListComponent { -  needs: Need[] = []; -  searchResults: Need[] = []; +    needs: Need[] = []; +    searchResults: Need[] = []; -  constructor( -    private cupboardService: CupboardService, -    private usersService: UsersService -  ) {} +    constructor( +        private cupboardService: CupboardService, +        private usersService: UsersService, +        private authService: AuthService +    ) {}      refresh() {          this.cupboardService.getNeeds().subscribe(n => this.needs = n)      } -  ngOnInit(): void { -    this.refresh() -    -     this.close(); -  } +    ngOnInit(): void { +        this.refresh() -  private showElement(element: any) { -    if (element){ -      element.style.visibility = 'visible'; -      element.style.position = 'relative'; +        this.close();      } -  } -  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'; +        }      } -  } -  private updateSearchStatus(text: string) { -    let element = document.getElementById('search-status'); -    if (element) { -      element.innerHTML = text; +    private hideElement(element: any) { +        if (element) { +            element.style.visibility = 'hidden'; +            element.style.position = 'absolute'; +        }      } -  } -  open() { -    this.hideElement(document.getElementById('search-button')); -    this.showElement(document.getElementById('search-form')); -  } +    private updateSearchStatus(text: string) { +        let element = document.getElementById('search-status'); +        if (element) { +            element.innerHTML = text; +        } +    } -  close() { -    this.hideElement(document.getElementById('search-form')); -    this.showElement(document.getElementById('search-button')); -    this.hideElement(document.getElementById('search-status')); -  } +    open() { +        this.hideElement(document.getElementById('search-button')); +        this.showElement(document.getElementById('search-form')); +    } -  private searchDelay: any; +    close() { +        this.hideElement(document.getElementById('search-form')); +        this.showElement(document.getElementById('search-button')); +        this.hideElement(document.getElementById('search-status')); +    } -  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 +    private searchDelay: any; -    //remove previous search if it exists -    if (this.searchDelay) { -      clearTimeout(this.searchDelay); -    } +    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 -    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); -        this.showElement(document.getElementById('search-results')); -        this.showElement(document.getElementById('search-status')); -        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:"); +        //remove previous search if it exists +        if (this.searchDelay) { +            clearTimeout(this.searchDelay);          } -      }); -    }, 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); -  } - -  isHelper() { -    const type = this.usersService.getCurrentUser()?.type; -    return type === ("HELPER" as unknown as userType); -  } - -  add(need: Need) { -    const currentUser = this.usersService.getCurrentUser(); -    //console.log("get current user in angular:", currentUser) -    if (currentUser) { -      if (!currentUser.basket.includes(need.id)) { -        currentUser.basket.push(need.id); -        this.usersService.updateUser(currentUser).subscribe(() => { -          this.usersService.refreshBasket(); -          error: (err: any) => { -            console.error(err); -          } -        }); -      } else { -        window.alert("This need is already in your basket!") -      } -       +        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); +                this.showElement(document.getElementById('search-results')); +                this.showElement(document.getElementById('search-status')); +                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.authService.getCurrentUser()?.type; +        return type === ("MANAGER" as unknown as userType); +    } + +    isHelper() { +        const type = this.authService.getCurrentUser()?.type; +        return type === ("HELPER" as unknown as userType); +    } + +    add(need: Need) { +        const currentUser = this.authService.getCurrentUser(); +        //console.log("get current user in angular:", currentUser) +        if (currentUser) { +            if (!currentUser.basket.includes(need.id)) { +                currentUser.basket.push(need.id); +                this.usersService.updateUser(currentUser) +                    .pipe(catchError((err: any, _) => { +                        console.error(err); +                        return of(); +                    })) +                    .subscribe(() => { +                        this.usersService.refreshBasket(); +                    }); +            } else { +                window.alert("This need is already in your basket!") +            } + -  back() { -    this.searchResults = []; -  } +        } + +    } + +    back() { +        this.searchResults = []; +    }  } 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 597d0e0..e38554c 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 @@ -2,30 +2,30 @@ import {Component, Input} from '@angular/core';  import {GoalType, Need} from '../../models/Need';  import {ActivatedRoute} from "@angular/router";  import {CupboardService} from "../../services/cupboard.service"; -import { NgFor } from '@angular/common'; +import {NgFor} from '@angular/common';  @Component({ -  selector: 'app-need-page', -  standalone: false, -  templateUrl: './need-page.component.html', -  styleUrl: './need-page.component.css' +    selector: 'app-need-page', +    standalone: false, +    templateUrl: './need-page.component.html', +    styleUrl: './need-page.component.css'  })  export class NeedPageComponent { -  constructor( -     private route: ActivatedRoute, -     private cupboardService: CupboardService, -  ) {} +    constructor( +        private route: ActivatedRoute, +        private cupboardService: CupboardService, +    ) {} -  public GoalType = GoalType; +    public GoalType = GoalType; -  @Input() need?: Need; +    @Input() need?: Need; -  ngOnInit(): void { -    const id = Number(this.route.snapshot.paramMap.get('id')); -    this.cupboardService.getNeed(id).subscribe(n => this.need = n); -  } +    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 +    back() { +        window.history.back(); +    } +} diff --git a/ufund-ui/src/app/models/Need.ts b/ufund-ui/src/app/models/Need.ts index 9e97fd4..5cd4e39 100644 --- a/ufund-ui/src/app/models/Need.ts +++ b/ufund-ui/src/app/models/Need.ts @@ -1,13 +1,13 @@  export interface Need { -  name: string, -  id: number, -  filterAttributes: string[], -  type: GoalType; -  maxGoal: number; -  current: number; +    name: string, +    id: number, +    filterAttributes: string[], +    type: GoalType; +    maxGoal: number; +    current: number;  }  export enum GoalType { -  MONETARY, -  PHYSICAL +    MONETARY, +    PHYSICAL  } diff --git a/ufund-ui/src/app/models/User.ts b/ufund-ui/src/app/models/User.ts index f4396f6..e6848fa 100644 --- a/ufund-ui/src/app/models/User.ts +++ b/ufund-ui/src/app/models/User.ts @@ -1,5 +1,3 @@ -import {Need} from './Need'; -  export enum userType {      HELPER,      MANAGER diff --git a/ufund-ui/src/app/services/auth.service.ts b/ufund-ui/src/app/services/auth.service.ts new file mode 100644 index 0000000..6bc7145 --- /dev/null +++ b/ufund-ui/src/app/services/auth.service.ts @@ -0,0 +1,57 @@ +import {Injectable} from '@angular/core'; +import {BehaviorSubject, firstValueFrom} from 'rxjs'; +import {User} from '../models/User'; +import {HttpClient, HttpHeaders} from '@angular/common/http'; + +@Injectable({ +    providedIn: 'root' +}) +export class AuthService { + +    private authUrl = "http://localhost:8080/auth" +    private userUrl = "http://localhost:8080/users" + +    private currentUser : BehaviorSubject<User | null> = new BehaviorSubject<User | null>(null); +    private apiKey: string = ""; + +    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 +    ) {} + +    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.userUrl}/${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)) +    } + +    getCurrentUserSubject() { +        return this.currentUser; +    } + +    getCurrentUser() { +        return this.currentUser.getValue() +    } + +    getApiKey() { +        return this.apiKey; +    } + +} diff --git a/ufund-ui/src/app/services/cupboard.service.ts b/ufund-ui/src/app/services/cupboard.service.ts index 9e14106..9232c0c 100644 --- a/ufund-ui/src/app/services/cupboard.service.ts +++ b/ufund-ui/src/app/services/cupboard.service.ts @@ -2,6 +2,7 @@ import {Injectable} from '@angular/core';  import {HttpClient, HttpHeaders} from '@angular/common/http';  import {Need} from '../models/Need';  import {Observable} from 'rxjs'; +import {AuthService} from './auth.service';  @Injectable({      providedIn: 'root' @@ -9,35 +10,44 @@ import {Observable} from 'rxjs';  export class CupboardService {      private url = "http://localhost:8080/cupboard" -    private httpOptions = { -        headers: new HttpHeaders({'Content-Type': 'application/json'}) -    }; + +    httpOptions = () => ({ +        headers: new HttpHeaders({ +            'Content-Type': 'application/json', +            "jelly-api-key": this.authService.getApiKey() +        }) +    });      constructor( -        private http: HttpClient +        private http: HttpClient, +        private authService: AuthService      ) {}      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[]> { -        return this.http.get<Need[]>(this.url, this.httpOptions) +        return this.http.get<Need[]>(this.url, this.httpOptions())      }      searchNeeds(name: String): Observable<Need[]> { -        return this.http.get<Need[]>(`${this.url}/?name=${name}`, this.httpOptions) +        return this.http.get<Need[]>(`${this.url}/?name=${name}`, this.httpOptions())      }      getNeed(id: number): Observable<Need> { -        return this.http.get<Need>(`${this.url}/${id}`, this.httpOptions) +        return this.http.get<Need>(`${this.url}/${id}`, this.httpOptions())      }      updateNeed(id: number, data: Need): Observable<boolean> { -        return this.http.put<boolean>(`${this.url}/${id}`, data, this.httpOptions) +        return this.http.put<boolean>(`${this.url}/${id}`, data, this.httpOptions())      }      deleteNeed(id: number): Observable<boolean> { -        return this.http.delete<boolean>(`${this.url}/${id}`, this.httpOptions) +        return this.http.delete<boolean>(`${this.url}/${id}`, this.httpOptions()) +    } + +    checkoutNeed(id: number, quantity: number) { +        return this.http.put(`${this.url}/checkout`, {needID: id, amount: quantity}, this.httpOptions())      }  } diff --git a/ufund-ui/src/app/services/users.service.ts b/ufund-ui/src/app/services/users.service.ts index dba8185..4080ebf 100644 --- a/ufund-ui/src/app/services/users.service.ts +++ b/ufund-ui/src/app/services/users.service.ts @@ -1,96 +1,69 @@  import { Injectable } from '@angular/core';  import {HttpClient, HttpHeaders} from '@angular/common/http'; -import {BehaviorSubject, firstValueFrom, Observable} from 'rxjs'; +import {BehaviorSubject, catchError, firstValueFrom, Observable, of} from 'rxjs';  import {User} from '../models/User';  import { Need } from '../models/Need';  import { CupboardService } from './cupboard.service'; +import {AuthService} from './auth.service';  @Injectable({    providedIn: 'root'  })  export class UsersService { -    private currentUser : BehaviorSubject<User | null> = new BehaviorSubject<User | null>(null); -    private apiKey: string = "";      private basket = new BehaviorSubject<Need[]>([]); -      private url = "http://localhost:8080/users" -    private authUrl = "http://localhost:8080/auth" -    private httpOptions = { + +    httpOptions = () => ({          headers: new HttpHeaders({              'Content-Type': 'application/json', -            "jelly-api-key": this.apiKey +            "jelly-api-key": this.authService.getApiKey()          }) -    }; -    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,          private cupboardService: CupboardService, +        private authService: AuthService      ) {}      async createUser(username:string, password:string) { -        await firstValueFrom(this.http.post<User>(this.url, {username: username, password: password}, this.httpOptions)) +        await firstValueFrom(this.http.post<User>(this.url, {username: username, password: password}, this.httpOptions()))      }      getUser(id: string): Observable<User> { -        return this.http.get<User>(`${this.url}/${id}`, this.httpOptions) +        return this.http.get<User>(`${this.url}/${id}`, this.httpOptions())      }      updateUser(user: User): Observable<User> { -        return this.http.put<User>(`${this.url}/${user.username}`,user, this.httpOptions2) +        console.log(`${this.url}/${user.username}`, user, this.httpOptions) +        return this.http.put<User>(`${this.url}/${user.username}`, user, this.httpOptions())      }      deleteUser(id: number): Observable<boolean> { -        return this.http.delete<boolean>(`${this.url}/${id}`, this.httpOptions) -    } - -    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)) +        return this.http.delete<boolean>(`${this.url}/${id}`, this.httpOptions())      }      refreshBasket() { -        let promiseArr = this.getCurrentUser()!.basket.map(async needID => { +        let promiseArr = this.authService.getCurrentUser()!.basket.map(async needID => {              return await firstValueFrom(this.cupboardService.getNeed(needID));          })          Promise.all(promiseArr).then(r => this.basket.next(r));      } -     +      removeNeed(id: number) {          let newArr = this.basket.getValue().filter(v => v.id != id);          this.basket.next(newArr); -        this.getCurrentUser()!.basket = newArr.map(need => need.id); -        this.updateUser(this.getCurrentUser()!).subscribe(() => { +        this.authService.getCurrentUser()!.basket = newArr.map(need => need.id); +        this.updateUser(this.authService.getCurrentUser()!) +            .pipe( +                catchError((err: any, _) => { +                    console.error(err); +                    return of(); +                }) +            ) +            .subscribe(() => {              this.refreshBasket(); -            error: (err: any) => { -              console.error(err); -            }            });      }  | 
