diff --git a/NEWS.md b/NEWS.md index 88e0e1568..65d890d83 100644 --- a/NEWS.md +++ b/NEWS.md @@ -4,7 +4,6 @@ * Required sourceId field in holdings record ([MODINVSTOR-1161](https://folio-org.atlassian.net/browse/MODINVSTOR-1161)) ### New APIs versions -* Provides `subject-source 1.0` * Provides `subject-types 1.0` * Provides `instance-date-types 1.0` * Provides `instance-storage 10.1` @@ -18,10 +17,6 @@ * Implement a POST request to get Holdings and Instances ([MODINVSTOR-1223](https://folio-org.atlassian.net/browse/MODINVSTOR-1223)) * Implement instance-date-types endpoint ([MODINVSTOR-1235](https://folio-org.atlassian.net/browse/MODINVSTOR-1235)) * Implement Subject types management ([MODINVSTOR-1221](https://folio-org.atlassian.net/browse/MODINVSTOR-1221)) -* Implement endpoint to publish reindex event for the range of instance/item/holding records ([MODINVSTOR-1230](https://folio-org.atlassian.net/browse/MODINVSTOR-1230)) -* Info, not warn, about expected 403 from /user-tenants ([MODINVSTOR-1237](https://folio-org.atlassian.net/browse/MODINVSTOR-1237)) -* Implement Subject sources management ([MODINVSTOR-1222](https://folio-org.atlassian.net/browse/MODINVSTOR-1222)) - ### Bug fixes * Unintended update of instance records \_version (optimistic locking) whenever any of its holdings or items are created, updated or deleted. ([MODINVSTOR-1186](https://folio-org.atlassian.net/browse/MODINVSTOR-1186)) diff --git a/README.MD b/README.MD index 6f31c26ce..10a6e0262 100644 --- a/README.MD +++ b/README.MD @@ -111,8 +111,6 @@ These properties can be changed by setting env variable. * `KAFKA_CAMPUS_TOPIC_NUM_PARTITIONS` Default value - `1` * `KAFKA_INSTITUTION_TOPIC_NUM_PARTITIONS` Default value - `1` * `KAFKA_SUBJECT_TYPE_TOPIC_NUM_PARTITIONS` Default value - `1` -* `KAFKA_REINDEX_RECORDS_TOPIC_NUM_PARTITIONS` Default value - `16` -* `KAFKA_SUBJECT_SOURCE_TOPIC_NUM_PARTITIONS` Default value - `1` # Building @@ -129,9 +127,6 @@ These environment variables configure Kafka, for details see [Kafka](#kafka): * `KAFKA_DOMAIN_TOPIC_NUM_PARTITIONS` * `KAFKA_CLASSIFICATION_TYPE_TOPIC_NUM_PARTITIONS` * `KAFKA_SUBJECT_TYPE_TOPIC_NUM_PARTITIONS` -* `KAFKA_REINDEX_RECORDS_TOPIC_NUM_PARTITIONS` -* `KAFKA_SUBJECT_SOURCE_TOPIC_NUM_PARTITIONS` - These environment variables configure Kafka topic for specific business-related topics * `KAFKA_CLASSIFICATION_TYPE_TOPIC_NUM_PARTITIONS` @@ -140,8 +135,6 @@ These environment variables configure Kafka topic for specific business-related * `KAFKA_CAMPUS_TOPIC_NUM_PARTITIONS` * `KAFKA_INSTITUTION_TOPIC_NUM_PARTITIONS` * `KAFKA_SUBJECT_TYPE_TOPIC_NUM_PARTITIONS` -* `KAFKA_REINDEX_RECORDS_TOPIC_NUM_PARTITIONS` -* `KAFKA_SUBJECT_SOURCE_TOPIC_NUM_PARTITIONS` mod-inventory-storage also supports all Raml Module Builder (RMB) environment variables, for details see [RMB](https://github.com/folio-org/raml-module-builder#environment-variables): diff --git a/descriptors/ModuleDescriptor-template.json b/descriptors/ModuleDescriptor-template.json index 33d4b6ac2..95ee3ded1 100755 --- a/descriptors/ModuleDescriptor-template.json +++ b/descriptors/ModuleDescriptor-template.json @@ -557,33 +557,6 @@ } ] }, - { - "id": "subject-sources", - "version": "1.0", - "handlers": [ - { - "methods": ["GET"], - "pathPattern": "/subject-sources", - "permissionsRequired": ["inventory-storage.subject-sources.collection.get"] - }, { - "methods": ["GET"], - "pathPattern": "/subject-sources/{id}", - "permissionsRequired": ["inventory-storage.subject-sources.item.get"] - }, { - "methods": ["POST"], - "pathPattern": "/subject-sources", - "permissionsRequired": ["inventory-storage.subject-sources.item.post"] - }, { - "methods": ["PUT"], - "pathPattern": "/subject-sources/{id}", - "permissionsRequired": ["inventory-storage.subject-sources.item.put"] - }, { - "methods": ["DELETE"], - "pathPattern": "/subject-sources/{id}", - "permissionsRequired": ["inventory-storage.subject-sources.item.delete"] - } - ] - }, { "id": "contributor-types", "version": "2.0", @@ -649,20 +622,6 @@ } ] }, - { - "id": "inventory-reindex-records", - "version": "1.0", - "handlers": [ - { - "methods": ["POST"], - "pathPattern": "/inventory-reindex-records/publish", - "permissionsRequired": ["inventory-storage.reindex-records.publish.post"], - "modulePermissions": [ - "user-tenants.collection.get" - ] - } - ] - }, { "id": "instance-formats", "version": "2.0", @@ -1960,11 +1919,6 @@ "displayName": "inventory storage - get bulk ids", "description": "get a bulk set of record ids from storage" }, - { - "permissionName": "inventory-storage.reindex-records.publish.post", - "displayName": "inventory storage - publish inventory records for reindex", - "description": "publish inventory storage reindex records" - }, { "permissionName": "inventory-storage.instance-formats.collection.get", "displayName": "inventory storage - get formats collection", @@ -2015,31 +1969,6 @@ "displayName": "inventory storage - delete individual subject type", "description": "delete subject type in storage" }, - { - "permissionName": "inventory-storage.subject-sources.collection.get", - "displayName": "inventory storage - get subject sources collection", - "description": "get subject-sources collection from storage" - }, - { - "permissionName": "inventory-storage.subject-sources.item.get", - "displayName": "inventory storage - get individual subject source", - "description": "get individual subject source from storage" - }, - { - "permissionName": "inventory-storage.subject-sources.item.post", - "displayName": "inventory storage - create individual subject source", - "description": "create individual subject source in storage" - }, - { - "permissionName": "inventory-storage.subject-sources.item.put", - "displayName": "inventory storage - modify subject source", - "description": "modify subject source in storage" - }, - { - "permissionName": "inventory-storage.subject-sources.item.delete", - "displayName": "inventory storage - delete individual subject source", - "description": "delete subject source in storage" - }, { "permissionName": "inventory-storage.instance-types.collection.get", "displayName": "inventory storage - get instance types collection", @@ -2713,11 +2642,6 @@ "inventory-storage.subject-types.item.post", "inventory-storage.subject-types.item.put", "inventory-storage.subject-types.item.delete", - "inventory-storage.subject-sources.collection.get", - "inventory-storage.subject-sources.item.get", - "inventory-storage.subject-sources.item.post", - "inventory-storage.subject-sources.item.put", - "inventory-storage.subject-sources.item.delete", "inventory-storage.instance-types.collection.get", "inventory-storage.instance-types.item.get", "inventory-storage.instance-types.item.post", @@ -2845,7 +2769,6 @@ "inventory-storage.migration.job.post", "inventory-storage.migration.job.item.get", "inventory-storage.migration.item.get", - "inventory-storage.reindex-records.publish.post", "inventory-storage.instance-date-types.collection.get", "inventory-storage.instance-date-types.item.patch" ] @@ -2888,9 +2811,7 @@ { "name": "KAFKA_LIBRARY_TOPIC_NUM_PARTITIONS", "value": "1"}, { "name": "KAFKA_CAMPUS_TOPIC_NUM_PARTITIONS", "value": "1"}, { "name": "KAFKA_INSTITUTION_TOPIC_NUM_PARTITIONS", "value": "1"}, - { "name": "KAFKA_SUBJECT_TYPE_TOPIC_NUM_PARTITIONS", "value": "1"}, - { "name": "KAFKA_SUBJECT_SOURCE_TOPIC_NUM_PARTITIONS", "value": "1"}, - { "name": "KAFKA_REINDEX_RECORDS_TOPIC_NUM_PARTITIONS", "value": "16"} + { "name": "KAFKA_SUBJECT_TYPE_TOPIC_NUM_PARTITIONS", "value": "1"} ] } } diff --git a/ramls/examples/subject-type.json b/ramls/examples/subject-type.json new file mode 100644 index 000000000..f71475a8b --- /dev/null +++ b/ramls/examples/subject-type.json @@ -0,0 +1,5 @@ +{ + "id": "535e3160-763a-42f9-b0c0-d8ed7df6e2a2", + "name": "Personal name", + "source": "folio" +} diff --git a/ramls/examples/subject-types.json b/ramls/examples/subject-types.json new file mode 100644 index 000000000..c3c44c5d0 --- /dev/null +++ b/ramls/examples/subject-types.json @@ -0,0 +1,20 @@ +{ + "subjectTypes": [ + { + "id": "06b2cbd8-66bf-4956-9d90-97c9776365b8", + "name": "Personal name", + "source": "folio" + }, + { + "id": "f9e5b41b-8d5b-47d3-91d0-ca9004796400", + "name": "Occupation", + "source": "folio" + }, + { + "id": "6e09d47d-95e2-4d8a-831b-f777b8ef6d99", + "name": "Phone number", + "source": "local" + } + ], + "totalRecords": 3 +} diff --git a/ramls/subject-type.json b/ramls/subject-type.json index b9808b1d6..968a7daf5 100644 --- a/ramls/subject-type.json +++ b/ramls/subject-type.json @@ -7,12 +7,12 @@ "type": "string" }, "name": { - "description": "label for the subject type", + "description": "label for the identifier type", "type": "string" }, "source": { "type": "string", - "description": "label indicating where the subject type entry originates from, i.e. 'folio' or 'local'", + "description": "label indicating where the identifier type entry originates from, i.e. 'folio' or 'local'", "enum": [ "folio", "local" diff --git a/ramls/subject-type.raml b/ramls/subject-type.raml new file mode 100644 index 000000000..4c31a3a64 --- /dev/null +++ b/ramls/subject-type.raml @@ -0,0 +1,47 @@ +#%RAML 1.0 +title: Subject Types API +version: v1.0 +protocols: [ HTTP, HTTPS ] +baseUri: http://localhost + +documentation: + - title: Subject Types API + content: This documents the API calls that can be made to query and manage subject types + +types: + subjectType: !include subject-type.json + subjectTypes: !include subject-types.json + errors: !include raml-util/schemas/errors.schema + +traits: + pageable: !include raml-util/traits/pageable.raml + searchable: !include raml-util/traits/searchable.raml + validate: !include raml-util/traits/validation.raml + +resourceTypes: + collection: !include raml-util/rtypes/collection.raml + collection-item: !include raml-util/rtypes/item-collection.raml + get-delete-only: !include raml-util/rtypes/get-delete.raml + +/subject-types: + type: + collection: + exampleCollection: !include examples/subject-types.json + exampleItem: !include examples/subject-type.json + schemaCollection: subjectTypes + schemaItem: subjectType + get: + is: [ + searchable: {description: "with valid searchable fields", example: "name=aaa"}, + pageable + ] + description: Return a list of subject types + post: + description: Create a new subject type + is: [validate] + /{subjectTypeId}: + description: Pass in the subject type id + type: + collection-item: + exampleItem: !include examples/subject-type.json + schema: subjectType diff --git a/ramls/subject-types.json b/ramls/subject-types.json new file mode 100644 index 000000000..0a41481e5 --- /dev/null +++ b/ramls/subject-types.json @@ -0,0 +1,24 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "description": "A collection of subject types", + "type": "object", + "properties": { + "subjectTypes": { + "description": "List of subject types", + "id": "subjectType", + "type": "array", + "items": { + "type": "object", + "$ref": "subject-type.json" + } + }, + "totalRecords": { + "description": "Estimated or exact total number of records", + "type": "integer" + } + }, + "required": [ + "subjectTypes", + "totalRecords" + ] +} diff --git a/src/main/java/org/folio/InventoryKafkaTopic.java b/src/main/java/org/folio/InventoryKafkaTopic.java index 9b6210511..c0b9dc8df 100644 --- a/src/main/java/org/folio/InventoryKafkaTopic.java +++ b/src/main/java/org/folio/InventoryKafkaTopic.java @@ -20,9 +20,7 @@ public enum InventoryKafkaTopic implements KafkaTopic { LIBRARY("library"), CAMPUS("campus"), SUBJECT_TYPE("subject-types"), - INSTITUTION("institution"), - REINDEX_RECORDS("reindex-records"), - SUBJECT_SOURCE("subject-sources"); + INSTITUTION("institution"); private static final String DEFAULT_NUM_PARTITIONS_PROPERTY = "KAFKA_DOMAIN_TOPIC_NUM_PARTITIONS"; private static final String DEFAULT_NUM_PARTITIONS_VALUE = "50"; @@ -37,9 +35,7 @@ public enum InventoryKafkaTopic implements KafkaTopic { LIBRARY, Pair.of("KAFKA_LIBRARY_TOPIC_NUM_PARTITIONS", "1"), CAMPUS, Pair.of("KAFKA_CAMPUS_TOPIC_NUM_PARTITIONS", "1"), INSTITUTION, Pair.of("KAFKA_INSTITUTION_TOPIC_NUM_PARTITIONS", "1"), - SUBJECT_TYPE, Pair.of("KAFKA_SUBJECT_TYPE_TOPIC_NUM_PARTITIONS", "1"), - REINDEX_RECORDS, Pair.of("KAFKA_REINDEX_RECORDS_TOPIC_NUM_PARTITIONS", "16"), - SUBJECT_SOURCE, Pair.of("KAFKA_SUBJECT_SOURCE_TOPIC_NUM_PARTITIONS", "1") + SUBJECT_TYPE, Pair.of("KAFKA_SUBJECT_TYPE_TOPIC_NUM_PARTITIONS", "1") ); private final String topic; diff --git a/src/main/java/org/folio/persist/SubjectTypeRepository.java b/src/main/java/org/folio/persist/SubjectTypeRepository.java new file mode 100644 index 000000000..bb66c6b3d --- /dev/null +++ b/src/main/java/org/folio/persist/SubjectTypeRepository.java @@ -0,0 +1,15 @@ +package org.folio.persist; + +import static org.folio.rest.persist.PgUtil.postgresClient; +import static org.folio.services.subjecttype.SubjectTypeService.SUBJECT_TYPE; + +import io.vertx.core.Context; +import java.util.Map; +import org.folio.rest.jaxrs.model.SubjectType; + +public class SubjectTypeRepository extends AbstractRepository { + + public SubjectTypeRepository(Context context, Map okapiHeaders) { + super(postgresClient(context, okapiHeaders), SUBJECT_TYPE, SubjectType.class); + } +} diff --git a/src/main/java/org/folio/rest/impl/SubjectTypeApi.java b/src/main/java/org/folio/rest/impl/SubjectTypeApi.java new file mode 100644 index 000000000..872c13ca7 --- /dev/null +++ b/src/main/java/org/folio/rest/impl/SubjectTypeApi.java @@ -0,0 +1,71 @@ +package org.folio.rest.impl; + +import static io.vertx.core.Future.succeededFuture; +import static org.folio.rest.support.EndpointFailureHandler.handleFailure; + +import io.vertx.core.AsyncResult; +import io.vertx.core.Context; +import io.vertx.core.Handler; +import java.util.Map; +import javax.ws.rs.core.Response; +import org.folio.rest.jaxrs.model.SubjectType; +import org.folio.rest.jaxrs.resource.SubjectTypes; +import org.folio.services.subjecttype.SubjectTypeService; + +public class SubjectTypeApi implements SubjectTypes { + + @Override + public void getSubjectTypes(String query, String totalRecords, int offset, int limit, + Map okapiHeaders, + Handler> asyncResultHandler, + Context vertxContext) { + new SubjectTypeService(vertxContext, okapiHeaders) + .getByQuery(query, offset, limit) + .onSuccess(response -> asyncResultHandler.handle(succeededFuture(response))) + .onFailure(handleFailure(asyncResultHandler)); + } + + @Override + public void postSubjectTypes(SubjectType entity, + Map okapiHeaders, + Handler> asyncResultHandler, + Context vertxContext) { + new SubjectTypeService(vertxContext, okapiHeaders) + .create(entity) + .onSuccess(response -> asyncResultHandler.handle(succeededFuture(response))) + .onFailure(handleFailure(asyncResultHandler)); + } + + @Override + public void getSubjectTypesBySubjectTypeId(String subjectTypeId, + Map okapiHeaders, + Handler> asyncResultHandler, + Context vertxContext) { + new SubjectTypeService(vertxContext, okapiHeaders) + .getById(subjectTypeId) + .onSuccess(response -> asyncResultHandler.handle(succeededFuture(response))) + .onFailure(handleFailure(asyncResultHandler)); + } + + @Override + public void deleteSubjectTypesBySubjectTypeId(String subjectTypeId, + Map okapiHeaders, + Handler> asyncResultHandler, + Context vertxContext) { + new SubjectTypeService(vertxContext, okapiHeaders) + .delete(subjectTypeId) + .onSuccess(response -> asyncResultHandler.handle(succeededFuture(response))) + .onFailure(handleFailure(asyncResultHandler)); + } + + @Override + public void putSubjectTypesBySubjectTypeId(String subjectTypeId, SubjectType entity, + Map okapiHeaders, + Handler> asyncResultHandler, + Context vertxContext) { + new SubjectTypeService(vertxContext, okapiHeaders) + .update(subjectTypeId, entity) + .onSuccess(response -> asyncResultHandler.handle(succeededFuture(response))) + .onFailure(handleFailure(asyncResultHandler)); + } +} diff --git a/src/main/java/org/folio/services/domainevent/SubjectTypeDomainEventPublisher.java b/src/main/java/org/folio/services/domainevent/SubjectTypeDomainEventPublisher.java new file mode 100644 index 000000000..98983fe3e --- /dev/null +++ b/src/main/java/org/folio/services/domainevent/SubjectTypeDomainEventPublisher.java @@ -0,0 +1,40 @@ +package org.folio.services.domainevent; + +import static io.vertx.core.Future.succeededFuture; +import static org.folio.InventoryKafkaTopic.SUBJECT_TYPE; +import static org.folio.rest.tools.utils.TenantTool.tenantId; + +import io.vertx.core.Context; +import io.vertx.core.Future; +import java.util.Collection; +import java.util.List; +import java.util.Map; +import org.apache.commons.lang3.tuple.Pair; +import org.folio.persist.SubjectTypeRepository; +import org.folio.rest.jaxrs.model.SubjectType; + +public class SubjectTypeDomainEventPublisher extends AbstractDomainEventPublisher { + + public SubjectTypeDomainEventPublisher(Context context, Map okapiHeaders) { + super(new SubjectTypeRepository(context, okapiHeaders), + new CommonDomainEventPublisher<>(context, okapiHeaders, SUBJECT_TYPE.fullTopicName(tenantId(okapiHeaders)))); + } + + @Override + protected Future>> getRecordIds(Collection subjectTypes) { + return succeededFuture(subjectTypes.stream() + .map(subjectType -> pair(subjectType.getId(), subjectType)) + .toList() + ); + } + + @Override + protected SubjectType convertDomainToEvent(String instanceId, SubjectType subjectType) { + return subjectType; + } + + @Override + protected String getId(SubjectType subjectType) { + return subjectType.getId(); + } +} diff --git a/src/main/java/org/folio/services/subjecttype/SubjectTypeService.java b/src/main/java/org/folio/services/subjecttype/SubjectTypeService.java index 78895d341..82af7f874 100644 --- a/src/main/java/org/folio/services/subjecttype/SubjectTypeService.java +++ b/src/main/java/org/folio/services/subjecttype/SubjectTypeService.java @@ -6,8 +6,6 @@ import static org.folio.rest.persist.PgUtil.get; import static org.folio.rest.persist.PgUtil.post; import static org.folio.rest.persist.PgUtil.put; -import static org.folio.rest.support.ResponseUtil.SOURCE_CANNOT_BE_FOLIO; -import static org.folio.rest.support.ResponseUtil.SOURCE_CANNOT_BE_UPDATED; import static org.folio.rest.tools.utils.ValidationHelper.createValidationErrorMessage; import io.vertx.core.Context; @@ -28,6 +26,10 @@ public class SubjectTypeService { public static final String SUBJECT_TYPE = "subject_type"; + private static final String SOURCE_CANNOT_BE_FOLIO = + "Illegal operation: Source field cannot be set to folio"; + private static final String SOURCE_CANNOT_BE_UPDATED = + "Illegal operation: Source field cannot be updated"; private final Context context; private final Map okapiHeaders; diff --git a/src/main/resources/templates/db_scripts/addSubjectTypes.sql b/src/main/resources/templates/db_scripts/addSubjectTypes.sql new file mode 100644 index 000000000..39ab7de67 --- /dev/null +++ b/src/main/resources/templates/db_scripts/addSubjectTypes.sql @@ -0,0 +1,76 @@ +INSERT INTO ${myuniversity}_${mymodule}.subject_type (id, jsonb) +VALUES ('d6488f88-1e74-40ce-81b5-b19a928ff5b1', + json_build_object('id','d6488f88-1e74-40ce-81b5-b19a928ff5b1', 'name', 'Personal name', 'source', 'folio')) +ON CONFLICT DO NOTHING; + +INSERT INTO ${myuniversity}_${mymodule}.subject_type (id, jsonb) +VALUES ('d6488f88-1e74-40ce-81b5-b19a928ff5b2', + json_build_object('id','d6488f88-1e74-40ce-81b5-b19a928ff5b2', 'name', 'Corporate name', 'source', 'folio')) +ON CONFLICT DO NOTHING; + +INSERT INTO ${myuniversity}_${mymodule}.subject_type (id, jsonb) +VALUES ('d6488f88-1e74-40ce-81b5-b19a928ff5b3', + json_build_object('id','d6488f88-1e74-40ce-81b5-b19a928ff5b3', 'name', 'Meeting name', 'source', 'folio')) +ON CONFLICT DO NOTHING; + +INSERT INTO ${myuniversity}_${mymodule}.subject_type (id, jsonb) +VALUES ('d6488f88-1e74-40ce-81b5-b19a928ff5b4', + json_build_object('id','d6488f88-1e74-40ce-81b5-b19a928ff5b4', 'name', 'Uniform title', 'source', 'folio')) +ON CONFLICT DO NOTHING; +INSERT INTO ${myuniversity}_${mymodule}.subject_type (id, jsonb) +VALUES ('d6488f88-1e74-40ce-81b5-b19a928ff5b5', + json_build_object('id','d6488f88-1e74-40ce-81b5-b19a928ff5b5', 'name', 'Named event', 'source', 'folio')) +ON CONFLICT DO NOTHING; + +INSERT INTO ${myuniversity}_${mymodule}.subject_type (id, jsonb) +VALUES ('d6488f88-1e74-40ce-81b5-b19a928ff5b6', + json_build_object('id','d6488f88-1e74-40ce-81b5-b19a928ff5b6', 'name', 'Chronological term', 'source', 'folio')) +ON CONFLICT DO NOTHING; + +INSERT INTO ${myuniversity}_${mymodule}.subject_type (id, jsonb) +VALUES ('d6488f88-1e74-40ce-81b5-b19a928ff5b7', + json_build_object('id','d6488f88-1e74-40ce-81b5-b19a928ff5b7', 'name', 'Topical term', 'source', 'folio')) +ON CONFLICT DO NOTHING; + +INSERT INTO ${myuniversity}_${mymodule}.subject_type (id, jsonb) +VALUES ('d6488f88-1e74-40ce-81b5-b19a928ff5b8', + json_build_object('id','d6488f88-1e74-40ce-81b5-b19a928ff5b8', 'name', 'Geographic name', 'source', 'folio')) +ON CONFLICT DO NOTHING; +INSERT INTO ${myuniversity}_${mymodule}.subject_type (id, jsonb) +VALUES ('d6488f88-1e74-40ce-81b5-b19a928ff5b9', + json_build_object('id','d6488f88-1e74-40ce-81b5-b19a928ff5b9', 'name', 'Uncontrolled', 'source', 'folio')) +ON CONFLICT DO NOTHING; + +INSERT INTO ${myuniversity}_${mymodule}.subject_type (id, jsonb) +VALUES ('d6488f88-1e74-40ce-81b5-b19a928ff510', + json_build_object('id','d6488f88-1e74-40ce-81b5-b19a928ff510', 'name', 'Faceted topical terms', 'source', 'folio')) +ON CONFLICT DO NOTHING; + +INSERT INTO ${myuniversity}_${mymodule}.subject_type (id, jsonb) +VALUES ('d6488f88-1e74-40ce-81b5-b19a928ff511', + json_build_object('id','d6488f88-1e74-40ce-81b5-b19a928ff511', 'name', 'Genre/form', 'source', 'folio')) +ON CONFLICT DO NOTHING; + +INSERT INTO ${myuniversity}_${mymodule}.subject_type (id, jsonb) +VALUES ('d6488f88-1e74-40ce-81b5-b19a928ff512', + json_build_object('id','d6488f88-1e74-40ce-81b5-b19a928ff512', 'name', 'Occupation', 'source', 'folio')) +ON CONFLICT DO NOTHING; +INSERT INTO ${myuniversity}_${mymodule}.subject_type (id, jsonb) +VALUES ('d6488f88-1e74-40ce-81b5-b19a928ff513', + json_build_object('id','d6488f88-1e74-40ce-81b5-b19a928ff5b13', 'name', 'Function', 'source', 'folio')) +ON CONFLICT DO NOTHING; + +INSERT INTO ${myuniversity}_${mymodule}.subject_type (id, jsonb) +VALUES ('d6488f88-1e74-40ce-81b5-b19a928ff514', + json_build_object('id','d6488f88-1e74-40ce-81b5-b19a928ff514', 'name', 'Curriculum objective', 'source', 'folio')) +ON CONFLICT DO NOTHING; + +INSERT INTO ${myuniversity}_${mymodule}.subject_type (id, jsonb) +VALUES ('d6488f88-1e74-40ce-81b5-b19a928ff515', + json_build_object('id','d6488f88-1e74-40ce-81b5-b19a928ff515', 'name', 'Hierarchical place name', 'source', 'folio')) +ON CONFLICT DO NOTHING; + +INSERT INTO ${myuniversity}_${mymodule}.subject_type (id, jsonb) +VALUES ('d6488f88-1e74-40ce-81b5-b19a928ff516', + json_build_object('id','d6488f88-1e74-40ce-81b5-b19a928ff516', 'name', 'Type of entity unspecified', 'source', 'folio')) +ON CONFLICT DO NOTHING; diff --git a/src/main/resources/templates/db_scripts/schema.json b/src/main/resources/templates/db_scripts/schema.json index e5ad7d242..6d79845da 100644 --- a/src/main/resources/templates/db_scripts/schema.json +++ b/src/main/resources/templates/db_scripts/schema.json @@ -900,17 +900,6 @@ "tOps": "ADD" } ] - }, - { - "tableName": "subject_source", - "withMetadata": true, - "withAuditing": false, - "uniqueIndex": [ - { - "fieldName": "name", - "tOps": "ADD" - } - ] } ], "scripts": [ @@ -1208,11 +1197,6 @@ "run": "after", "snippetPath": "addSubjectTypes.sql", "fromModuleVersion": "27.2.0" - }, - { - "run": "after", - "snippetPath": "addSubjectSources.sql", - "fromModuleVersion": "27.2.0" } ] } diff --git a/src/test/java/org/folio/rest/api/SubjectTypeTest.java b/src/test/java/org/folio/rest/api/SubjectTypeTest.java new file mode 100644 index 000000000..efd5becdd --- /dev/null +++ b/src/test/java/org/folio/rest/api/SubjectTypeTest.java @@ -0,0 +1,106 @@ +package org.folio.rest.api; + +import static org.folio.rest.support.http.InterfaceUrls.subjectTypesUrl; +import static org.folio.utility.ModuleUtility.getClient; +import static org.folio.utility.RestUtility.TENANT_ID; +import static org.folio.utility.RestUtility.send; +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.junit.jupiter.api.Assertions.assertEquals; + +import io.vertx.core.http.HttpMethod; +import io.vertx.core.json.JsonArray; +import io.vertx.core.json.JsonObject; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; +import org.folio.rest.support.Response; +import org.folio.rest.support.ResponseHandler; +import org.folio.rest.support.http.ResourceClient; +import org.junit.BeforeClass; +import org.junit.Test; + +public class SubjectTypeTest extends TestBase { + + private static ResourceClient subjectTypeClient; + private static final String SUBJECT_TYPE_ID = "d6488f88-1e74-40ce-81b5-b19a928ff5b1"; + + @BeforeClass + public static void beforeAll() { + TestBase.beforeAll(); + subjectTypeClient = ResourceClient.forSubjectTypes(getClient()); + } + + @Test + public void cannotCreateSubjectTypeWithDuplicateName() + throws InterruptedException, TimeoutException, + ExecutionException { + + JsonObject subjectType = new JsonObject() + .put("name", "Topical name") + .put("source", "local"); + + subjectTypeClient.create(subjectType); + + CompletableFuture postCompleted = new CompletableFuture<>(); + getClient().post(subjectTypesUrl(""), subjectType, TENANT_ID, ResponseHandler.json(postCompleted)); + + Response response = postCompleted.get(TIMEOUT, TimeUnit.SECONDS); + assertThat(response.getStatusCode(), is(422)); + + JsonArray errors = response.getJson().getJsonArray("errors"); + assertThat(errors.size(), is(1)); + } + + @Test + public void cannotCreateSubjectTypeWithSourceFolio() { + JsonObject subjectType = new JsonObject() + .put("name", "Topical name2") + .put("source", "folio"); + + Response response = createSubjectType(subjectType); + + JsonArray errors = response.getJson().getJsonArray("errors"); + assertEquals(422, response.getStatusCode()); + assertEquals(1, errors.size()); + assertEquals( + "Illegal operation: Source field cannot be set to folio", + errors.getJsonObject(0).getString("message")); + } + + @Test + public void cannotUpdateSubjectTypeWithSourceFolio() { + JsonObject subjectType = new JsonObject() + .put("name", "Topical name2") + .put("source", "local"); + + Response response = updateSubjectType(SUBJECT_TYPE_ID, subjectType); + + JsonArray errors = response.getJson().getJsonArray("errors"); + assertEquals(422, response.getStatusCode()); + assertEquals(1, errors.size()); + assertEquals( + "Illegal operation: Source field cannot be updated", + errors.getJsonObject(0).getString("message")); + } + + private Response createSubjectType(JsonObject object) { + + CompletableFuture createSubjectType = new CompletableFuture<>(); + + send(subjectTypesUrl("").toString(), HttpMethod.POST, object.toString(), + SUPPORTED_CONTENT_TYPE_JSON_DEF, ResponseHandler.json(createSubjectType)); + + return get(createSubjectType); + } + + private Response updateSubjectType(String id, JsonObject object) { + CompletableFuture updateSubjectType = new CompletableFuture<>(); + + send(subjectTypesUrl("/" + id).toString(), HttpMethod.PUT, object.toString(), + SUPPORTED_CONTENT_TYPE_JSON_DEF, ResponseHandler.json(updateSubjectType)); + + return get(updateSubjectType); + } +} diff --git a/src/test/java/org/folio/rest/api/TestBase.java b/src/test/java/org/folio/rest/api/TestBase.java index 4164d17a4..60aa0c403 100644 --- a/src/test/java/org/folio/rest/api/TestBase.java +++ b/src/test/java/org/folio/rest/api/TestBase.java @@ -52,6 +52,7 @@ public abstract class TestBase { * timeout in seconds for simple requests. Usage: completableFuture.get(TIMEOUT, TimeUnit.SECONDS) */ public static final long TIMEOUT = 100; + public static final String SUPPORTED_CONTENT_TYPE_JSON_DEF = "application/json"; public static ResourceClient holdingsClient; protected static final Logger logger = LogManager.getLogger(); protected static ResourceClient instancesClient; diff --git a/src/test/java/org/folio/rest/impl/SubjectTypesIT.java b/src/test/java/org/folio/rest/impl/SubjectTypesIT.java new file mode 100644 index 000000000..7818911bb --- /dev/null +++ b/src/test/java/org/folio/rest/impl/SubjectTypesIT.java @@ -0,0 +1,71 @@ +package org.folio.rest.impl; + +import java.util.List; +import java.util.UUID; +import java.util.function.Function; +import java.util.function.UnaryOperator; +import org.folio.rest.jaxrs.model.Metadata; +import org.folio.rest.jaxrs.model.SubjectType; +import org.folio.rest.jaxrs.model.SubjectTypes; +import org.folio.services.subjecttype.SubjectTypeService; + +public class SubjectTypesIT extends BaseReferenceDataIntegrationTest { + + @Override + protected String referenceTable() { + return SubjectTypeService.SUBJECT_TYPE; + } + + @Override + protected String resourceUrl() { + return "/subject-types"; + } + + @Override + protected Class targetClass() { + return SubjectType.class; + } + + @Override + protected Class collectionClass() { + return SubjectTypes.class; + } + + @Override + protected SubjectType sampleRecord() { + return new SubjectType() + .withId(UUID.randomUUID().toString()) + .withName("Controlled") + .withSource(SubjectType.Source.LOCAL); + } + + @Override + protected Function> collectionRecordsExtractor() { + return SubjectTypes::getSubjectTypes; + } + + @Override + protected List> recordFieldExtractors() { + return List.of(SubjectType::getName, SubjectType::getSource); + } + + @Override + protected Function idExtractor() { + return SubjectType::getId; + } + + @Override + protected Function metadataExtractor() { + return SubjectType::getMetadata; + } + + @Override + protected UnaryOperator recordModifyingFunction() { + return subjectType -> subjectType.withName("updated"); + } + + @Override + protected List queries() { + return List.of("name==Controlled"); + } +} diff --git a/src/test/java/org/folio/rest/support/http/InterfaceUrls.java b/src/test/java/org/folio/rest/support/http/InterfaceUrls.java index 10dabb88d..35159f990 100644 --- a/src/test/java/org/folio/rest/support/http/InterfaceUrls.java +++ b/src/test/java/org/folio/rest/support/http/InterfaceUrls.java @@ -26,10 +26,6 @@ public static URL subjectTypesUrl(String subPath) { return vertxUrl("/subject-types" + subPath); } - public static URL subjectSourcesUrl(String subPath) { - return vertxUrl("/subject-sources" + subPath); - } - public static URL itemsStorageUrl(String subPath) { return vertxUrl("/item-storage/items" + subPath); } diff --git a/src/test/java/org/folio/rest/support/http/ResourceClient.java b/src/test/java/org/folio/rest/support/http/ResourceClient.java index 007fab387..b259f1e5b 100644 --- a/src/test/java/org/folio/rest/support/http/ResourceClient.java +++ b/src/test/java/org/folio/rest/support/http/ResourceClient.java @@ -126,11 +126,6 @@ public static ResourceClient forSubjectTypes(HttpClient client) { "subject types", "subjectTypes"); } - public static ResourceClient forSubjectSources(HttpClient client) { - return new ResourceClient(client, InterfaceUrls::subjectSourcesUrl, - "subject sources", "subjectSources"); - } - public static ResourceClient forCallNumberTypes(HttpClient client) { return new ResourceClient(client, InterfaceUrls::callNumberTypesUrl, "call number types", "callNumberTypes"); diff --git a/src/test/java/org/folio/services/kafka/topic/KafkaAdminClientServiceTest.java b/src/test/java/org/folio/services/kafka/topic/KafkaAdminClientServiceTest.java index 4535cd584..b41ae80a3 100644 --- a/src/test/java/org/folio/services/kafka/topic/KafkaAdminClientServiceTest.java +++ b/src/test/java/org/folio/services/kafka/topic/KafkaAdminClientServiceTest.java @@ -42,8 +42,7 @@ public class KafkaAdminClientServiceTest { "folio.foo-tenant.inventory.service-point", "folio.foo-tenant.inventory.classification-type", "folio.foo-tenant.inventory.location", "folio.foo-tenant.inventory.library", "folio.foo-tenant.inventory.campus", "folio.foo-tenant.inventory.subject-types", - "folio.foo-tenant.inventory.institution", "folio.foo-tenant.inventory.reindex-records", - "folio.foo-tenant.inventory.subject-sources"); + "folio.foo-tenant.inventory.institution"); private KafkaAdminClient mockClient; private Vertx vertx;