Skip to content

Commit

Permalink
Merge branch 'master' of https://github.com/folio-org/mod-dcb into MO…
Browse files Browse the repository at this point in the history
…DDCB-59
  • Loading branch information
MagzhanArtykov committed Dec 12, 2023
2 parents bae1574 + a010d6e commit 59c328e
Show file tree
Hide file tree
Showing 34 changed files with 875 additions and 354 deletions.
58 changes: 58 additions & 0 deletions PERSONAL_DATA_DISCLOSURE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@


## Overview
The purpose of this form is to disclose the types of personal data stored by each module. This information enables those hosting FOLIO to better manage and comply with various privacy laws and restrictions, e.g. GDPR.

It's important to note that personal data is not limited to that which can be used to identify a person on it's own (e.g. Social security number), but also data used in conjunction with other data to identify a person (e.g. date of birth + city + gender).

For the purposes of this form, "store" includes the following:
* Persisting to storage - Either internal (e.g. Postgres) or external (e.g. S3, etc.) to FOLIO
* Caching - In-memory, etc.
* Logging
* Sending to an external piece of infrastructure such as a queue (e.g. Kafka), search engine (e.g. Elasticsearch), distributed table, etc.

## Personal Data Stored by This Module
- [ ] This module does not store any personal data.
- [ ] This module provides [custom fields](https://github.com/folio-org/folio-custom-fields).
- [ ] This module stores fields with free-form text (tags, notes, descriptions, etc.)
- [ ] This module caches personal data
---
- [ ] First name
- [ ] Last name
- [ ] Middle name
- [x] Pseudonym / Alias / Nickname / Username / User ID
- [ ] Gender
- [ ] Date of birth
- [ ] Place of birth
- [ ] Racial or ethnic origin
- [ ] Address
- [ ] Location information
- [ ] Phone numbers
- [ ] Passport number / National identification numbers
- [ ] Driver’s license number
- [ ] Social security number
- [ ] Email address
- [ ] Web cookies
- [ ] IP address
- [ ] Geolocation data
- [ ] Financial information
- [ ] Logic or algorithms used to build a user/profile
<!--- - [ ] Other personal data - Please list as needed -->
<!--- - [ ] Other personal data - Please list as needed -->

**NOTE** This is not intended to be a comprehensive list, but instead provide a starting point for module developers/maintainers to use.

## Privacy Laws, Regulations, and Policies
The following laws and policies were considered when creating the list of personal data fields above.
* [General Data Protection Regulation (GDPR)](https://gdpr.eu/)
* [California Consumer Privacy Act (CCPA)](https://oag.ca.gov/privacy/ccpa)
* [U.S. Department of Labor: Guidance on the Protection of Personal Identifiable Information](https://www.dol.gov/general/ppii)
* Cybersecurity Law of the People's Republic of China
* https://www.newamerica.org/cybersecurity-initiative/digichina/blog/translation-cybersecurity-law-peoples-republic-china/
* http://en.east-concord.com/zygd/Article/20203/ArticleContent_1690.html?utm_source=Mondaq&utm_medium=syndication&utm_campaign=LinkedIn-integration
* [Personal Data Protection Bill, 2019 (India)](https://www.prsindia.org/billtrack/personal-data-protection-bill-2019)
* [Data protection act 2018 (UK)](https://www.legislation.gov.uk/ukpga/2018/12/section/3/enacted)

---

v1.0
25 changes: 13 additions & 12 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -59,18 +59,19 @@ requires and provides, the permissions, and the additional module metadata.

### Environment variables

| Name | Default value | Description |
|:-----------------------|:---------------:|:--------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| DB_HOST | postgres | Postgres hostname |
| DB_PORT | 5432 | Postgres port |
| DB_USERNAME | folio_admin | Postgres username |
| DB_PASSWORD | - | Postgres username password |
| DB_DATABASE | okapi_modules | Postgres database name |
| KAFKA_HOST | kafka | Kafka broker hostname |
| KAFKA_PORT | 9092 | Kafka broker port |
| ENV | folio | Environment. Logical name of the deployment, must be set if Kafka/Elasticsearch are shared for environments, `a-z (any case)`, `0-9`, `-`, `_` symbols only allowed |
| SYSTEM\_USER\_NAME | dcb-system-user | Username of the system user |
| SYSTEM\_USER\_PASSWORD | - | Password of the system user |
| Name | Default value | Description |
|:-----------------------|:-------------------:|:---------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| DB_HOST | postgres | Postgres hostname |
| DB_PORT | 5432 | Postgres port |
| DB_USERNAME | folio_admin | Postgres username |
| DB_PASSWORD | - | Postgres username password |
| DB_DATABASE | okapi_modules | Postgres database name |
| KAFKA_HOST | kafka | Kafka broker hostname |
| KAFKA_PORT | 9092 | Kafka broker port |
| ENV | folio | Environment. Logical name of the deployment, must be set if Kafka/Elasticsearch are shared for environments, `a-z (any case)`, `0-9`, `-`, `_` symbols only allowed |
| SYSTEM\_USER\_NAME | dcb-system-user | Username of the system user |
| SYSTEM\_USER\_PASSWORD | - | Password of the system user |
| ACTUATOR\_EXPOSURE | health,info,loggers | Back End Module Health Check Protocol |
## Additional information

### System user configuration
Expand Down
Original file line number Diff line number Diff line change
@@ -1,22 +1,22 @@
package org.folio.dcb.client.feign;

import org.folio.dcb.domain.dto.CirculationItemRequest;
import org.folio.dcb.domain.dto.CirculationItem;
import org.folio.dcb.domain.dto.CirculationItemCollection;
import org.folio.spring.config.FeignClientConfiguration;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
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.RequestParam;

@FeignClient(name = "circulation-item", configuration = FeignClientConfiguration.class)
public interface CirculationItemClient {
@PostMapping("/{circulationItemId}")
CirculationItemRequest createCirculationItem(@PathVariable("circulationItemId") String circulationItemId, @RequestBody CirculationItemRequest circulationRequest);
CirculationItem createCirculationItem(@PathVariable("circulationItemId") String circulationItemId, @RequestBody CirculationItem circulationRequest);

@GetMapping("/{circulationItemId}")
CirculationItemRequest retrieveCirculationItemById(@PathVariable("circulationItemId") String circulationItemId);
CirculationItem retrieveCirculationItemById(@PathVariable("circulationItemId") String circulationItemId);

@PutMapping("/{circulationItemId}")
CirculationItemRequest updateCirculationItem(@PathVariable("circulationItemId") String circulationItemId, @RequestBody CirculationItemRequest circulationRequest);
}
@GetMapping("/items")
CirculationItemCollection fetchItemByIdAndBarcode(@RequestParam("query") String query);}
Original file line number Diff line number Diff line change
Expand Up @@ -34,15 +34,21 @@ public Errors handleGlobalException(Exception ex) {
}

@ResponseStatus(HttpStatus.NOT_FOUND)
@ExceptionHandler(NotFoundException.class)
public Errors handleNotFoundException(NotFoundException ex) {
@ExceptionHandler({
NotFoundException.class,
FeignException.NotFound.class
})
public Errors handleNotFoundException(Exception ex) {
logExceptionMessage(ex);
return createExternalError(ex.getMessage(), NOT_FOUND_ERROR);
}

@ResponseStatus(HttpStatus.CONFLICT)
@ExceptionHandler(ResourceAlreadyExistException.class)
public Errors handleAlreadyExistException(ResourceAlreadyExistException ex) {
@ExceptionHandler({
ResourceAlreadyExistException.class,
FeignException.Conflict.class
})
public Errors handleAlreadyExistException(Exception ex) {
logExceptionMessage(ex);
return createExternalError(ex.getMessage(), DUPLICATE_ERROR);
}
Expand All @@ -59,7 +65,8 @@ public Errors handleBadGatewayException(FeignException.BadGateway ex) {
MissingServletRequestParameterException.class,
MethodArgumentTypeMismatchException.class,
HttpMessageNotReadableException.class,
IllegalArgumentException.class
IllegalArgumentException.class,
FeignException.BadRequest.class
})
public Errors handleValidationErrors(Exception ex) {
logExceptionMessage(ex);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import org.folio.dcb.domain.dto.TransactionStatus;
import org.folio.dcb.rest.resource.TransactionsApi;
import org.folio.dcb.domain.dto.TransactionStatusResponse;
import org.folio.dcb.service.TransactionAuditService;
import org.folio.dcb.service.TransactionsService;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
Expand All @@ -17,24 +18,49 @@
public class TransactionApiController implements TransactionsApi {

private final TransactionsService transactionsService;
private final TransactionAuditService transactionAuditService;
@Override
public ResponseEntity<TransactionStatusResponse> getTransactionStatusById(String dcbTransactionId) {
log.info("getTransactionStatus:: by id {} ", dcbTransactionId);
TransactionStatusResponse transactionStatusResponse;
try {
transactionStatusResponse = transactionsService.getTransactionStatusById(dcbTransactionId);
} catch (Exception ex) {
transactionAuditService.logErrorIfTransactionAuditExists(dcbTransactionId, ex.getMessage());
throw ex;
}

return ResponseEntity.status(HttpStatus.OK)
.body(transactionsService.getTransactionStatusById(dcbTransactionId));
.body(transactionStatusResponse);
}

@Override
public ResponseEntity<TransactionStatusResponse> createCirculationRequest(String dcbTransactionId, DcbTransaction dcbTransaction) {
log.info("createCirculationRequest:: creating dcbTransaction {} with id {} ", dcbTransaction, dcbTransactionId);
TransactionStatusResponse transactionStatusResponse;
try {
transactionStatusResponse = transactionsService.createCirculationRequest(dcbTransactionId, dcbTransaction);
} catch (Exception ex) {
transactionAuditService.logErrorIfTransactionAuditNotExists(dcbTransactionId, dcbTransaction, ex.getMessage());
throw ex;
}

return ResponseEntity.status(HttpStatus.CREATED)
.body(transactionsService.createCirculationRequest(dcbTransactionId, dcbTransaction));
.body(transactionStatusResponse);
}

@Override
public ResponseEntity<TransactionStatusResponse> updateTransactionStatus(String dcbTransactionId, TransactionStatus transactionStatus) {
log.info("updateTransactionStatus:: updating dcbTransaction with id {} to status {} ", dcbTransactionId, transactionStatus.getStatus());
TransactionStatusResponse transactionStatusResponse;
try {
transactionStatusResponse = transactionsService.updateTransactionStatus(dcbTransactionId, transactionStatus);
} catch (Exception ex) {
transactionAuditService.logErrorIfTransactionAuditExists(dcbTransactionId, ex.getMessage());
throw ex;
}

return ResponseEntity.status(HttpStatus.OK)
.body(transactionsService.updateTransactionStatus(dcbTransactionId, transactionStatus));
.body(transactionStatusResponse);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ public class TransactionAuditEntity extends TransactionAuditableEntity {
@ColumnTransformer(write = "?::jsonb")
@Column(columnDefinition = "jsonb")
private String after;
private String errorMessage;

private String transactionId;
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import jakarta.persistence.Enumerated;
import jakarta.persistence.Id;
import jakarta.persistence.Table;
import jakarta.persistence.Transient;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
Expand All @@ -19,6 +20,7 @@
import org.folio.dcb.domain.dto.TransactionStatus.StatusEnum;
import org.folio.dcb.listener.entity.TransactionAuditEntityListener;

import java.io.Serializable;
import java.util.UUID;

@Entity
Expand All @@ -30,7 +32,7 @@
@AllArgsConstructor
@NoArgsConstructor
@Builder
public class TransactionEntity extends AuditableEntity {
public class TransactionEntity extends AuditableEntity implements Serializable {
@Id
private String id;
@Convert(converter = UUIDConverter.class)
Expand All @@ -53,6 +55,7 @@ public class TransactionEntity extends AuditableEntity {
private StatusEnum status;
@Enumerated(EnumType.STRING)
private DcbTransaction.RoleEnum role;

@Transient
protected TransactionEntity savedState;

}
Original file line number Diff line number Diff line change
Expand Up @@ -3,32 +3,26 @@
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import jakarta.persistence.EntityManager;
import jakarta.persistence.PostPersist;
import jakarta.persistence.PostLoad;
import jakarta.persistence.PrePersist;
import jakarta.persistence.PreUpdate;
import lombok.extern.log4j.Log4j2;
import org.apache.commons.lang3.SerializationUtils;
import org.folio.dcb.domain.entity.TransactionAuditEntity;
import org.folio.dcb.domain.entity.TransactionEntity;
import org.folio.dcb.service.impl.TransactionsServiceImpl;
import org.folio.dcb.utils.BeanUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.util.HashMap;
import java.util.Map;
import java.util.Objects;

@Component
@Log4j2
public class TransactionAuditEntityListener {
private static final String CREATE_ACTION = "CREATE";
private static final String UPDATE_ACTION = "UPDATE";

@Autowired
private BeanUtil beanUtil;
@Autowired
private ObjectMapper objectMapper;
private static final Map<String, TransactionEntity> originalStateCache = new HashMap<>();

@PrePersist
public void onPrePersist(Object entity) throws JsonProcessingException {
Expand All @@ -44,37 +38,25 @@ public void onPrePersist(Object entity) throws JsonProcessingException {
getEntityManager().persist(transactionAuditEntity);
}

@PostPersist
public void onPostPersist(Object entity) {
TransactionEntity transactionEntity = (TransactionEntity) entity;
originalStateCache.put(transactionEntity.getId(), transactionEntity);
}

@PreUpdate
public void onPreUpdate(Object entity) throws JsonProcessingException {
log.debug("onPreUpdate:: creating transaction audit record");
TransactionEntity transactionEntity = (TransactionEntity) entity;
TransactionAuditEntity transactionAuditEntity = new TransactionAuditEntity();
transactionAuditEntity.setBefore(objectMapper.writeValueAsString(getTransactionEntity(transactionEntity.getId())));
transactionAuditEntity.setBefore(objectMapper.writeValueAsString(transactionEntity.getSavedState()));
transactionAuditEntity.setAfter(objectMapper.writeValueAsString(transactionEntity));
transactionAuditEntity.setTransactionId(transactionEntity.getId());
transactionAuditEntity.setAction(UPDATE_ACTION);

log.info("onPreUpdate:: creating transaction audit record {} with action {}", transactionEntity.getId(), UPDATE_ACTION);
getEntityManager().persist(transactionAuditEntity);
originalStateCache.put(transactionEntity.getId(), transactionEntity);
}

private TransactionEntity getTransactionEntity(String transactionId) {
// Try to get the original state from the cache
TransactionEntity originalState = originalStateCache.get(transactionId);

// If not found, fetch it from the database
if (Objects.isNull(originalState)) {
originalState = getTransactionsService().getTransactionEntityOrThrow(transactionId);
originalStateCache.put(transactionId, originalState);
}
return originalState;
//This method will be invoked when the transactionEntity is loaded and the transactionEntity is stored in a transient field
//The stored value will be used in onPreUpdate method's setBefore method.
@PostLoad
public void saveState(TransactionEntity transactionEntity){
transactionEntity.setSavedState(SerializationUtils.clone((transactionEntity)));
}

//EntityListeners are instantiated by JPA, not Spring,
Expand All @@ -83,7 +65,4 @@ private EntityManager getEntityManager() {
return beanUtil.getBean(EntityManager.class);
}

private TransactionsServiceImpl getTransactionsService() {
return beanUtil.getBean(TransactionsServiceImpl.class);
}
}
Loading

0 comments on commit 59c328e

Please sign in to comment.