mirror of
https://github.com/open-metadata/OpenMetadata.git
synced 2025-09-20 06:21:45 +00:00
* Fix #6603 Backend: Add support for delete API for threads * Fix compilation error
This commit is contained in:
parent
bd5ac5c67f
commit
c968fc8912
@ -578,6 +578,9 @@ public interface CollectionDAO {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@SqlUpdate("DELETE FROM thread_entity WHERE id = :id")
|
||||||
|
void delete(@Bind("id") String id);
|
||||||
|
|
||||||
@ConnectionAwareSqlUpdate(value = "UPDATE task_sequence SET id=LAST_INSERT_ID(id+1)", connectionType = MYSQL)
|
@ConnectionAwareSqlUpdate(value = "UPDATE task_sequence SET id=LAST_INSERT_ID(id+1)", connectionType = MYSQL)
|
||||||
@ConnectionAwareSqlUpdate(value = "UPDATE task_sequence SET id=(id+1) RETURNING id", connectionType = POSTGRES)
|
@ConnectionAwareSqlUpdate(value = "UPDATE task_sequence SET id=(id+1) RETURNING id", connectionType = POSTGRES)
|
||||||
void updateTaskId();
|
void updateTaskId();
|
||||||
@ -1081,11 +1084,11 @@ public interface CollectionDAO {
|
|||||||
@Bind("isResolved") boolean isResolved);
|
@Bind("isResolved") boolean isResolved);
|
||||||
|
|
||||||
@SqlQuery(
|
@SqlQuery(
|
||||||
"SELECT entityLink, COUNT(id) count FROM thread_entity WHERE resolved = :resolved AND "
|
"SELECT entityLink, COUNT(id) count FROM thread_entity WHERE resolved = :resolved AND (:type IS NULL OR type = :type) AND "
|
||||||
+ "(:type IS NULL OR type = :type) AND id in (SELECT toId FROM entity_relationship WHERE "
|
+ "(entityId in (SELECT toId FROM entity_relationship WHERE "
|
||||||
+ "(((fromEntity='user' AND fromId= :userId) OR "
|
+ "((fromEntity='user' AND fromId= :userId) OR "
|
||||||
+ "(fromEntity='team' AND fromId IN (<teamIds>))) AND relation=8) OR "
|
+ "(fromEntity='team' AND fromId IN (<teamIds>))) AND relation=8) OR "
|
||||||
+ "(fromEntity='user' AND fromId= :userId AND toEntity='THREAD' AND relation IN (1,2))) "
|
+ "id in (SELECT toId FROM entity_relationship WHERE (fromEntity='user' AND fromId= :userId AND toEntity='THREAD' AND relation IN (1,2)))) "
|
||||||
+ "GROUP BY entityLink")
|
+ "GROUP BY entityLink")
|
||||||
@RegisterRowMapper(CountFieldMapper.class)
|
@RegisterRowMapper(CountFieldMapper.class)
|
||||||
List<List<String>> listCountByOwner(
|
List<List<String>> listCountByOwner(
|
||||||
|
@ -505,8 +505,25 @@ public class FeedRepository {
|
|||||||
return new DeleteResponse<>(post, RestUtil.ENTITY_DELETED);
|
return new DeleteResponse<>(post, RestUtil.ENTITY_DELETED);
|
||||||
}
|
}
|
||||||
|
|
||||||
public EntityReference getOwnerOfPost(Post post) {
|
@Transaction
|
||||||
User fromUser = dao.userDAO().findEntityByName(post.getFrom());
|
public DeleteResponse<Thread> deleteThread(Thread thread, String deletedByUser) throws IOException {
|
||||||
|
String id = thread.getId().toString();
|
||||||
|
|
||||||
|
// Delete all the relationships to other entities
|
||||||
|
dao.relationshipDAO().deleteAll(id, Entity.THREAD);
|
||||||
|
|
||||||
|
// Delete all the field relationships to other entities
|
||||||
|
dao.fieldRelationshipDAO().deleteAllByPrefix(id);
|
||||||
|
|
||||||
|
// Finally, delete the entity
|
||||||
|
dao.feedDAO().delete(id);
|
||||||
|
|
||||||
|
LOG.info("{} deleted thread with id {}", deletedByUser, thread.getId());
|
||||||
|
return new DeleteResponse<>(thread, RestUtil.ENTITY_DELETED);
|
||||||
|
}
|
||||||
|
|
||||||
|
public EntityReference getOwnerReference(String username) {
|
||||||
|
User fromUser = dao.userDAO().findEntityByName(username);
|
||||||
return fromUser.getEntityReference();
|
return fromUser.getEntityReference();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -67,6 +67,7 @@ import org.openmetadata.catalog.security.Authorizer;
|
|||||||
import org.openmetadata.catalog.security.policyevaluator.OperationContext;
|
import org.openmetadata.catalog.security.policyevaluator.OperationContext;
|
||||||
import org.openmetadata.catalog.security.policyevaluator.PostResourceContext;
|
import org.openmetadata.catalog.security.policyevaluator.PostResourceContext;
|
||||||
import org.openmetadata.catalog.security.policyevaluator.ResourceContextInterface;
|
import org.openmetadata.catalog.security.policyevaluator.ResourceContextInterface;
|
||||||
|
import org.openmetadata.catalog.security.policyevaluator.ThreadResourceContext;
|
||||||
import org.openmetadata.catalog.type.CreateTaskDetails;
|
import org.openmetadata.catalog.type.CreateTaskDetails;
|
||||||
import org.openmetadata.catalog.type.EntityReference;
|
import org.openmetadata.catalog.type.EntityReference;
|
||||||
import org.openmetadata.catalog.type.MetadataOperation;
|
import org.openmetadata.catalog.type.MetadataOperation;
|
||||||
@ -510,6 +511,33 @@ public class FeedResource {
|
|||||||
return response.toResponse();
|
return response.toResponse();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@DELETE
|
||||||
|
@Path("/{threadId}")
|
||||||
|
@Operation(
|
||||||
|
operationId = "deleteThread",
|
||||||
|
summary = "Delete a thread",
|
||||||
|
tags = "feeds",
|
||||||
|
description = "Delete an existing thread and all its relationships.",
|
||||||
|
responses = {
|
||||||
|
@ApiResponse(responseCode = "200", description = "OK"),
|
||||||
|
@ApiResponse(responseCode = "404", description = "thread with {threadId} is not found"),
|
||||||
|
@ApiResponse(responseCode = "400", description = "Bad request")
|
||||||
|
})
|
||||||
|
public Response deleteThread(
|
||||||
|
@Context SecurityContext securityContext,
|
||||||
|
@Parameter(description = "ThreadId of the thread to be deleted", schema = @Schema(type = "string"))
|
||||||
|
@PathParam("threadId")
|
||||||
|
String threadId)
|
||||||
|
throws IOException {
|
||||||
|
// validate and get the thread
|
||||||
|
Thread thread = dao.get(threadId);
|
||||||
|
// delete thread only if the admin/bot/author tries to delete it
|
||||||
|
OperationContext operationContext = new OperationContext(Entity.THREAD, MetadataOperation.DELETE);
|
||||||
|
ResourceContextInterface resourceContext = new ThreadResourceContext(dao.getOwnerReference(thread.getCreatedBy()));
|
||||||
|
authorizer.authorize(securityContext, operationContext, resourceContext, true);
|
||||||
|
return dao.deleteThread(thread, securityContext.getUserPrincipal().getName()).toResponse();
|
||||||
|
}
|
||||||
|
|
||||||
@DELETE
|
@DELETE
|
||||||
@Path("/{threadId}/posts/{postId}")
|
@Path("/{threadId}/posts/{postId}")
|
||||||
@Operation(
|
@Operation(
|
||||||
@ -537,7 +565,7 @@ public class FeedResource {
|
|||||||
// delete post only if the admin/bot/author tries to delete it
|
// delete post only if the admin/bot/author tries to delete it
|
||||||
// TODO fix this
|
// TODO fix this
|
||||||
OperationContext operationContext = new OperationContext(Entity.THREAD, MetadataOperation.DELETE);
|
OperationContext operationContext = new OperationContext(Entity.THREAD, MetadataOperation.DELETE);
|
||||||
ResourceContextInterface resourceContext = new PostResourceContext(dao.getOwnerOfPost(post));
|
ResourceContextInterface resourceContext = new PostResourceContext(dao.getOwnerReference(post.getFrom()));
|
||||||
authorizer.authorize(securityContext, operationContext, resourceContext, true);
|
authorizer.authorize(securityContext, operationContext, resourceContext, true);
|
||||||
return dao.deletePost(thread, post, securityContext.getUserPrincipal().getName()).toResponse();
|
return dao.deletePost(thread, post, securityContext.getUserPrincipal().getName()).toResponse();
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,31 @@
|
|||||||
|
package org.openmetadata.catalog.security.policyevaluator;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.List;
|
||||||
|
import org.openmetadata.catalog.EntityInterface;
|
||||||
|
import org.openmetadata.catalog.type.EntityReference;
|
||||||
|
import org.openmetadata.catalog.type.TagLabel;
|
||||||
|
|
||||||
|
/** Conversation threads require special handling */
|
||||||
|
public class ThreadResourceContext implements ResourceContextInterface {
|
||||||
|
private EntityReference owner;
|
||||||
|
|
||||||
|
public ThreadResourceContext(EntityReference owner) {
|
||||||
|
this.owner = owner;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public EntityReference getOwner() throws IOException {
|
||||||
|
return owner;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<TagLabel> getTags() throws IOException {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public EntityInterface getEntity() throws IOException {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
@ -311,6 +311,7 @@ public class FeedResourceTest extends CatalogApplicationTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Test the /api/v1/feed/count API
|
// Test the /api/v1/feed/count API
|
||||||
|
assertEquals(userThreadCount, listThreads(USER_LINK, null, userAuthHeaders).getPaging().getTotal());
|
||||||
assertEquals(userThreadCount, listThreadsCount(USER_LINK, userAuthHeaders).getTotalCount());
|
assertEquals(userThreadCount, listThreadsCount(USER_LINK, userAuthHeaders).getTotalCount());
|
||||||
assertEquals(tableDescriptionThreadCount, getThreadCount(TABLE_DESCRIPTION_LINK, userAuthHeaders));
|
assertEquals(tableDescriptionThreadCount, getThreadCount(TABLE_DESCRIPTION_LINK, userAuthHeaders));
|
||||||
assertEquals(tableColumnDescriptionThreadCount, getThreadCount(TABLE_COLUMN_LINK, userAuthHeaders));
|
assertEquals(tableColumnDescriptionThreadCount, getThreadCount(TABLE_COLUMN_LINK, userAuthHeaders));
|
||||||
@ -1225,6 +1226,15 @@ public class FeedResourceTest extends CatalogApplicationTest {
|
|||||||
entityNotFound("Post", NON_EXISTENT_ENTITY));
|
entityNotFound("Post", NON_EXISTENT_ENTITY));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void delete_thread_404() {
|
||||||
|
// Test with an invalid thread id
|
||||||
|
assertResponse(
|
||||||
|
() -> deleteThread(NON_EXISTENT_ENTITY, AUTH_HEADERS),
|
||||||
|
NOT_FOUND,
|
||||||
|
entityNotFound("Thread", NON_EXISTENT_ENTITY));
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void delete_post_200() throws HttpResponseException {
|
void delete_post_200() throws HttpResponseException {
|
||||||
// Create a thread and add a post
|
// Create a thread and add a post
|
||||||
@ -1247,6 +1257,20 @@ public class FeedResourceTest extends CatalogApplicationTest {
|
|||||||
assertEquals(0, getThread.getPostsCount());
|
assertEquals(0, getThread.getPostsCount());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void delete_thread_200() throws HttpResponseException {
|
||||||
|
// Create a thread
|
||||||
|
Thread thread = createAndCheck(create(), AUTH_HEADERS);
|
||||||
|
assertNotNull(thread);
|
||||||
|
|
||||||
|
// delete the thread
|
||||||
|
Thread deletedThread = deleteThread(thread.getId(), AUTH_HEADERS);
|
||||||
|
assertEquals(thread.getId(), deletedThread.getId());
|
||||||
|
|
||||||
|
// Check if thread is not found
|
||||||
|
assertResponse(() -> getThread(thread.getId(), AUTH_HEADERS), NOT_FOUND, entityNotFound("Thread", thread.getId()));
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void delete_post_unauthorized_403() throws HttpResponseException {
|
void delete_post_unauthorized_403() throws HttpResponseException {
|
||||||
// Create a thread and add a post as admin user
|
// Create a thread and add a post as admin user
|
||||||
@ -1266,6 +1290,22 @@ public class FeedResourceTest extends CatalogApplicationTest {
|
|||||||
permissionNotAllowed(USER.getName(), List.of(MetadataOperation.DELETE)));
|
permissionNotAllowed(USER.getName(), List.of(MetadataOperation.DELETE)));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void delete_thread_unauthorized_403() throws HttpResponseException {
|
||||||
|
// Create a thread as admin user
|
||||||
|
CreateThread create = create();
|
||||||
|
Thread thread = createAndCheck(create.withFrom(ADMIN_USER_NAME), ADMIN_AUTH_HEADERS);
|
||||||
|
assertNotNull(thread);
|
||||||
|
|
||||||
|
// delete the thread using a different user who is not an admin
|
||||||
|
// Here thread author is ADMIN, and we try to delete as USER
|
||||||
|
UUID threadId = thread.getId();
|
||||||
|
assertResponse(
|
||||||
|
() -> deleteThread(threadId, AUTH_HEADERS),
|
||||||
|
FORBIDDEN,
|
||||||
|
permissionNotAllowed(USER.getName(), List.of(MetadataOperation.DELETE)));
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void patch_post_reactions_200() throws IOException {
|
void patch_post_reactions_200() throws IOException {
|
||||||
// Create a thread and add a post
|
// Create a thread and add a post
|
||||||
@ -1352,6 +1392,10 @@ public class FeedResourceTest extends CatalogApplicationTest {
|
|||||||
return TestUtils.post(getResource("feed/" + threadId + "/posts"), post, Thread.class, authHeaders);
|
return TestUtils.post(getResource("feed/" + threadId + "/posts"), post, Thread.class, authHeaders);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static Thread deleteThread(UUID threadId, Map<String, String> authHeaders) throws HttpResponseException {
|
||||||
|
return TestUtils.delete(getResource("feed/" + threadId), Thread.class, authHeaders);
|
||||||
|
}
|
||||||
|
|
||||||
public static Post deletePost(UUID threadId, UUID postId, Map<String, String> authHeaders)
|
public static Post deletePost(UUID threadId, UUID postId, Map<String, String> authHeaders)
|
||||||
throws HttpResponseException {
|
throws HttpResponseException {
|
||||||
return TestUtils.delete(getResource("feed/" + threadId + "/posts/" + postId), Post.class, authHeaders);
|
return TestUtils.delete(getResource("feed/" + threadId + "/posts/" + postId), Post.class, authHeaders);
|
||||||
|
Loading…
x
Reference in New Issue
Block a user