From 59ff6dbbf8cdc528bc44e466f866c53c6a6c3574 Mon Sep 17 00:00:00 2001 From: alexanderkurash Date: Wed, 19 Jun 2024 17:33:57 +0300 Subject: [PATCH] CIRC-2111 Initial implementation --- descriptors/ModuleDescriptor-template.json | 80 +++++++++++++ ramls/circulation-setting.json | 33 ++++++ ramls/circulation-settings.json | 23 ++++ ramls/circulation-settings.raml | 105 +++++++++++++++++ ramls/examples/circulation-setting.json | 7 ++ ramls/examples/circulation-settings.json | 12 ++ .../circulation/CirculationVerticle.java | 2 + .../domain/CirculationSetting.java | 32 +++++ .../CirculationSettingsRepository.java | 76 ++++++++++++ .../CirculationSettingsResource.java | 109 ++++++++++++++++++ .../folio/circulation/support/Clients.java | 13 +++ 11 files changed, 492 insertions(+) create mode 100644 ramls/circulation-setting.json create mode 100644 ramls/circulation-settings.json create mode 100644 ramls/circulation-settings.raml create mode 100644 ramls/examples/circulation-setting.json create mode 100644 ramls/examples/circulation-settings.json create mode 100644 src/main/java/org/folio/circulation/domain/CirculationSetting.java create mode 100644 src/main/java/org/folio/circulation/infrastructure/storage/CirculationSettingsRepository.java create mode 100644 src/main/java/org/folio/circulation/resources/CirculationSettingsResource.java diff --git a/descriptors/ModuleDescriptor-template.json b/descriptors/ModuleDescriptor-template.json index b41b0ffe80..e127e3294a 100644 --- a/descriptors/ModuleDescriptor-template.json +++ b/descriptors/ModuleDescriptor-template.json @@ -682,6 +682,61 @@ } ] }, + { + "id": "circulation-settings", + "version": "1.0", + "handlers": [ + { + "methods": [ + "GET" + ], + "pathPattern": "/circulation/settings", + "permissionsRequired": [ + "circulation.settings.collection.get" + ], + "modulePermissions": [ + "circulation-storage.circulation-settings.collection.get" + ] + }, + { + "methods": ["GET"], + "pathPattern": "/circulation/settings/{id}", + "permissionsRequired": [ + "circulation.settings.item.get" + ], + "modulePermissions": [ + "circulation-storage.circulation-settings.item.get" + ] + }, { + "methods": ["PUT"], + "pathPattern": "/circulation/settings/{id}", + "permissionsRequired": [ + "circulation.settings.item.put" + ], + "modulePermissions": [ + "circulation-storage.circulation-settings.item.put" + ] + }, { + "methods": ["POST"], + "pathPattern": "/circulation/settings", + "permissionsRequired": [ + "circulation.settings.item.post" + ], + "modulePermissions": [ + "circulation-storage.circulation-settings.item.post" + ] + }, { + "methods": ["DELETE"], + "pathPattern": "/circulation/settings/{id}", + "permissionsRequired": [ + "circulation.settings.item.delete" + ], + "modulePermissions": [ + "circulation-storage.circulation-settings.item.delete" + ] + } + ] + }, { "id": "_timer", "version": "1.0", @@ -1518,6 +1573,31 @@ "displayName": "circulation settings - Read configuration", "description": "To read the configuration from mod settings." }, + { + "permissionName": "circulation.settings.collection.get", + "displayName": "circulation - get circulation settings", + "description": "get a collection of circulation settings" + }, + { + "permissionName": "circulation.settings.item.get", + "displayName": "circulation - get an individual circulation setting", + "description": "get an individual circulation setting by ID" + }, + { + "permissionName": "circulation.settings.item.put", + "displayName": "circulation - update circulation setting", + "description": "update circulation setting by ID" + }, + { + "permissionName": "circulation.settings.item.post", + "displayName": "circulation - create circulation setting", + "description": "create a new circulation setting" + }, + { + "permissionName": "circulation.settings.item.delete", + "displayName": "circulation - delete circulation setting", + "description": "delete circulation setting by ID" + }, { "permissionName": "circulation.all", "displayName": "circulation - all permissions", diff --git a/ramls/circulation-setting.json b/ramls/circulation-setting.json new file mode 100644 index 0000000000..0907442e8b --- /dev/null +++ b/ramls/circulation-setting.json @@ -0,0 +1,33 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "title": "Circulation Setting Schema", + "description": "Circulation setting", + "type": "object", + "properties": { + "id": { + "description": "ID of the circulation setting", + "type": "string", + "$ref": "raml-util/schemas/uuid.schema" + }, + "name": { + "description": "Circulation setting name", + "type": "string" + }, + "value": { + "description": "Circulation setting", + "type": "object", + "additionalProperties": true + }, + "metadata": { + "description": "Metadata about creation and changes, provided by the server (client should not provide)", + "type": "object", + "$ref": "raml-util/schemas/metadata.schema" + } + }, + "additionalProperties": false, + "required": [ + "id", + "name", + "value" + ] +} diff --git a/ramls/circulation-settings.json b/ramls/circulation-settings.json new file mode 100644 index 0000000000..99173df7ba --- /dev/null +++ b/ramls/circulation-settings.json @@ -0,0 +1,23 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "description": "Collection of Circulation settings", + "type": "object", + "properties": { + "circulationSettings": { + "description": "List of circulation settings", + "id": "circulationSettings", + "type": "array", + "items": { + "type": "object", + "$ref": "circulation-setting.json" + } + }, + "totalRecords": { + "type": "integer" + } + }, + "required": [ + "circulationSettings", + "totalRecords" + ] +} diff --git a/ramls/circulation-settings.raml b/ramls/circulation-settings.raml new file mode 100644 index 0000000000..9af4023eac --- /dev/null +++ b/ramls/circulation-settings.raml @@ -0,0 +1,105 @@ +#%RAML 1.0 +title: Circulation Settings +version: v1.0 +protocols: [ HTTP, HTTPS ] +baseUri: http://localhost:9130 + +documentation: + - title: Circulation Settings API + content: API for circulation settings + +traits: + language: !include raml-util/traits/language.raml + pageable: !include raml-util/traits/pageable.raml + searchable: !include raml-util/traits/searchable.raml + validate: !include raml-util/traits/validation.raml + +types: + circulation-setting: !include circulation-setting.json + circulation-settings: !include circulation-settings.json + errors: !include raml-util/schemas/errors.schema + parameters: !include raml-util/schemas/parameters.schema + +resourceTypes: + collection: !include raml-util/rtypes/collection.raml + collection-item: !include raml-util/rtypes/item-collection.raml + +/circulation/settings: + type: + collection: + exampleCollection: !include examples/circulation-settings.json + exampleItem: !include examples/circulation-setting.json + schemaCollection: circulation-settings + schemaItem: circulation-setting + post: + is: [validate] + description: Create a new circulation setting + body: + application/json: + type: circulation-setting + responses: + 201: + description: "Circulation setting has been created" + body: + application/json: + type: circulation-setting + 500: + description: "Internal server error" + body: + text/plain: + example: "Internal server error" + get: + is: [validate, pageable, searchable: { description: "with valid searchable fields", example: "id=497f6eca-6276-4993-bfeb-98cbbbba8f79" }] + description: Get all circulation settings + responses: + 200: + description: "Circulation settings successfully retreived" + body: + application/json: + type: circulation-settings + 500: + description: "Internal server error" + body: + text/plain: + example: "Internal server error" + /{circulationSettingId}: + type: + collection-item: + exampleItem: !include examples/circulation-setting.json + schema: circulation-setting + get: + responses: + 200: + description: "Circulation setting successfully retreived" + body: + application/json: + type: circulation-setting + 500: + description: "Internal server error" + body: + text/plain: + example: "Internal server error" + put: + is: [ validate ] + body: + application/json: + type: circulation-setting + responses: + 204: + description: "Circulation settings have been saved." + 500: + description: "Internal server error" + body: + text/plain: + example: "Internal server error" + delete: + is: [ validate ] + responses: + 204: + description: "Circulation settings deleted" + 500: + description: "Internal server error" + body: + text/plain: + example: "Internal server error" + diff --git a/ramls/examples/circulation-setting.json b/ramls/examples/circulation-setting.json new file mode 100644 index 0000000000..35f0aba430 --- /dev/null +++ b/ramls/examples/circulation-setting.json @@ -0,0 +1,7 @@ +{ + "id": "497f6eca-6276-4993-bfeb-53cbbbba6f09", + "name": "Sample settings", + "value": { + "org.folio.circulation.settings": "true" + } +} diff --git a/ramls/examples/circulation-settings.json b/ramls/examples/circulation-settings.json new file mode 100644 index 0000000000..b9b7a9c8b2 --- /dev/null +++ b/ramls/examples/circulation-settings.json @@ -0,0 +1,12 @@ +{ + "circulationSettings": [ + { + "id": "497f6eca-6276-4993-bfeb-53cbbbba6f09", + "name": "Sample settings", + "value": { + "org.folio.circulation.settings": "true" + } + } + ], + "totalRecords": 1 +} diff --git a/src/main/java/org/folio/circulation/CirculationVerticle.java b/src/main/java/org/folio/circulation/CirculationVerticle.java index cd6960808b..f0d2371440 100644 --- a/src/main/java/org/folio/circulation/CirculationVerticle.java +++ b/src/main/java/org/folio/circulation/CirculationVerticle.java @@ -10,6 +10,7 @@ import org.folio.circulation.resources.CheckInByBarcodeResource; import org.folio.circulation.resources.CheckOutByBarcodeResource; import org.folio.circulation.resources.CirculationRulesResource; +import org.folio.circulation.resources.CirculationSettingsResource; import org.folio.circulation.resources.ClaimItemReturnedResource; import org.folio.circulation.resources.DeclareClaimedReturnedItemAsMissingResource; import org.folio.circulation.resources.DeclareLostResource; @@ -150,6 +151,7 @@ public void start(Promise startFuture) { // Handlers new LoanRelatedFeeFineClosedHandlerResource(client).register(router); new FeeFineBalanceChangedHandlerResource(client).register(router); + new CirculationSettingsResource(client).register(router); server.requestHandler(router) .listen(config().getInteger("port"), result -> { diff --git a/src/main/java/org/folio/circulation/domain/CirculationSetting.java b/src/main/java/org/folio/circulation/domain/CirculationSetting.java new file mode 100644 index 0000000000..25f3f1bf28 --- /dev/null +++ b/src/main/java/org/folio/circulation/domain/CirculationSetting.java @@ -0,0 +1,32 @@ +package org.folio.circulation.domain; + +import static lombok.AccessLevel.PRIVATE; +import static org.folio.circulation.support.json.JsonPropertyFetcher.getObjectProperty; +import static org.folio.circulation.support.json.JsonPropertyFetcher.getProperty; + +import io.vertx.core.json.JsonObject; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.ToString; + +@AllArgsConstructor(access = PRIVATE) +@ToString(onlyExplicitlyIncluded = true) +public class CirculationSetting { + @ToString.Include + @Getter + private final JsonObject representation; + + @Getter + private final String id; + + @Getter + private final String name; + + @Getter + private final JsonObject value; + + public static CirculationSetting from(JsonObject representation) { + return new CirculationSetting(representation, getProperty(representation, "id"), + getProperty(representation, "name"), getObjectProperty(representation, "value")); + } +} diff --git a/src/main/java/org/folio/circulation/infrastructure/storage/CirculationSettingsRepository.java b/src/main/java/org/folio/circulation/infrastructure/storage/CirculationSettingsRepository.java new file mode 100644 index 0000000000..4795c30f34 --- /dev/null +++ b/src/main/java/org/folio/circulation/infrastructure/storage/CirculationSettingsRepository.java @@ -0,0 +1,76 @@ +package org.folio.circulation.infrastructure.storage; + +import static org.folio.circulation.support.http.ResponseMapping.forwardOnFailure; +import static org.folio.circulation.support.http.ResponseMapping.mapUsingJson; +import static org.folio.circulation.support.results.Result.failed; +import static org.folio.circulation.support.results.ResultBinding.flatMapResult; + +import java.lang.invoke.MethodHandles; +import java.util.concurrent.CompletableFuture; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.folio.circulation.domain.CirculationSetting; +import org.folio.circulation.domain.MultipleRecords; +import org.folio.circulation.support.Clients; +import org.folio.circulation.support.CollectionResourceClient; +import org.folio.circulation.support.FetchSingleRecord; +import org.folio.circulation.support.RecordNotFoundFailure; +import org.folio.circulation.support.http.client.ResponseInterpreter; +import org.folio.circulation.support.results.Result; + +public class CirculationSettingsRepository { + private static final Logger log = LogManager.getLogger(MethodHandles.lookup().lookupClass()); + public static final String RECORDS_PROPERTY_NAME = "circulationSettings"; + private final CollectionResourceClient circulationSettingsStorageClient; + + public CirculationSettingsRepository(Clients clients) { + circulationSettingsStorageClient = clients.circulationSettingsStorageClient(); + } + + public CompletableFuture> getById(String id) { + log.debug("getById:: parameters id: {}", id); + + return FetchSingleRecord.forRecord(RECORDS_PROPERTY_NAME) + .using(circulationSettingsStorageClient) + .mapTo(CirculationSetting::from) + .whenNotFound(failed(new RecordNotFoundFailure(RECORDS_PROPERTY_NAME, id))) + .fetch(id); + } + + public CompletableFuture>> findBy(String query) { + log.debug("findBy:: parameters query: {}", query); + + return circulationSettingsStorageClient.getManyWithRawQueryStringParameters(query) + .thenApply(flatMapResult(response -> + MultipleRecords.from(response, CirculationSetting::from, RECORDS_PROPERTY_NAME))); + } + + public CompletableFuture> create( + CirculationSetting circulationSetting) { + + log.debug("create:: parameters circulationSetting: {}", circulationSetting); + + final var storageCirculationSetting = circulationSetting.getRepresentation(); + + return circulationSettingsStorageClient.post(storageCirculationSetting) + .thenApply(interpreter()::flatMap); + } + + public CompletableFuture> update( + CirculationSetting circulationSetting) { + + log.debug("update:: parameters circulationSetting: {}", circulationSetting); + + final var storageCirculationSetting = circulationSetting.getRepresentation(); + + return circulationSettingsStorageClient.put(circulationSetting.getId(), storageCirculationSetting) + .thenApply(interpreter()::flatMap); + } + + private ResponseInterpreter interpreter() { + return new ResponseInterpreter() + .flatMapOn(201, mapUsingJson(CirculationSetting::from)) + .otherwise(forwardOnFailure()); + } +} diff --git a/src/main/java/org/folio/circulation/resources/CirculationSettingsResource.java b/src/main/java/org/folio/circulation/resources/CirculationSettingsResource.java new file mode 100644 index 0000000000..579e523ae2 --- /dev/null +++ b/src/main/java/org/folio/circulation/resources/CirculationSettingsResource.java @@ -0,0 +1,109 @@ +package org.folio.circulation.resources; + +import static org.folio.circulation.infrastructure.storage.CirculationSettingsRepository.RECORDS_PROPERTY_NAME; +import static org.folio.circulation.support.results.MappingFunctions.toFixedValue; + +import java.lang.invoke.MethodHandles; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.folio.circulation.domain.CirculationSetting; +import org.folio.circulation.infrastructure.storage.CirculationSettingsRepository; +import org.folio.circulation.support.Clients; +import org.folio.circulation.support.http.server.JsonHttpResponse; +import org.folio.circulation.support.http.server.NoContentResponse; +import org.folio.circulation.support.http.server.WebContext; + +import io.vertx.core.http.HttpClient; +import io.vertx.ext.web.RoutingContext; + +public class CirculationSettingsResource extends CollectionResource { + private static final Logger log = LogManager.getLogger(MethodHandles.lookup().lookupClass()); + + public CirculationSettingsResource(HttpClient client) { + super(client, "/circulation/settings"); + } + + @Override + void create(RoutingContext routingContext) { + final var context = new WebContext(routingContext); + final var clients = Clients.create(context, client); + final var circulationSettingsRepository = new CirculationSettingsRepository(clients); + + final var incomingRepresentation = routingContext.body().asJsonObject(); + final var circulationSetting = CirculationSetting.from(incomingRepresentation); + + circulationSettingsRepository.create(circulationSetting) + .thenApply(r -> r.map(CirculationSetting::getRepresentation)) + .thenApply(r -> r.map(JsonHttpResponse::created)) + .thenAccept(context::writeResultToHttpResponse); + } + + @Override + void replace(RoutingContext routingContext) { + final var context = new WebContext(routingContext); + final var clients = Clients.create(context, client); + final var circulationSettingsRepository = new CirculationSettingsRepository(clients); + + final var incomingRepresentation = routingContext.body().asJsonObject(); + final var circulationSetting = CirculationSetting.from(incomingRepresentation); + + circulationSettingsRepository.update(circulationSetting) + .thenApply(r -> r.map(CirculationSetting::getRepresentation)) + .thenApply(r -> r.map(JsonHttpResponse::created)) + .thenAccept(context::writeResultToHttpResponse); + } + + @Override + void get(RoutingContext routingContext) { + final var context = new WebContext(routingContext); + final var clients = Clients.create(context, client); + final var circulationSettingsRepository = new CirculationSettingsRepository(clients); + + final var id = routingContext.request().getParam("id"); + log.debug("get:: Requested circulation setting ID: {}", id); + + circulationSettingsRepository.getById(id) + .thenApply(r -> r.map(CirculationSetting::getRepresentation)) + .thenApply(r -> r.map(JsonHttpResponse::ok)) + .thenAccept(context::writeResultToHttpResponse); + } + + @Override + void delete(RoutingContext routingContext) { + final var context = new WebContext(routingContext); + final var clients = Clients.create(context, client); + + String id = routingContext.request().getParam("id"); + + clients.loansStorage().delete(id) + .thenApply(r -> r.map(toFixedValue(NoContentResponse::noContent))) + .thenAccept(context::writeResultToHttpResponse); + } + + @Override + void getMany(RoutingContext routingContext) { + final var context = new WebContext(routingContext); + final var clients = Clients.create(context, client); + final var circulationSettingsRepository = new CirculationSettingsRepository(clients); + + final var query = routingContext.request().query(); + log.debug("get:: Requested circulation settings by query: {}", query); + + circulationSettingsRepository.findBy(query) + .thenApply(multipleLoanRecordsResult -> multipleLoanRecordsResult.map(multipleRecords -> + multipleRecords.asJson(CirculationSetting::getRepresentation, RECORDS_PROPERTY_NAME))) + .thenApply(r -> r.map(JsonHttpResponse::ok)) + .thenAccept(context::writeResultToHttpResponse); + } + + @Override + void empty(RoutingContext routingContext) { + WebContext context = new WebContext(routingContext); + Clients clients = Clients.create(context, client); + + clients.loansStorage().delete() + .thenApply(r -> r.map(toFixedValue(NoContentResponse::noContent))) + .thenAccept(context::writeResultToHttpResponse); + } +} diff --git a/src/main/java/org/folio/circulation/support/Clients.java b/src/main/java/org/folio/circulation/support/Clients.java index 3ffc41941a..404b433461 100644 --- a/src/main/java/org/folio/circulation/support/Clients.java +++ b/src/main/java/org/folio/circulation/support/Clients.java @@ -69,6 +69,7 @@ public class Clients { private final CollectionResourceClient checkOutLockStorageClient; private final CollectionResourceClient circulationItemClient; private final GetManyRecordsClient settingsStorageClient; + private final CollectionResourceClient circulationSettingsStorageClient; public static Clients create(WebContext context, HttpClient httpClient) { return new Clients(context.createHttpClient(httpClient), context); @@ -136,6 +137,7 @@ private Clients(OkapiHttpClient client, WebContext context) { checkOutLockStorageClient = createCheckoutLockClient(client, context); settingsStorageClient = createSettingsStorageClient(client, context); circulationItemClient = createCirculationItemClient(client, context); + circulationSettingsStorageClient = createCirculationSettingsStorageClient(client, context); } catch(MalformedURLException e) { throw new InvalidOkapiLocationException(context.getOkapiLocation(), e); @@ -374,6 +376,10 @@ public CollectionResourceClient circulationItemClient() { return circulationItemClient; } + public CollectionResourceClient circulationSettingsStorageClient() { + return circulationSettingsStorageClient; + } + private static CollectionResourceClient getCollectionResourceClient( OkapiHttpClient client, WebContext context, String path) @@ -801,6 +807,13 @@ private CollectionResourceClient createCirculationItemClient( return getCollectionResourceClient(client, context, "/circulation-item"); } + private CollectionResourceClient createCirculationSettingsStorageClient( + OkapiHttpClient client, WebContext context) throws MalformedURLException { + + return getCollectionResourceClient(client, context, + "/circulation-settings-storage/circulation-settings"); + } + private GetManyRecordsClient createSettingsStorageClient( OkapiHttpClient client, WebContext context) throws MalformedURLException {