Skip to content

Commit

Permalink
Merge pull request #241 from AreaFiftyLAN/feature-consumption_tracker
Browse files Browse the repository at this point in the history
Consumption Tracking
  • Loading branch information
Martijn Janssen committed May 31, 2016
2 parents e255fac + 7591386 commit dcdc9b5
Show file tree
Hide file tree
Showing 10 changed files with 806 additions and 0 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
package ch.wisv.areafiftylan.controller;

import ch.wisv.areafiftylan.exception.AlreadyConsumedException;
import ch.wisv.areafiftylan.exception.ConsumptionNotFoundException;
import ch.wisv.areafiftylan.model.util.Consumption;
import ch.wisv.areafiftylan.service.ConsumptionService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.*;

import java.util.Collection;
import java.util.Collections;
import java.util.Map;

import static ch.wisv.areafiftylan.util.ResponseEntityBuilder.createResponseEntity;

/**
* Created by beer on 16-5-16.
*/
@RestController
@PreAuthorize("hasRole('ADMIN')")
@RequestMapping(value = "/consumptions")
public class ConsumptionController {
@Autowired
private ConsumptionService consumptionService;

@RequestMapping(value = "/{ticketId}", method = RequestMethod.GET)
public Collection<Consumption> consumptionsMade(@PathVariable Long ticketId){
return consumptionService.getByTicketIdIfValid(ticketId).getConsumptionsMade();
}

@RequestMapping(method = RequestMethod.GET)
public Collection<Consumption> getAllPossibleConsumptions(){
return consumptionService.getPossibleConsumptions();
}

@RequestMapping(method = RequestMethod.POST)
public ResponseEntity<?> addAvailableConsumption(@RequestBody String consumptionName){
consumptionService.addPossibleConsumption(consumptionName);

return createResponseEntity(HttpStatus.OK, "Successfully added " + consumptionName + " as a supported consumption.");
}

@RequestMapping(method = RequestMethod.DELETE)
public ResponseEntity<?> removeAvailableConsumption(@RequestBody Long consumptionId){
Consumption c = consumptionService.removePossibleConsumption(consumptionId);

return createResponseEntity(HttpStatus.OK, "Successfully removed " + c.getName() + " as a supported consumption.");
}

@RequestMapping(value = "/{ticketId}/consume", method = RequestMethod.POST)
public ResponseEntity<?> consume(@PathVariable Long ticketId, @RequestBody Long consumptionId){
consumptionService.consume(ticketId, consumptionId);
return createResponseEntity(HttpStatus.OK, "Successfully consumed consumption");
}

@RequestMapping(value = "/{ticketId}/reset", method = RequestMethod.POST)
public ResponseEntity<?> reset(@PathVariable Long ticketId, @RequestBody Long consumptionId){
consumptionService.reset(ticketId, consumptionId);
return createResponseEntity(HttpStatus.OK, "Successfully reset consumption");
}

@ExceptionHandler(value = AlreadyConsumedException.class)
public ResponseEntity<?> handleAlreadyConsumed(AlreadyConsumedException e){
return createResponseEntity(HttpStatus.CONFLICT, e.getMessage());
}

@ExceptionHandler(value = ConsumptionNotFoundException.class)
public ResponseEntity<?> handleConsumptionNotSupported(ConsumptionNotFoundException e){
return createResponseEntity(HttpStatus.NOT_FOUND, e.getMessage());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package ch.wisv.areafiftylan.exception;

import ch.wisv.areafiftylan.model.util.Consumption;

/**
* Created by beer on 16-5-16.
*/
public class AlreadyConsumedException extends RuntimeException {
public AlreadyConsumedException(Consumption consumption) {
super("Consumption " + consumption.getName() + " has already been consumed.");
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package ch.wisv.areafiftylan.exception;

import ch.wisv.areafiftylan.model.util.Consumption;

/**
* Created by beer on 8-5-16.
*/
public class ConsumptionNotFoundException extends RuntimeException {
public ConsumptionNotFoundException(Long consumptionId) {
super("Can't find a consumption with id: \"" + consumptionId + "\" is unsupported.");
}
}
62 changes: 62 additions & 0 deletions src/main/java/ch/wisv/areafiftylan/model/ConsumptionMap.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
package ch.wisv.areafiftylan.model;

import ch.wisv.areafiftylan.exception.AlreadyConsumedException;
import ch.wisv.areafiftylan.model.util.Consumption;
import lombok.Getter;
import lombok.NonNull;

import javax.persistence.*;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;

/**
* Created by beer on 8-5-16.
*/
@Entity
public class ConsumptionMap {
@Id
@GeneratedValue
Long id;

@NonNull
@ElementCollection(fetch = FetchType.EAGER)
private Collection<Consumption> consumptionsMade;

@OneToOne(targetEntity = Ticket.class, cascade = CascadeType.MERGE)
@NonNull
@Getter
private Ticket ticket;

public ConsumptionMap() {
// JPA Only
}

public ConsumptionMap(Ticket t) {
this.consumptionsMade = new ArrayList<>();
this.ticket = t;
}

public boolean isConsumed(Consumption consumption){
return consumptionsMade.contains(consumption);
}

public void consume(Consumption consumption){
if(isConsumed(consumption)){
throw new AlreadyConsumedException(consumption);
}

consumptionsMade.add(consumption);
}

public void reset(Consumption consumption){
if(isConsumed(consumption)){
consumptionsMade.remove(consumption);
}
}

public Collection<Consumption> getConsumptionsMade(){
return consumptionsMade;
}
}
42 changes: 42 additions & 0 deletions src/main/java/ch/wisv/areafiftylan/model/util/Consumption.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
package ch.wisv.areafiftylan.model.util;

import lombok.Getter;

import javax.persistence.GeneratedValue;
import javax.persistence.Id;

import javax.persistence.Entity;

/**
* Created by beer on 20-5-16.
*/
@Entity
public class Consumption {
@GeneratedValue
@Getter
@Id
Long id;

@Getter
String name;

public Consumption() {
// JPA Only
}

public Consumption(String name) {
this.name = name;
}

@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof Consumption)) return false;

Consumption that = (Consumption) o;

if (id != that.id) return false;
return name.equals(that.getName());

}
}
27 changes: 27 additions & 0 deletions src/main/java/ch/wisv/areafiftylan/service/ConsumptionService.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package ch.wisv.areafiftylan.service;

import ch.wisv.areafiftylan.model.ConsumptionMap;
import ch.wisv.areafiftylan.model.util.Consumption;

import java.util.Collection;

/**
* Created by beer on 16-5-16.
*/
public interface ConsumptionService {
ConsumptionMap getByTicketIdIfValid(Long ticketId);

boolean isConsumed(Long ticketId, Long consumptionId);

void consume(Long ticketId, Long consumptionId);

void reset(Long ticketId, Long consumptionId);

Consumption getByConsumptionId(Long consumptionId);

Collection<Consumption> getPossibleConsumptions();

Consumption removePossibleConsumption(Long consumptionId);

Consumption addPossibleConsumption(String consumptionName);
}
115 changes: 115 additions & 0 deletions src/main/java/ch/wisv/areafiftylan/service/ConsumptionServiceImpl.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
package ch.wisv.areafiftylan.service;

import ch.wisv.areafiftylan.exception.ConsumptionNotFoundException;
import ch.wisv.areafiftylan.exception.InvalidTicketException;
import ch.wisv.areafiftylan.model.ConsumptionMap;
import ch.wisv.areafiftylan.model.Ticket;
import ch.wisv.areafiftylan.model.util.Consumption;
import ch.wisv.areafiftylan.service.repository.ConsumptionMapsRepository;
import ch.wisv.areafiftylan.service.repository.PossibleConsumptionsRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.dao.DuplicateKeyException;
import org.springframework.stereotype.Service;

import java.util.Collection;
import java.util.Optional;
import java.util.stream.Collectors;

/**
* Created by beer on 16-5-16.
*/
@Service
public class ConsumptionServiceImpl implements ConsumptionService {
ConsumptionMapsRepository consumptionMapsRepository;
PossibleConsumptionsRepository possibleConsumptionsRepository;
TicketService ticketService;

@Autowired
public ConsumptionServiceImpl(ConsumptionMapsRepository consumptionMapsRepository,
PossibleConsumptionsRepository possibleConsumptionsRepository,
TicketService ticketService) {
this.consumptionMapsRepository = consumptionMapsRepository;
this.possibleConsumptionsRepository = possibleConsumptionsRepository;
this.ticketService = ticketService;
}

@Override
public ConsumptionMap getByTicketIdIfValid(Long ticketId) {
if(!ticketService.getTicketById(ticketId).isValid()){
throw new InvalidTicketException("Ticket is invalid; It can not be used for consumptions.");
}

Optional<ConsumptionMap> mapOptional = consumptionMapsRepository.findByTicketId(ticketId);

if(mapOptional.isPresent()){
return mapOptional.get();
}else{
return initializeConsumptionMap(ticketId);
}
}

@Override
public boolean isConsumed(Long ticketId, Long consumptionId) {
Consumption c = getByConsumptionId(consumptionId);
return getByTicketIdIfValid(ticketId).isConsumed(c);
}

private ConsumptionMap initializeConsumptionMap(Long ticketId){
Ticket t = ticketService.getTicketById(ticketId);
return consumptionMapsRepository.saveAndFlush(new ConsumptionMap(t));
}

@Override
public void consume(Long ticketId, Long consumptionId) {
ConsumptionMap consumptions = getByTicketIdIfValid(ticketId);
Consumption consumption = getByConsumptionId(consumptionId);
consumptions.consume(consumption);
consumptionMapsRepository.saveAndFlush(consumptions);
}

@Override
public void reset(Long ticketId, Long consumptionId) {
ConsumptionMap consumptions = getByTicketIdIfValid(ticketId);
Consumption consumption = getByConsumptionId(consumptionId);
consumptions.reset(consumption);
consumptionMapsRepository.saveAndFlush(consumptions);
}

@Override
public Consumption getByConsumptionId(Long consumptionId) {
return possibleConsumptionsRepository.findById(consumptionId)
.orElseThrow(() -> new ConsumptionNotFoundException(consumptionId));
}

@Override
public Collection<Consumption> getPossibleConsumptions() {
return possibleConsumptionsRepository.findAll();
}

@Override
public Consumption removePossibleConsumption(Long consumptionId) {
Consumption consumption = getByConsumptionId(consumptionId);

resetConsumptionEverywhere(consumption);

possibleConsumptionsRepository.delete(consumption);
return consumption;
}

@Override
public Consumption addPossibleConsumption(String consumptionName) {
if(possibleConsumptionsRepository.findByName(consumptionName).isPresent()){
throw new DuplicateKeyException("Consumption " + consumptionName + " is already supported");
}

Consumption consumption = new Consumption(consumptionName);
return possibleConsumptionsRepository.saveAndFlush(consumption);
}

private void resetConsumptionEverywhere(Consumption consumption){
Collection<Ticket> allValidTickets = ticketService.getAllTickets().stream()
.filter(t -> t.isValid()).collect(Collectors.toList());

allValidTickets.forEach(t -> reset(t.getId(), consumption.getId()));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package ch.wisv.areafiftylan.service.repository;

import ch.wisv.areafiftylan.model.ConsumptionMap;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;

import java.util.Optional;

/**
* Created by beer on 16-5-16.
*/
@Repository
public interface ConsumptionMapsRepository extends JpaRepository<ConsumptionMap, Long> {
Optional<ConsumptionMap> findByTicketId(Long ticketId);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package ch.wisv.areafiftylan.service.repository;

import ch.wisv.areafiftylan.model.util.Consumption;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;

import java.util.Optional;

/**
* Created by beer on 20-5-16.
*/
@Repository
public interface PossibleConsumptionsRepository extends JpaRepository<Consumption, String> {
Optional<Consumption> findByName(String name);

Optional<Consumption> findById(Long consumptionId);
}
Loading

0 comments on commit dcdc9b5

Please sign in to comment.