Skip to content

Commit

Permalink
feat: implement publication deletion workflow (#302)(#295)
Browse files Browse the repository at this point in the history
  • Loading branch information
Maxim-Gadalov authored Apr 11, 2024
1 parent fb8afbd commit 2f4a45f
Show file tree
Hide file tree
Showing 4 changed files with 345 additions and 25 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,9 @@ public Future<?> deletePublication() {
String url = ProxyUtil.convertToObject(body, ResourceLink.class).url();
ResourceDescription resource = decodePublication(url, false);
checkAccess(resource, true);
return vertx.executeBlocking(() -> publicationService.deletePublication(resource));
return vertx.executeBlocking(() ->
lockService.underBucketLock(BlobStorageUtil.PUBLIC_LOCATION,
() -> publicationService.deletePublication(resource, isAdmin())));
})
.onSuccess(publication -> context.respond(HttpStatus.OK))
.onFailure(error -> respondError("Can't delete publication", error));
Expand Down Expand Up @@ -224,7 +226,7 @@ private ResourceDescription decodeRule(String path) {
}

private void checkAccess(ResourceDescription resource, boolean allowUser) {
boolean hasAccess = accessService.hasAdminAccess(context);
boolean hasAccess = isAdmin();

if (!hasAccess && allowUser) {
String bucket = BlobStorageUtil.buildInitiatorBucket(context);
Expand All @@ -236,6 +238,10 @@ private void checkAccess(ResourceDescription resource, boolean allowUser) {
}
}

private boolean isAdmin() {
return accessService.hasAdminAccess(context);
}

private void checkRuleAccess(ResourceDescription rule) {
if (!accessService.hasReadAccess(rule, context)) {
throw new HttpException(HttpStatus.FORBIDDEN, "Forbidden resource: " + rule.getUrl());
Expand Down
2 changes: 1 addition & 1 deletion src/main/java/com/epam/aidial/core/data/Publication.java
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ public class Publication {
List<Rule> rules;

public enum Status {
PENDING, APPROVED, REJECTED
PENDING, APPROVED, REJECTED, REQUESTED_FOR_DELETION
}

@Data
Expand Down
107 changes: 85 additions & 22 deletions src/main/java/com/epam/aidial/core/service/PublicationService.java
Original file line number Diff line number Diff line change
Expand Up @@ -231,38 +231,37 @@ public Publication createPublication(ResourceDescription bucket, Publication pub
return publication;
}

public Publication deletePublication(ResourceDescription resource) {
public Publication deletePublication(ResourceDescription resource, boolean isAdmin) {
if (resource.getType() != ResourceType.PUBLICATION || resource.isPublic() || resource.isFolder() || resource.getParentPath() != null) {
throw new IllegalArgumentException("Bad publication url: " + resource.getUrl());
}

MutableObject<Publication> reference = new MutableObject<>();
// get publication state
Map<String, Publication> userPublications = decodePublications(resources.getResource(publications(resource)));
Publication publication = userPublications.get(resource.getUrl());

resources.computeResource(PUBLIC_PUBLICATIONS, body -> {
Map<String, Publication> publications = decodePublications(body);
Publication publication = publications.remove(resource.getUrl());
return (publication == null) ? body : encodePublications(publications);
});
if (publication == null) {
throw new ResourceNotFoundException("No publication: " + resource.getUrl());
}

resources.computeResource(publications(resource), body -> {
Map<String, Publication> publications = decodePublications(body);
Publication publication = publications.remove(resource.getUrl());
// if admin - delete from any state, if user - delete only PENDING/REJECTED publications
if (isAdmin || isSafeForDeletion(publication)) {
publication = cleanUpPublications(resource);
Publication.Status status = publication.getStatus();

if (publication == null) {
throw new ResourceNotFoundException("No publication: " + resource.getUrl());
if (status == Publication.Status.PENDING) {
deleteReviewResources(publication);
}

reference.setValue(publication);
return encodePublications(publications);
});

Publication publication = reference.getValue();
if (status == Publication.Status.APPROVED || status == Publication.Status.REQUESTED_FOR_DELETION) {
deletePublicResources(publication);
}

if (publication.getStatus() == Publication.Status.PENDING) {
deleteReviewResources(publication);
return publication;
}

return publication;
// if user and resources already published - move publication for REQUESTED_FOR_DELETION state
return preparePublicationForDeletion(resource, publication);
}

@Nullable
Expand Down Expand Up @@ -322,12 +321,15 @@ public Publication rejectPublication(ResourceDescription resource) {
throw new ResourceNotFoundException("No publication: " + resource.getUrl());
}

if (publication.getStatus() != Publication.Status.PENDING) {
boolean isPending = publication.getStatus() == Publication.Status.PENDING;
if (!isPending && publication.getStatus() != Publication.Status.REQUESTED_FOR_DELETION) {
throw new ResourceNotFoundException("Publication is already finalized: " + resource.getUrl());
}

reference.setValue(publication);
publication.setStatus(Publication.Status.REJECTED);
// if publication is PENDING - finalize status to REJECTED,
// if REQUESTED_FOR_DELETION - rollback to APPROVED state, because admin do to want to approve user's request
publication.setStatus(isPending ? Publication.Status.REJECTED : Publication.Status.APPROVED);
return encodePublications(publications);
});

Expand Down Expand Up @@ -436,6 +438,59 @@ private void preparePublication(ResourceDescription bucket, Publication publicat
}
}

private Publication cleanUpPublications(ResourceDescription resource) {
MutableObject<Publication> reference = new MutableObject<>();
resources.computeResource(PUBLIC_PUBLICATIONS, body -> {
Map<String, Publication> publications = decodePublications(body);
Publication publication = publications.remove(resource.getUrl());
return (publication == null) ? body : encodePublications(publications);
});

resources.computeResource(publications(resource), body -> {
Map<String, Publication> publications = decodePublications(body);
Publication publication = publications.remove(resource.getUrl());

if (publication == null) {
throw new ResourceNotFoundException("No publication: " + resource.getUrl());
}

reference.setValue(publication);
return encodePublications(publications);
});
return reference.getValue();
}

private Publication preparePublicationForDeletion(ResourceDescription resource, Publication publication) {
if (publication.getStatus() != Publication.Status.APPROVED) {
throw new IllegalStateException("Expected APPROVED publication, but got " + publication.getStatus());
}
publication.setStatus(Publication.Status.REQUESTED_FOR_DELETION);
MutableObject<Publication> reference = new MutableObject<>();
resources.computeResource(PUBLIC_PUBLICATIONS, body -> {
Map<String, Publication> publications = decodePublications(body);
publications.put(resource.getUrl(), publication);
return encodePublications(publications);
});

resources.computeResource(publications(resource), body -> {
Map<String, Publication> publications = decodePublications(body);
Publication userPublication = publications.get(resource.getUrl());

if (userPublication == null) {
throw new ResourceNotFoundException("No publication: " + resource.getUrl());
}

userPublication.setStatus(Publication.Status.REQUESTED_FOR_DELETION);
reference.setValue(userPublication);
return encodePublications(publications);
});
return reference.getValue();
}

private static boolean isSafeForDeletion(Publication publication) {
return (publication.getStatus() == Publication.Status.PENDING) || (publication.getStatus() == Publication.Status.REJECTED);
}

private void checkSourceResources(Publication publication) {
for (Publication.Resource resource : publication.getResources()) {
String url = resource.getSourceUrl();
Expand Down Expand Up @@ -505,6 +560,14 @@ private void deleteReviewResources(Publication publication) {
}
}

private void deletePublicResources(Publication publication) {
for (Publication.Resource resource : publication.getResources()) {
String url = resource.getTargetUrl();
ResourceDescription descriptor = ResourceDescription.fromPublicUrl(url);
deleteResource(descriptor);
}
}

private boolean checkResource(ResourceDescription descriptor) {
return switch (descriptor.getType()) {
case FILE -> files.exists(descriptor.getAbsoluteFilePath());
Expand Down
Loading

0 comments on commit 2f4a45f

Please sign in to comment.