Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
449 changes: 449 additions & 0 deletions Postman_test_suite

Large diffs are not rendered by default.

10 changes: 9 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,12 @@ To run the project through maven:
`mvn exec:java`

### Testing ###
By default, the application will be running on port 8080.
By default, the application will be running on port 8080.

This application does use a H2 SQL database using the persistance API. However, the application is still configured to
remake the database upon start up so user accounts and to do lists will not be persisted between runs.

The included file `Postman_test_suite.json` can be imported into the Postman extension for Chrome to be run as an integration test suite.

Note: Most of the intermittent commits will not compile due to a file rename not being committed by IntelliJ. Manually
renaming 'ToDoListService.java' to 'TodoListService.java' will fix this.
40 changes: 29 additions & 11 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -21,17 +21,27 @@
<start-class>co.redeye.spring.challenge.Application</start-class>
</properties>

<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.3.5.RELEASE</version>
</parent>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
</dependencies>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.3.5.RELEASE</version>
</parent>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>

<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<version>1.4.192</version>
</dependency>
</dependencies>

<pluginRepositories>
<pluginRepository>
Expand Down Expand Up @@ -70,6 +80,14 @@
<mainClass>${start-class}</mainClass>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>1.8</source>
<target>1.8</target>
</configuration>
</plugin>
</plugins>
</build>
</project>
14 changes: 0 additions & 14 deletions src/main/java/co/redeye/spring/challenge/SampleController.java

This file was deleted.

16 changes: 16 additions & 0 deletions src/main/java/co/redeye/spring/challenge/Utils.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package co.redeye.spring.challenge;

/**
* Helper functions.
*/
public class Utils {
/**
* Checks that a string value is non-null and non-empty
*
* @param string The String to check.
* @return True if String is non-null and has a length >= 1
*/
public static boolean stringPresent(String string) {
return string != null && !string.isEmpty();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package co.redeye.spring.challenge.controllers;

import co.redeye.spring.challenge.Utils;
import co.redeye.spring.challenge.exceptions.AuthenticationException;
import co.redeye.spring.challenge.services.AuthenticatorService;
import co.redeye.spring.challenge.views.LoginRequest;
import co.redeye.spring.challenge.views.LoginResponse;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;

@RestController
@RequestMapping("/account")
public class AuthenticationController {
@Autowired
AuthenticatorService authenticator;

@RequestMapping(value = "/register", method = RequestMethod.POST)
@ResponseBody
public LoginResponse register(@RequestBody LoginRequest registerRequest) throws AuthenticationException {
registerRequest.validate();

String token = authenticator.register(registerRequest.getUsername(), registerRequest.getPassword());
return new LoginResponse(token);
}

@RequestMapping(value = "/login", method = RequestMethod.POST)
@ResponseBody
public LoginResponse login(@RequestBody LoginRequest loginRequest) throws AuthenticationException {
loginRequest.validate();

String token = authenticator.login(loginRequest.getUsername(), loginRequest.getPassword());
return new LoginResponse(token);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package co.redeye.spring.challenge.controllers;

import co.redeye.spring.challenge.exceptions.AuthenticationException;
import co.redeye.spring.challenge.exceptions.IllegalItemException;
import co.redeye.spring.challenge.exceptions.UserException;
import co.redeye.spring.challenge.views.ErrorResponse;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.ResponseStatus;

/**
* Global Handler for uncaught Exceptions.
*/
@ControllerAdvice
public class ExceptionHandlerController {
@ExceptionHandler (value = IllegalItemException.class)
@ResponseStatus(HttpStatus.FORBIDDEN)
@ResponseBody
public ErrorResponse illegalAccessException(IllegalItemException e) {
return new ErrorResponse(e.getMessage());
}

@ExceptionHandler (value = UserException.class)
@ResponseStatus(HttpStatus.BAD_REQUEST)
@ResponseBody
public ErrorResponse authenticationException(UserException e) {
return new ErrorResponse(e.getMessage());
}

@ExceptionHandler (value = Throwable.class)
@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
@ResponseBody
public ErrorResponse catchAll(Throwable e) {
e.printStackTrace();
return new ErrorResponse("An unexpected error has occurred.");
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
package co.redeye.spring.challenge.controllers;

import co.redeye.spring.challenge.exceptions.AuthenticationException;
import co.redeye.spring.challenge.exceptions.UserException;
import co.redeye.spring.challenge.services.TodoListService;
import co.redeye.spring.challenge.views.TodoItem;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;

import java.util.List;

/**
* Controller for handling all access of the user's to do list.
*/
@Controller
@RequestMapping("/todo")
public class TodoListController {
@Autowired
private TodoListService todoListService;

/**
* Retrieves the user's entire to do list
*
* @param authToken The user's authentication token
* @throws AuthenticationException If the authentication token is missing or invalid.
*/
@RequestMapping(value = "/", method = RequestMethod.GET)
@ResponseBody
public List<TodoItem> getTasks(@RequestHeader("Authorization") String authToken) throws UserException {
return todoListService.getItems(authToken);
}

/**
* Adds a new item to the authenticated user's to do list.
*
* @param newItem The item being added.
* @param authToken The user's authentication token
* @throws AuthenticationException If the authentication token is missing or invalid.
*/
@RequestMapping(value = "/", method = RequestMethod.POST)
@ResponseStatus(HttpStatus.OK)
public void newTask(@RequestBody TodoItem newItem, @RequestHeader("Authorization") String authToken) throws UserException {
newItem.validate();
todoListService.addItem(authToken, newItem.getText(), newItem.isDone());
}

/**
* Gets the user's incomplete items.
*
* @param authToken The user's authentication token.
* @return All of the user's items which are not done.
* @throws AuthenticationException If the user's token is invalid.
*/
@RequestMapping(value = "/active", method = RequestMethod.GET)
@ResponseBody
public List<TodoItem> getActiveItems(@RequestHeader("Authorization") String authToken) throws AuthenticationException {
return todoListService.getIncompleteItems(authToken);
}

/**
* Gets the user's complete items.
*
* @param authToken The user's authentication token.
* @return All of the user's items which are done.
* @throws AuthenticationException If the user's token is invalid.
*/
@RequestMapping(value = "/inactive", method = RequestMethod.GET)
@ResponseBody
public List<TodoItem> getInactiveItems(@RequestHeader("Authorization") String authToken) throws AuthenticationException {
return todoListService.getCompleteItems(authToken);
}

/**
* Modifies an existing task.
*
* @param item The new values for the item.
* @param authToken The user's authentication token.
* @param taskId The id of the task being modified.
* @throws UserException If there is any problem with the request.
*/
@RequestMapping(value = "/{id}", method = RequestMethod.POST)
@ResponseStatus(HttpStatus.OK)
public void editTask(@RequestBody TodoItem item, @RequestHeader("Authorization") String authToken, @PathVariable("id") long taskId) throws UserException {
item.validate();
todoListService.editItem(authToken, taskId, item.getText(), item.isDone());
}

/**
* Completely removes an item from the user's to do list.
*
* @param authToken The user's authentication token.
* @param taskId The id of the task to be removed
* @throws UserException If there is an authentication issue or the task does not belong to the user.
*/
@RequestMapping(value = "/{id}", method = RequestMethod.DELETE)
@ResponseStatus(HttpStatus.OK)
public void deleteTask(@RequestHeader("Authorization") String authToken, @PathVariable("id") long taskId) throws UserException {
todoListService.deleteItem(authToken, taskId);
}

/**
* Completely removes all items user's to do list.
*
* @param authToken The user's authentication token.
* @throws AuthenticationException If there is an authentication issue.
*/
@RequestMapping(value = "/clear", method = RequestMethod.DELETE)
@ResponseStatus(HttpStatus.OK)
public void deleteAllTasks(@RequestHeader("Authorization") String authToken) throws AuthenticationException {
todoListService.deleteAllItems(authToken);
}
}
56 changes: 56 additions & 0 deletions src/main/java/co/redeye/spring/challenge/db/Item.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
package co.redeye.spring.challenge.db;

import javax.persistence.*;

/**
* Represents a single to do list item for a user.
*/
@Entity
@Table(name = "items")
public class Item {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private long id;

@ManyToOne(targetEntity = User.class)
@JoinColumn(name = "user_id", referencedColumnName = "id", nullable = false)
private User user;

@Column(nullable = false)
private boolean done;

@Column(nullable = false)
private String description;

public long getId() {
return id;
}

public void setId(long id) {
this.id = id;
}

public User getUser() {
return user;
}

public void setUser(User user) {
this.user = user;
}

public boolean isDone() {
return done;
}

public void setDone(boolean done) {
this.done = done;
}

public String getDescription() {
return description;
}

public void setDescription(String description) {
this.description = description;
}
}
21 changes: 21 additions & 0 deletions src/main/java/co/redeye/spring/challenge/db/ItemRepository.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package co.redeye.spring.challenge.db;

import org.springframework.data.repository.CrudRepository;
import org.springframework.stereotype.Repository;

import java.util.List;

/**
* Provides custom database access methods we require.
*/
@Repository
public interface ItemRepository extends CrudRepository<Item, Long> {
/**
* Query to retrieve all to do list items belonging to a specific user with a given status.
*
* @param user The user.
* @param done The item's status
* @return The user's to do list items with the given status.
*/
List<Item> findByUserAndDone(User user, boolean done);
}
Loading