diff --git a/graph/docs/docs.go b/graph/docs/docs.go index 6d28d0f..7c3bab4 100644 --- a/graph/docs/docs.go +++ b/graph/docs/docs.go @@ -87,6 +87,78 @@ const docTemplate = `{ } } }, + "/connections/delete": { + "post": { + "description": "문서 간의 그래프 연결을 삭제합니다.", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Graph Connection" + ], + "summary": "그래프 연결 삭제", + "parameters": [ + { + "description": "삭제할 Source 문서 ID 및 Target 문서 ID", + "name": "request", + "in": "body", + "required": true, + "schema": { + "type": "object", + "properties": { + "sourceId": { + "type": "string" + }, + "targetId": { + "type": "string" + }, + "workspaceId": { + "type": "string" + } + } + } + } + ], + "responses": { + "200": { + "description": "연결 삭제 성공", + "schema": { + "type": "object", + "properties": { + "status": { + "type": "string" + } + } + } + }, + "400": { + "description": "잘못된 요청 형식", + "schema": { + "type": "object", + "properties": { + "error": { + "type": "string" + } + } + } + }, + "500": { + "description": "서버 내부 오류로 연결 삭제 실패", + "schema": { + "type": "object", + "properties": { + "error": { + "type": "string" + } + } + } + } + } + } + }, "/connections/edit": { "post": { "description": "사용자가 문서 간의 연결을 수동으로 편집했음을 표시하기 위해 연결 상태를 'edited'로 변경합니다.", diff --git a/graph/docs/swagger.json b/graph/docs/swagger.json index e807d65..40cb518 100644 --- a/graph/docs/swagger.json +++ b/graph/docs/swagger.json @@ -76,6 +76,78 @@ } } }, + "/connections/delete": { + "post": { + "description": "문서 간의 그래프 연결을 삭제합니다.", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Graph Connection" + ], + "summary": "그래프 연결 삭제", + "parameters": [ + { + "description": "삭제할 Source 문서 ID 및 Target 문서 ID", + "name": "request", + "in": "body", + "required": true, + "schema": { + "type": "object", + "properties": { + "sourceId": { + "type": "string" + }, + "targetId": { + "type": "string" + }, + "workspaceId": { + "type": "string" + } + } + } + } + ], + "responses": { + "200": { + "description": "연결 삭제 성공", + "schema": { + "type": "object", + "properties": { + "status": { + "type": "string" + } + } + } + }, + "400": { + "description": "잘못된 요청 형식", + "schema": { + "type": "object", + "properties": { + "error": { + "type": "string" + } + } + } + }, + "500": { + "description": "서버 내부 오류로 연결 삭제 실패", + "schema": { + "type": "object", + "properties": { + "error": { + "type": "string" + } + } + } + } + } + } + }, "/connections/edit": { "post": { "description": "사용자가 문서 간의 연결을 수동으로 편집했음을 표시하기 위해 연결 상태를 'edited'로 변경합니다.", diff --git a/graph/docs/swagger.yaml b/graph/docs/swagger.yaml index fa9bd2b..3e1e8d6 100644 --- a/graph/docs/swagger.yaml +++ b/graph/docs/swagger.yaml @@ -47,6 +47,52 @@ paths: summary: 그래프 연결 확정 tags: - Graph Connection + /connections/delete: + post: + consumes: + - application/json + description: 문서 간의 그래프 연결을 삭제합니다. + parameters: + - description: 삭제할 Source 문서 ID 및 Target 문서 ID + in: body + name: request + required: true + schema: + properties: + sourceId: + type: string + targetId: + type: string + workspaceId: + type: string + type: object + produces: + - application/json + responses: + "200": + description: 연결 삭제 성공 + schema: + properties: + status: + type: string + type: object + "400": + description: 잘못된 요청 형식 + schema: + properties: + error: + type: string + type: object + "500": + description: 서버 내부 오류로 연결 삭제 실패 + schema: + properties: + error: + type: string + type: object + summary: 그래프 연결 삭제 + tags: + - Graph Connection /connections/edit: post: consumes: diff --git a/graph/handlers/graph_handlers.go b/graph/handlers/graph_handlers.go index 351db4a..a69a4f8 100644 --- a/graph/handlers/graph_handlers.go +++ b/graph/handlers/graph_handlers.go @@ -73,6 +73,34 @@ func (h *GraphConnectionHandler) EditGraphConnection(c *gin.Context) { c.JSON(http.StatusOK, gin.H{"status": "success"}) } +// DeleteGraphConnection +// @Summary 그래프 연결 삭제 +// @Description 문서 간의 그래프 연결을 삭제합니다. +// @Tags Graph Connection +// @Accept json +// @Produce json +// @Param request body object{sourceId=string,targetId=string,workspaceId=string} true "삭제할 Source 문서 ID 및 Target 문서 ID" +// @Success 200 {object} object{status=string} "연결 삭제 성공" +// @Failure 400 {object} object{error=string} "잘못된 요청 형식" +// @Failure 500 {object} object{error=string} "서버 내부 오류로 연결 삭제 실패" +// @Router /connections/delete [post] +func (h *GraphConnectionHandler) DeleteGraphConnection(c *gin.Context) { + var req struct { + SourceID string `json:"sourceId" binding:"required"` + TargetID string `json:"targetId" binding:"required"` + WorkspaceID string `json:"workspaceId" binding:"required"` + } + if err := c.ShouldBindJSON(&req); err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + if err := h.service.DeleteGraphConnection(c.Request.Context(), req.SourceID, req.TargetID, req.WorkspaceID); err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": fmt.Sprintf("Failed to delete connection: %s", err.Error())}) + return + } + c.JSON(http.StatusOK, gin.H{"status": "success"}) +} + // DeleteNoteGraph // @Summary 노트 삭제 이벤트 처리 // @Description 외부 노트 서비스에서 노트 삭제 시 관련 그래프 연결을 모두 삭제합니다. diff --git a/graph/server/server.go b/graph/server/server.go index ce87751..08ce3ac 100644 --- a/graph/server/server.go +++ b/graph/server/server.go @@ -20,6 +20,7 @@ func NewServer(gs *services.GraphService, ws *services.WorkspaceService) *gin.En // 그래프 연결 관리 router.POST("/connections/confirm", graphHandler.ConfirmGraphConnection) router.POST("/connections/edit", graphHandler.EditGraphConnection) + router.POST("/connections/delete", graphHandler.DeleteGraphConnection) router.POST("/connections/note-deleted", graphHandler.DeleteNoteGraph) router.POST("/workspaces/:workspaceId/clear-pending", graphHandler.ClearPendingConnections) diff --git a/graph/services/graph_service.go b/graph/services/graph_service.go index ac31f72..a95e3bd 100644 --- a/graph/services/graph_service.go +++ b/graph/services/graph_service.go @@ -228,6 +228,22 @@ func (s *GraphService) EditGraphConnection(ctx context.Context, sourceID, target return err } +func (s *GraphService) DeleteGraphConnection(ctx context.Context, sourceID, targetID, workspaceID string) error { + result, err := s.db.Collection("graph_connections").DeleteOne( + ctx, bson.M{"workspace_id": workspaceID, "source_id": sourceID, "target_id": targetID}, + ) + if err != nil { + return fmt.Errorf("failed to delete graph connection: %w", err) + } + + if result.DeletedCount == 0 { + log.Printf("[DeleteGraphConnection] No connection found for (%s -> %s) in WS %s", sourceID, targetID, workspaceID) + } + + log.Printf("[DeleteGraphConnection] Deleted (%s -> %s) from WS %s", sourceID, targetID, workspaceID) + return nil +} + func (s *GraphService) ConfirmAllConnections(ctx context.Context, workspaceID string) error { _, err := s.db.Collection("graph_connections").UpdateMany( ctx, diff --git a/graph/services/graph_service_test.go b/graph/services/graph_service_test.go index e1dee59..6db526e 100644 --- a/graph/services/graph_service_test.go +++ b/graph/services/graph_service_test.go @@ -173,6 +173,58 @@ func TestGraphService_ConfirmGraphConnection(t *testing.T) { }) } +func TestGraphService_DeleteGraphConnection(t *testing.T) { + mt := mtest.New(t, mtest.NewOptions().ClientType(mtest.Mock)) + sourceID := "6517a2624a081a27e7d0f93a" + targetID := "6517a2624a081a27e7d0f93b" + workspaceID := "workspace1" + ctx := context.Background() + + mt.Run("Success_DeletesSingleConnection", func(mt *mtest.T) { + service := services.NewGraphService(mt.DB) + + mt.AddMockResponses( + mtest.CreateSuccessResponse( + bson.E{Key: "ok", Value: 1}, + bson.E{Key: "n", Value: 1}, + bson.E{Key: "deletedCount", Value: 1}, + ), + ) + + err := service.DeleteGraphConnection(ctx, sourceID, targetID, workspaceID) + assert.NoError(t, err) + }) + + mt.Run("Success_NoConnectionFound", func(mt *mtest.T) { + service := services.NewGraphService(mt.DB) + + mt.AddMockResponses( + mtest.CreateSuccessResponse( + bson.E{Key: "ok", Value: 1}, + bson.E{Key: "n", Value: 0}, + bson.E{Key: "deletedCount", Value: 0}, + ), + ) + + err := service.DeleteGraphConnection(ctx, sourceID, targetID, workspaceID) + assert.NoError(t, err, "no data but should not return error") + }) + + mt.Run("Error_DeleteOperationFailed", func(mt *mtest.T) { + service := services.NewGraphService(mt.DB) + mt.AddMockResponses( + mtest.CreateCommandErrorResponse(mtest.CommandError{ + Message: "delete failed", + Code: 11000, + }), + ) + + err := service.DeleteGraphConnection(ctx, sourceID, targetID, workspaceID) + assert.Error(t, err) + assert.Contains(t, err.Error(), "failed to delete graph connection") + }) +} + func TestGraphService_ConfirmAllConnections(t *testing.T) { mt := mtest.New(t, mtest.NewOptions().ClientType(mtest.Mock)) workspaceID := "6517a2624a081a27e7d0f91e"