Skip to content

Commit

Permalink
MODSOURCE-859: Create new API POST /source-storage/records/{id}/un-de…
Browse files Browse the repository at this point in the history
…lete
  • Loading branch information
AndreiBordak committed Feb 18, 2025
1 parent 59b04d5 commit 48fc39c
Show file tree
Hide file tree
Showing 7 changed files with 107 additions and 0 deletions.
5 changes: 5 additions & 0 deletions NEWS.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,11 @@
* [MODSOURCE-860](https://folio-org.atlassian.net/browse/MODSOURCE-860) "Numerics only" option of existing record section does not work during MARC-BIB to MARC-BIB matching
* [MODINV-1114](https://folio-org.atlassian.net/browse/MODINV-1114) Extend matching records endpoint to support multiple marc-bib match results processing
* [MODSOURCE-863](https://folio-org.atlassian.net/browse/MODSOURCE-863) Add index to speed up the querying of a composite record
* [MODSOURCE-859](https://folio-org.atlassian.net/browse/MODSOURCE-859) Added record undelete endpoint

| METHOD | URL | DESCRIPTION |
|--------|------------------------------------------|-----------------------------------------------------|
| POST | /source-storage/records/{id}/un-delete | Undelete the record by setting the state to ACTUAL. |

## 2024-10-28 5.9.0
* [MODSOURCE-767](https://folio-org.atlassian.net/browse/MODSOURCE-767) Single record overlay creates duplicate OCLC#/035
Expand Down
15 changes: 15 additions & 0 deletions descriptors/ModuleDescriptor-template.json
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,15 @@
"source-storage.records.generation.item.put"
]
},
{
"methods": [
"POST"
],
"pathPattern": "/source-storage/records/{id}/un-delete",
"permissionsRequired": [
"source-storage.records.undelete.item.post"
]
},
{
"methods": [
"DELETE"
Expand Down Expand Up @@ -356,6 +365,11 @@
"displayName": "Source Storage - update record's generation",
"description": "Update record's generation"
},
{
"permissionName": "source-storage.records.undelete.item.post",
"displayName": "Source Storage - undelete record",
"description": "Undelete record"
},
{
"permissionName": "source-storage.parsed-records.collection.put",
"displayName": "Source Storage - update records",
Expand Down Expand Up @@ -474,6 +488,7 @@
"source-storage.migrations.post",
"source-storage.migrations.item.get",
"source-storage.records.generation.item.put",
"source-storage.records.undelete.item.post",
"source-storage.parsed-records.collection.put",
"source-storage.batch-records.collection.post",
"source-storage.batch.records.collection.post",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -193,4 +193,18 @@ public void postSourceStorageRecordsMatching(RecordMatchingDto recordMatchingDto
});
}

@Override
public void postSourceStorageRecordsUnDeleteById(String id, String idType, Map<String, String> okapiHeaders,
Handler<AsyncResult<Response>> asyncResultHandler, Context vertxContext) {
vertxContext.runOnContext(v -> {
try {
recordService.unDeleteRecordById(id, toExternalIdType(idType), okapiHeaders).map(r -> true)
.map(updated -> PostSourceStorageRecordsUnDeleteByIdResponse.respond204()).map(Response.class::cast)
.otherwise(ExceptionHelper::mapExceptionToResponse).onComplete(asyncResultHandler);
} catch (Exception e) {
LOG.warn("postSourceStorageRecordsUnDeleteById:: Failed to undelete record by id {}", id, e);
asyncResultHandler.handle(Future.succeededFuture(ExceptionHelper.mapExceptionToResponse(e)));
}
});
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -283,4 +283,6 @@ Future<RecordsBatchResponse> saveRecordsByExternalIds(List<String> externalIds,
Future<Void> updateRecordsState(String matchedId, RecordState state, RecordType recordType, String tenantId);

Future<Void> deleteRecordById(String id, IdType idType, Map<String, String> okapiHeaders);

Future<Void> unDeleteRecordById(String id, IdType idType, Map<String, String> okapiHeaders);
}
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,7 @@ public class RecordServiceImpl implements RecordService {
private static final String RECORD_WITH_GIVEN_MATCHED_ID_NOT_FOUND = "Record with given matched id (%s) not found";
private static final String NOT_FOUND_MESSAGE = "%s with id '%s' was not found";
private static final Character DELETED_LEADER_RECORD_STATUS = 'd';
private static final Character CORRECTED_LEADER_RECORD_STATUS = 'c';
public static final String UPDATE_RECORD_DUPLICATE_EXCEPTION = "Incoming record could be a duplicate, incoming record generation should not be the same as matched record generation and the execution of job should be started after of creating the previous record generation";
public static final String EXTERNAL_IDS_MISSING_ERROR = "MARC_BIB records must contain external instance and hr id's and 001 field into parsed record";
protected static final String UPDATE_RECORD_WITH_LINKED_DATA_ID_EXCEPTION = "Record with source=LINKED_DATA cannot be updated using QuickMARC. Please use Linked Data Editor.";
Expand Down Expand Up @@ -421,6 +422,21 @@ public Future<Void> deleteRecordById(String id, IdType idType, Map<String, Strin
.compose(record -> updateRecord(record, okapiHeaders)).map(r -> null);
}

@Override
public Future<Void> unDeleteRecordById(String id, IdType idType, Map<String, String> okapiHeaders) {
var tenantId = okapiHeaders.get(OKAPI_TENANT_HEADER);
return recordDao.getRecordByExternalId(id, idType, tenantId)
.map(recordOptional -> recordOptional.orElseThrow(() -> new NotFoundException(format(NOT_FOUND_MESSAGE, Record.class.getSimpleName(), id))))
.map(record -> {
update005field(record);
record.withState(Record.State.ACTUAL);
record.setAdditionalInfo(record.getAdditionalInfo().withDeleted(false));
ParsedRecordDaoUtil.updateLeaderStatus(record.getParsedRecord(), CORRECTED_LEADER_RECORD_STATUS);
return record;
})
.compose(record -> updateRecord(record, okapiHeaders)).map(r -> null);
}

private Future<Record> setMatchedIdForRecord(Record record, String tenantId) {
String marcField999s = getFieldFromMarcRecord(record, TAG_999, INDICATOR, INDICATOR, SUBFIELD_S);
if (marcField999s != null) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1714,6 +1714,49 @@ public void shouldHardDeleteMarcRecord(TestContext context) {
});
}

@Test
public void shouldUnDeleteMarcRecord(TestContext context) {
Async async = context.async();
var marcBibMock = TestMocks.getMarcBibRecord();
var sourceRecord = new Record()
.withId(UUID.randomUUID().toString())
.withSnapshotId(marcBibMock.getSnapshotId())
.withRecordType(marcBibMock.getRecordType())
.withState(State.DELETED)
.withOrder(marcBibMock.getOrder())
.withRawRecord(rawRecord)
.withParsedRecord(marcRecord)
.withAdditionalInfo(marcBibMock.getAdditionalInfo())
.withExternalIdsHolder(
new ExternalIdsHolder()
.withInstanceId(UUID.randomUUID().toString())
.withInstanceHrid(RandomStringUtils.randomAlphanumeric(9)))
.withMetadata(marcBibMock.getMetadata());

var okapiHeaders = Map.of(OKAPI_TENANT_HEADER, TENANT_ID);

recordService.saveRecord(sourceRecord, okapiHeaders).onComplete(saveResult -> {
if (saveResult.failed()) {
context.fail(saveResult.cause());
}
recordService.unDeleteRecordById(sourceRecord.getId(), IdType.RECORD, okapiHeaders).onComplete(undeleteResult -> {
if (undeleteResult.failed()) {
context.fail(undeleteResult.cause());
}
recordService.getRecordById(sourceRecord.getId(), TENANT_ID).onComplete(getResult -> {
if (getResult.failed()) {
context.fail(getResult.cause());
}
context.assertTrue(getResult.result().isPresent());
context.assertFalse(getResult.result().get().getDeleted());
verify(recordDomainEventPublisher, times(1))
.publishRecordUpdated(eq(saveResult.result()), eq(getResult.result().get()), any());
});
async.complete();
});
});
}

@Test
public void shouldSoftDeleteMarcRecord(TestContext context) {
Async async = context.async();
Expand Down
12 changes: 12 additions & 0 deletions ramls/source-record-storage-records.raml
Original file line number Diff line number Diff line change
Expand Up @@ -197,3 +197,15 @@ resourceTypes:
body:
application/json:
type: record
/un-delete:
displayName: Undelete
post:
description: Undelete specific record
queryParameters:
idType:
description: Type of Id for Record lookup
type: string
example: INSTANCE
default: RECORD
responses:
204:

0 comments on commit 48fc39c

Please sign in to comment.