mirror of
				https://github.com/open-metadata/OpenMetadata.git
				synced 2025-11-03 20:19:31 +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=(id+1) RETURNING id", connectionType = POSTGRES)
 | 
			
		||||
    void updateTaskId();
 | 
			
		||||
@ -1081,11 +1084,11 @@ public interface CollectionDAO {
 | 
			
		||||
        @Bind("isResolved") boolean isResolved);
 | 
			
		||||
 | 
			
		||||
    @SqlQuery(
 | 
			
		||||
        "SELECT entityLink, COUNT(id) count FROM thread_entity WHERE resolved = :resolved AND "
 | 
			
		||||
            + "(:type IS NULL OR type = :type) AND id in (SELECT toId FROM entity_relationship WHERE "
 | 
			
		||||
            + "(((fromEntity='user' AND fromId= :userId) OR "
 | 
			
		||||
        "SELECT entityLink, COUNT(id) count FROM thread_entity WHERE resolved = :resolved AND (:type IS NULL OR type = :type) AND "
 | 
			
		||||
            + "(entityId in (SELECT toId FROM entity_relationship WHERE "
 | 
			
		||||
            + "((fromEntity='user' AND fromId= :userId) 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")
 | 
			
		||||
    @RegisterRowMapper(CountFieldMapper.class)
 | 
			
		||||
    List<List<String>> listCountByOwner(
 | 
			
		||||
 | 
			
		||||
@ -505,8 +505,25 @@ public class FeedRepository {
 | 
			
		||||
    return new DeleteResponse<>(post, RestUtil.ENTITY_DELETED);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  public EntityReference getOwnerOfPost(Post post) {
 | 
			
		||||
    User fromUser = dao.userDAO().findEntityByName(post.getFrom());
 | 
			
		||||
  @Transaction
 | 
			
		||||
  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();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -67,6 +67,7 @@ import org.openmetadata.catalog.security.Authorizer;
 | 
			
		||||
import org.openmetadata.catalog.security.policyevaluator.OperationContext;
 | 
			
		||||
import org.openmetadata.catalog.security.policyevaluator.PostResourceContext;
 | 
			
		||||
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.EntityReference;
 | 
			
		||||
import org.openmetadata.catalog.type.MetadataOperation;
 | 
			
		||||
@ -510,6 +511,33 @@ public class FeedResource {
 | 
			
		||||
    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
 | 
			
		||||
  @Path("/{threadId}/posts/{postId}")
 | 
			
		||||
  @Operation(
 | 
			
		||||
@ -537,7 +565,7 @@ public class FeedResource {
 | 
			
		||||
    // delete post only if the admin/bot/author tries to delete it
 | 
			
		||||
    // TODO fix this
 | 
			
		||||
    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);
 | 
			
		||||
    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
 | 
			
		||||
    assertEquals(userThreadCount, listThreads(USER_LINK, null, userAuthHeaders).getPaging().getTotal());
 | 
			
		||||
    assertEquals(userThreadCount, listThreadsCount(USER_LINK, userAuthHeaders).getTotalCount());
 | 
			
		||||
    assertEquals(tableDescriptionThreadCount, getThreadCount(TABLE_DESCRIPTION_LINK, userAuthHeaders));
 | 
			
		||||
    assertEquals(tableColumnDescriptionThreadCount, getThreadCount(TABLE_COLUMN_LINK, userAuthHeaders));
 | 
			
		||||
@ -1225,6 +1226,15 @@ public class FeedResourceTest extends CatalogApplicationTest {
 | 
			
		||||
        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
 | 
			
		||||
  void delete_post_200() throws HttpResponseException {
 | 
			
		||||
    // Create a thread and add a post
 | 
			
		||||
@ -1247,6 +1257,20 @@ public class FeedResourceTest extends CatalogApplicationTest {
 | 
			
		||||
    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
 | 
			
		||||
  void delete_post_unauthorized_403() throws HttpResponseException {
 | 
			
		||||
    // 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)));
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  @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
 | 
			
		||||
  void patch_post_reactions_200() throws IOException {
 | 
			
		||||
    // 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);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  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)
 | 
			
		||||
      throws HttpResponseException {
 | 
			
		||||
    return TestUtils.delete(getResource("feed/" + threadId + "/posts/" + postId), Post.class, authHeaders);
 | 
			
		||||
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user