Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

CIRC-2099 POST Api implementation #1481

Merged
merged 17 commits into from
Jul 29, 2024
Merged
Show file tree
Hide file tree
Changes from 5 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
25 changes: 25 additions & 0 deletions descriptors/ModuleDescriptor-template.json
Original file line number Diff line number Diff line change
Expand Up @@ -737,6 +737,22 @@
}
]
},
{
"id": "print-events",
"version": "1.0",
"handlers": [
{
"methods": ["POST"],
"pathPattern": "/circulation/print-events",
"permissionsRequired": [
"circulation.print-events.post"
],
"modulePermissions": [
"circulation-storage.print-events.post"
]
}
]
},
{
"id": "_timer",
"version": "1.0",
Expand Down Expand Up @@ -1318,6 +1334,10 @@
{
"id": "circulation-settings-storage",
"version": "1.0"
},
{
"id": "print-events-storage",
"version": "1.0"
}
],
"optional": [
Expand All @@ -1327,6 +1347,11 @@
}
],
"permissionSets": [
{
"permissionName": "circulation.print-events.post",
"displayName": "circulation - create print events",
"description": "create print event logs"
},
{
"permissionName": "circulation.requests.queue.reorder.collection.post",
"displayName": "circulation - reorder queue for an item",
Expand Down
2 changes: 2 additions & 0 deletions src/main/java/org/folio/circulation/CirculationVerticle.java
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
import org.folio.circulation.resources.OverdueFineCirculationRulesEngineResource;
import org.folio.circulation.resources.OverdueFineScheduledNoticeProcessingResource;
import org.folio.circulation.resources.PickSlipsResource;
import org.folio.circulation.resources.PrintEventsResource;
import org.folio.circulation.resources.RequestByInstanceIdResource;
import org.folio.circulation.resources.RequestCirculationRulesEngineResource;
import org.folio.circulation.resources.RequestCollectionResource;
Expand Down Expand Up @@ -152,6 +153,7 @@ public void start(Promise<Void> startFuture) {
new LoanRelatedFeeFineClosedHandlerResource(client).register(router);
new FeeFineBalanceChangedHandlerResource(client).register(router);
new CirculationSettingsResource(client).register(router);
new PrintEventsResource(client).register(router);

server.requestHandler(router)
.listen(config().getInteger("port"), result -> {
Expand Down
59 changes: 59 additions & 0 deletions src/main/java/org/folio/circulation/domain/PrintEventRequest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
package org.folio.circulation.domain;

import io.vertx.core.json.JsonObject;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.ToString;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

import java.lang.invoke.MethodHandles;
import java.util.List;
import java.util.Set;

import static lombok.AccessLevel.PRIVATE;
import static org.folio.circulation.support.json.JsonPropertyFetcher.getArrayProperty;
import static org.folio.circulation.support.json.JsonPropertyFetcher.getProperty;

@AllArgsConstructor(access = PRIVATE)
@ToString(onlyExplicitlyIncluded = true)
public class PrintEventRequest {
private static final Logger log = LogManager.getLogger(MethodHandles.lookup().lookupClass());
public static final String REQUEST_IDS_FIELD = "requestIds";
public static final String REQUESTER_ID_FIELD = "requesterId";
public static final String REQUESTER_NAME_FIELD = "requesterName";
public static final String PRINT_DATE_FIELD = "printEventDate";

@ToString.Include
@Getter
private final JsonObject representation;

@Getter
private final List<String> requestIds;
@Getter
private final String requesterId;
@Getter
private final String requesterName;
@Getter
private final String printEventDate;

public static PrintEventRequest from(JsonObject representation) {
final var requestIds = getArrayProperty(representation, REQUEST_IDS_FIELD).stream()
.map(String.class::cast)
.toList();
final var requesterId = getProperty(representation, REQUESTER_ID_FIELD);
final var requesterName = getProperty(representation, REQUESTER_NAME_FIELD);
final var printEventDate = getProperty(representation, PRINT_DATE_FIELD);

if (requestIds.isEmpty() || null == requesterId || null == requesterName || null == printEventDate || !containsOnlyKnownFields(representation)) {
log.info("from:: Print Event Request JSON is invalid: {},{},{},{},{}", representation, requestIds, requesterName, requesterId, printEventDate);
return null;
}
return new PrintEventRequest(representation, requestIds, requesterId, requesterName, printEventDate);
}

private static boolean containsOnlyKnownFields(JsonObject representation) {
return Set.of(REQUEST_IDS_FIELD, REQUESTER_ID_FIELD, REQUESTER_NAME_FIELD, PRINT_DATE_FIELD)
.containsAll(representation.fieldNames());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package org.folio.circulation.infrastructure.storage;

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.folio.circulation.domain.PrintEventRequest;
import org.folio.circulation.support.Clients;
import org.folio.circulation.support.CollectionResourceClient;
import org.folio.circulation.support.http.client.ResponseInterpreter;
import org.folio.circulation.support.results.Result;

import java.lang.invoke.MethodHandles;
import java.util.concurrent.CompletableFuture;

import static org.folio.circulation.support.http.ResponseMapping.forwardOnFailure;
import static org.folio.circulation.support.results.Result.succeeded;

public class PrintEventsRepository {
private static final Logger log = LogManager.getLogger(MethodHandles.lookup().lookupClass());

private final CollectionResourceClient printEventsStorageClient;

public PrintEventsRepository(Clients clients) {
printEventsStorageClient = clients.printEventsStorageClient();
}

public CompletableFuture<Result<Void>> create(PrintEventRequest printEventRequest) {
log.info("create:: parameters printEvent: {}", printEventRequest);
final var storagePrintEventRequest = printEventRequest.getRepresentation();
final ResponseInterpreter<Void> interpreter = new ResponseInterpreter<Void>()
.on(201, succeeded(null))
.otherwise(forwardOnFailure());
return printEventsStorageClient.post(storagePrintEventRequest).thenApply(interpreter::flatMap);
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
package org.folio.circulation.resources;

import io.vertx.core.http.HttpClient;
import io.vertx.ext.web.Router;
import io.vertx.ext.web.RoutingContext;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.folio.circulation.domain.PrintEventRequest;
import org.folio.circulation.infrastructure.storage.PrintEventsRepository;
import org.folio.circulation.support.Clients;
import org.folio.circulation.support.RouteRegistration;
import org.folio.circulation.support.http.server.JsonHttpResponse;
import org.folio.circulation.support.http.server.WebContext;
import org.folio.circulation.support.results.Result;

import java.lang.invoke.MethodHandles;
import java.util.function.Function;

import static org.folio.circulation.support.ValidationErrorFailure.singleValidationError;
import static org.folio.circulation.support.results.Result.ofAsync;
import static org.folio.circulation.support.results.Result.succeeded;

public class PrintEventsResource extends Resource {
private static final Logger log = LogManager.getLogger(MethodHandles.lookup().lookupClass());

public PrintEventsResource(HttpClient client) {
super(client);
}

@Override
public void register(Router router) {
new RouteRegistration("/circulation/print-events", router)
.create(this::create);
}

void create(RoutingContext routingContext) {
final var context = new WebContext(routingContext);
final var clients = Clients.create(context, client);
final var printEventsRepository = new PrintEventsRepository(clients);
final var incomingRepresentation = routingContext.body().asJsonObject();
final var printEventRequest = PrintEventRequest.from(incomingRepresentation);

log.info("create:: Creating print event: {}", printEventRequest);

ofAsync(printEventRequest)
.thenApply(refuseWhenPrintEventRequestIsInvalid())
.thenCompose(r -> r.after(printEventsRepository::create))
.thenApply(r -> r.map(response -> {
assert printEventRequest != null;
return JsonHttpResponse.created(printEventRequest.getRepresentation(), null);
}))
.thenAccept(context::writeResultToHttpResponse);
}

private static Function<Result<PrintEventRequest>, Result<PrintEventRequest>>
refuseWhenPrintEventRequestIsInvalid() {
return r -> r.failWhen(printEventRequest -> succeeded(printEventRequest == null),
circulationSetting -> singleValidationError("Print Event Request JSON is invalid", "", ""));
}
}
15 changes: 15 additions & 0 deletions src/main/java/org/folio/circulation/support/Clients.java
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,8 @@ public class Clients {
private final CollectionResourceClient circulationItemClient;
private final GetManyRecordsClient settingsStorageClient;
private final CollectionResourceClient circulationSettingsStorageClient;
private final CollectionResourceClient printEventsStorageClient;


public static Clients create(WebContext context, HttpClient httpClient) {
return new Clients(context.createHttpClient(httpClient), context);
Expand Down Expand Up @@ -138,6 +140,8 @@ private Clients(OkapiHttpClient client, WebContext context) {
settingsStorageClient = createSettingsStorageClient(client, context);
circulationItemClient = createCirculationItemClient(client, context);
circulationSettingsStorageClient = createCirculationSettingsStorageClient(client, context);
printEventsStorageClient = createPrintEventsStorageClient(client, context);

}
catch(MalformedURLException e) {
throw new InvalidOkapiLocationException(context.getOkapiLocation(), e);
Expand Down Expand Up @@ -380,6 +384,10 @@ public CollectionResourceClient circulationSettingsStorageClient() {
return circulationSettingsStorageClient;
}

public CollectionResourceClient printEventsStorageClient() {
return printEventsStorageClient;
}

private static CollectionResourceClient getCollectionResourceClient(
OkapiHttpClient client, WebContext context,
String path)
Expand Down Expand Up @@ -814,6 +822,13 @@ private CollectionResourceClient createCirculationSettingsStorageClient(
"/circulation-settings-storage/circulation-settings");
}

private CollectionResourceClient createPrintEventsStorageClient(
OkapiHttpClient client, WebContext context) throws MalformedURLException {

return getCollectionResourceClient(client, context,
"/print-events-storage/print-events");
}

private GetManyRecordsClient createSettingsStorageClient(
OkapiHttpClient client, WebContext context)
throws MalformedURLException {
Expand Down
62 changes: 62 additions & 0 deletions src/test/java/api/printEvents/PrintEventsTests.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
package api.printEvents;

import api.support.APITests;
import io.vertx.core.json.JsonObject;
import org.folio.circulation.support.http.client.Response;
import org.junit.jupiter.api.Test;

import java.util.List;

import static api.support.matchers.ResponseStatusCodeMatcher.hasStatus;
import static org.folio.HttpStatus.HTTP_CREATED;
import static org.folio.HttpStatus.HTTP_UNPROCESSABLE_ENTITY;
import static org.hamcrest.MatcherAssert.assertThat;

class PrintEventsTests extends APITests {
public static final String REQUEST_IDS_FIELD = "requestIds";
public static final String REQUESTER_ID_FIELD = "requesterId";
public static final String REQUESTER_NAME_FIELD = "requesterName";
public static final String PRINT_DATE_FIELD = "printEventDate";
public static final String INVALID_FIELD = "invalidField";

@Test
void postPrintEventsTest() {
JsonObject printRequest = getPrintEvent();
Response response = printEventsClient.attemptCreate(printRequest);
assertThat(response, hasStatus(HTTP_CREATED));
}

@Test
void postPrintEventsWithInvalidField() {
JsonObject printRequest = getPrintEvent();
printRequest.put(INVALID_FIELD, "invalid");
Response response = printEventsClient.attemptCreate(printRequest);
assertThat(response, hasStatus(HTTP_UNPROCESSABLE_ENTITY));
}

@Test
void postPrintEventsWithInvalidField_EmptyRequestIdsList() {
JsonObject printRequest = getPrintEvent();
List<String> requestIds = List.of();
printRequest.put(REQUEST_IDS_FIELD, requestIds);
Response response = printEventsClient.attemptCreate(printRequest);
assertThat(response, hasStatus(HTTP_UNPROCESSABLE_ENTITY));
}

@Test
void postPrintEventsWithInvalidField_NullFeild() {
JsonObject printRequest = getPrintEvent();
printRequest.put(REQUESTER_ID_FIELD, null);
Response response = printEventsClient.attemptCreate(printRequest);
assertThat(response, hasStatus(HTTP_UNPROCESSABLE_ENTITY));
}

private JsonObject getPrintEvent() {
List<String> requestIds = List.of("request1", "request2");
return new JsonObject()
.put(REQUEST_IDS_FIELD, requestIds)
.put(REQUESTER_ID_FIELD, "sreeja")
.put(REQUESTER_NAME_FIELD, "Sample Requester")
.put(PRINT_DATE_FIELD, "2024-06-25T14:30:00Z");
}
}
3 changes: 3 additions & 0 deletions src/test/java/api/support/APITests.java
Original file line number Diff line number Diff line change
Expand Up @@ -197,6 +197,9 @@ public abstract class APITests {
protected final ResourceClient circulationSettingsClient =
ResourceClient.forCirculationSettings();

protected final ResourceClient printEventsClient =
ResourceClient.forPrintEvents();

protected final ServicePointsFixture servicePointsFixture
= new ServicePointsFixture(servicePointsClient);

Expand Down
5 changes: 5 additions & 0 deletions src/test/java/api/support/fakes/FakeOkapi.java
Original file line number Diff line number Diff line change
Expand Up @@ -422,6 +422,11 @@ public void start(Promise<Void> startFuture) throws IOException {
.withChangeMetadata()
.create().register(router);

new FakeStorageModuleBuilder()
.withRootPath("/print-events-storage/print-events")
.withChangeMetadata()
.create().register(router);

new FakeFeeFineOperationsModule().register(router);

server.requestHandler(router)
Expand Down
4 changes: 4 additions & 0 deletions src/test/java/api/support/http/InterfaceUrls.java
Original file line number Diff line number Diff line change
Expand Up @@ -337,4 +337,8 @@ public static URL settingsStorageUrl() {
public static URL circulationSettingsUrl(String subPath) {
return circulationModuleUrl("/circulation/settings" + subPath);
}

public static URL printEventsUrl(String subPath) {
return circulationModuleUrl("/circulation/print-events" + subPath);
}
}
4 changes: 4 additions & 0 deletions src/test/java/api/support/http/ResourceClient.java
Original file line number Diff line number Diff line change
Expand Up @@ -276,6 +276,10 @@ public static ResourceClient forCirculationSettings() {
return new ResourceClient(InterfaceUrls::circulationSettingsUrl, "circulationSettings");
}

public static ResourceClient forPrintEvents() {
return new ResourceClient(InterfaceUrls::printEventsUrl, " ");
}

private ResourceClient(UrlMaker urlMaker, String collectionArrayPropertyName) {
this.urlMaker = urlMaker;
this.collectionArrayPropertyName = collectionArrayPropertyName;
Expand Down