Skip to content
This repository has been archived by the owner on Feb 13, 2023. It is now read-only.

Events integration #64

Merged
merged 13 commits into from
Dec 9, 2017
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -68,8 +68,10 @@ fabric.properties
hs_err_pid*

application.properties
application.yml
.gradle/
.idea/
build/
src/main/resources/templates/fragments/product/
config/oidc-client-registration.json
*.iml
7 changes: 6 additions & 1 deletion config/application-test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,8 @@ security.basic.enabled: false
flyway.enabled: false

# Serve connect
server.port: 9000
server:
context-path: /payments/

#Spring Boot Actuator endpoint settings
management:
Expand Down Expand Up @@ -56,3 +57,7 @@ payments:
admin:
username: admin
password: password

events:
username: CH Events
password: secret
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ public interface ProductService {

Product getProductById(Integer productId);

Product getProductByKey(String key);

Set<Product> getProductByCommittee(CommitteeEnum committeeEnum, int year);

boolean isProductAvailable(Integer productId);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import ch.wisv.payments.admin.products.request.ProductRequest;
import ch.wisv.payments.exception.ProductGroupInUseException;
import ch.wisv.payments.exception.ProductInUseException;
import ch.wisv.payments.exception.ProductNotFoundException;
import ch.wisv.payments.model.*;
import ch.wisv.payments.rest.OrderService;
import ch.wisv.payments.rest.repository.ProductGroupRepository;
Expand Down Expand Up @@ -116,6 +117,11 @@ public Product getProductById(Integer productId) {
return productRepository.findOne(productId);
}

@Override
public Product getProductByKey(String key) {
return productRepository.findOneByKey(key).orElseThrow(ProductNotFoundException::new);
}

@Override
public Set<Product> getProductByCommittee(CommitteeEnum committeeEnum, int year) {
Committee committee = committeeService.getCommittee(committeeEnum, year);
Expand Down
3 changes: 2 additions & 1 deletion src/main/java/ch/wisv/payments/model/CommitteeEnum.java
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,8 @@ public enum CommitteeEnum {
WIFI("WiFi"),
W3CIE("W3Cie"),
WIEWIE("WIEWIE"),
WOCKY("Wocky!");
WOCKY("Wocky!"),
BESTUUR("Bestuur");

private final String name;

Expand Down
11 changes: 11 additions & 0 deletions src/main/java/ch/wisv/payments/model/eventsync/EventsSync.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package ch.wisv.payments.model.eventsync;

import lombok.Data;
import lombok.Getter;

@Data
class EventsSync {

@Getter
private String trigger;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package ch.wisv.payments.model.eventsync;

import lombok.Data;
import lombok.EqualsAndHashCode;

@EqualsAndHashCode(callSuper = false)
@Data
public class ProductEventsSync extends EventsSync {

/**
* Price of the product.
*/
private Double price;

/**
* Description of the product.
*/
private String description;

/**
* Title of the product.
*/
private String title;

/**
* UUID of the product.
*/
private String key;

/**
* Enum string of the organizing Committee.
*/
private String organizedBy;

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package ch.wisv.payments.rest.eventsync;

import ch.wisv.payments.model.eventsync.ProductEventsSync;

public interface EventsSyncProductService {

/**
* Create a Product from a ProductEventsSync model.
*
* @param productEventsSync of type ProductEventsSync
*/
void createProduct(ProductEventsSync productEventsSync);

/**
* Edit a Product from a ProductEventsSync model.
*
* @param productEventsSync of type ProductEventsSync
*/
void editProduct(ProductEventsSync productEventsSync);

/**
* Delete a Product from a ProductEventsSync model.
*
* @param productEventsSync of type ProductEventsSync
*/
void deleteProduct(ProductEventsSync productEventsSync);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
package ch.wisv.payments.rest.eventsync;

import ch.wisv.payments.admin.committees.CommitteeRepository;
import ch.wisv.payments.exception.ProductNotFoundException;
import ch.wisv.payments.model.Committee;
import ch.wisv.payments.model.CommitteeEnum;
import ch.wisv.payments.model.Product;
import ch.wisv.payments.model.eventsync.ProductEventsSync;
import ch.wisv.payments.rest.repository.ProductRepository;
import org.springframework.stereotype.Service;

@Service
public class EventsSyncProductServiceImpl implements EventsSyncProductService {

/**
* ProductRepository.
*/
private final ProductRepository productRepository;

/**
* CommitteeRepository.
*/
private final CommitteeRepository committeeRepository;

/**
* Constructor.
*
* @param productRepository of type ProductRepository
* @param committeeRepository of type CommitteeRepository
*/
public EventsSyncProductServiceImpl(ProductRepository productRepository, CommitteeRepository committeeRepository) {
this.productRepository = productRepository;
this.committeeRepository = committeeRepository;
}

/**
* @param productEventsSync of type ProductEventsSync
*/
@Override
public void createProduct(ProductEventsSync productEventsSync) {
Product product = new Product();
product.setKey(productEventsSync.getKey());

this.changeProductValues(product, productEventsSync);

productRepository.save(product);
}

/**
* @param productEventsSync of type ProductEventsSync
*/
@Override
public void editProduct(ProductEventsSync productEventsSync) {
Product product = this.getProductByKey(productEventsSync.getKey());

this.changeProductValues(product, productEventsSync);

productRepository.save(product);
}

/**
* Change the Product values using a ProductEventsSync model.
*
* @param product of type Product.
* @param productEventsSync of type ProductEventsSync.
*/
private void changeProductValues(Product product, ProductEventsSync productEventsSync) {
if (product.getCommittee() == null || product.getCommittee().toString().equals(productEventsSync.getOrganizedBy())) {
this.changeProductCommittee(product, productEventsSync);
}

product.setDescription(productEventsSync.getDescription());
product.setName(productEventsSync.getTitle());
product.setPrice(productEventsSync.getPrice().floatValue());
}

/**
* Change the Committee of Product.
*
* @param product of type Product
* @param productEventsSync of type ProductEventsSync
*/
private void changeProductCommittee(Product product, ProductEventsSync productEventsSync) {
CommitteeEnum committeeEnum = CommitteeEnum.valueOf(productEventsSync.getOrganizedBy());
int year = 2017;
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should be dynamic!

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Issue #65


Committee committee = committeeRepository.findOneByNameAndYear(committeeEnum, year)
.orElse(new Committee(committeeEnum, year));

if (committee.getId() == 0) {
committeeRepository.save(committee);
}

product.setCommittee(committee);
}

/**
* @param productEventsSync of type ProductEventsSync
*/
@Override
public void deleteProduct(ProductEventsSync productEventsSync) {
Product product = this.getProductByKey(productEventsSync.getKey());

productRepository.delete(product.getId());
}

/**
* Get a Product by its key.
*
* @param key of type Key
* @return Product
*/
private Product getProductByKey(String key) {
return productRepository.findOneByKey(key).orElseThrow(ProductNotFoundException::new);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
package ch.wisv.payments.rest.eventsync;

import ch.wisv.payments.admin.products.ProductService;
import ch.wisv.payments.exception.ProductNotFoundException;
import ch.wisv.payments.model.eventsync.ProductEventsSync;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;

import javax.servlet.http.HttpServletRequest;
import javax.validation.constraints.NotNull;
import java.nio.charset.Charset;
import java.util.Base64;

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

@RestController
@CrossOrigin
@RequestMapping(value = "/api/chevents/sync")
@Validated
public class EventsSyncRestController {

@Value("${events.username}")
@NotNull
private String username;

@Value("${events.password}")
@NotNull
private String password;

/**
* ProductService.
*/
private final ProductService productService;

private final EventsSyncProductService eventsSyncProductService;

/**
* Constructor ProductService.
*
* @param productService of type ProductService
* @param eventsSyncProductService of type EventsSyncProductService
*/
public EventsSyncRestController(ProductService productService, EventsSyncProductService eventsSyncProductService) {
this.productService = productService;
this.eventsSyncProductService = eventsSyncProductService;
}


@PostMapping("/product/")
public ResponseEntity<?> eventSync(HttpServletRequest request, @RequestBody ProductEventsSync productEventsSync) {
String[] credentials = this.decryptBasicAuthHeader(request.getHeader("Authorization"));
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This feels ugly, but it's quick and effective I guess... I'd prefer a more native Spring way using filters and an in-memory events user.


if (!this.authChEvents(credentials)) {
return createResponseEntity(HttpStatus.UNAUTHORIZED, "User is not authorized", null);
}

switch (productEventsSync.getTrigger()) {
case "PRODUCT_CREATE_EDIT":
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's turn magic strings into Enums?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fix in af590d2

this.determineCreateOrUpdate(productEventsSync);
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You're not determining anything, you're creating or updating it. createOrUpdate() works just fine here.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fix in commit b9e45db

break;
case "PRODUCT_DELETE":
eventsSyncProductService.deleteProduct(productEventsSync);
break;
default:
return createResponseEntity(HttpStatus.BAD_REQUEST, "Events trigger not supported!", null);
}

return createResponseEntity(HttpStatus.OK, null, null);
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should be more specific, with HttpStatus.CREATED when a new product is created

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should be a HttpStatus.OK, otherwise CH events thinks the sync failed.

}

/**
* Check if user is CH Events.
*
* @param credentials of type String[]
*/
private boolean authChEvents(String[] credentials) {
return credentials[0].equals(this.username) && credentials[1].equals(this.password);
}

/**
* Determine if a Product should be updated or created.
*
* @param productEventsSync of type ProductEventsSync
*/
private void determineCreateOrUpdate(ProductEventsSync productEventsSync) {
try {
productService.getProductByKey(productEventsSync.getKey());

eventsSyncProductService.editProduct(productEventsSync);
} catch (ProductNotFoundException e) {
eventsSyncProductService.createProduct(productEventsSync);
}
}

/**
* Decrypt the Basic Auth header.
*
* @param basicAuth of type String
* @return String[] with username and password
*/
private String[] decryptBasicAuthHeader(String basicAuth) {
String base64Credentials = basicAuth.substring("Basic".length()).trim();
String credentials = new String(Base64.getDecoder().decode(base64Credentials),
Charset.forName("UTF-8"));

return credentials.split(":", 2);
}
}
Loading