package controller import ( "github.com/gofiber/fiber/v2" "leafdev.top/Leaf/leaf-library-3/internal/pkg/validator" "leafdev.top/Leaf/leaf-library-3/internal/services/auth" "leafdev.top/Leaf/leaf-library-3/internal/services/collection" "leafdev.top/Leaf/leaf-library-3/internal/services/document" "leafdev.top/Leaf/leaf-library-3/internal/services/workspace" "leafdev.top/Leaf/leaf-library-3/internal/types/dto" "leafdev.top/Leaf/leaf-library-3/internal/types/errs" ) type DocumentController struct { documentService *document.Service collectionService *collection.Service workspaceService *workspace.Service authService *auth.Service } func NewDocumentController( documentService *document.Service, collectionService *collection.Service, workspaceService *workspace.Service, authService *auth.Service, ) *DocumentController { return &DocumentController{ documentService: documentService, collectionService: collectionService, workspaceService: workspaceService, authService: authService, } } // CreateDocument 创建文档 // @Summary 创建文档 // @Description 创建一个新的文档,可以是根文档或子文档 // @Tags Document // @Accept json // @Produce json // @Param request body dto.CreateDocumentRequest true "创建文档请求" // @Success 200 {object} dto.Response{data=dto.DocumentDTO} // @Failure 400 {object} dto.Response{data=[]dto.ValidateError} "参数验证失败" // @Failure 403 {object} dto.Response "无权访问" // @Failure 404 {object} dto.Response "集合不存在或父文档不存在" // @Router /documents [post] func (c *DocumentController) CreateDocument(ctx *fiber.Ctx) error { var req dto.CreateDocumentRequest if err := ctx.BodyParser(&req); err != nil { return err } if validationErrors, ok, err := validator.Struct(req); !ok { if err != nil { return err } return dto.Ctx(ctx).Data(validationErrors).Status(fiber.StatusBadRequest).Send() } // 检查集合是否存在 collection, err := c.collectionService.Get(ctx.Context(), req.CollectionID) if err != nil { return err } if collection == nil { return dto.Ctx(ctx).Error(errs.ErrCollectionNotExists).Status(fiber.StatusNotFound).Send() } // 检查用户是否有权限访问该集合 user := c.authService.GetUser(ctx) workspace, err := c.workspaceService.Get(ctx.Context(), collection.WorkspaceId) if err != nil { return err } if workspace == nil { return dto.Ctx(ctx).Error(errs.ErrWorkspaceNotExists).Status(fiber.StatusNotFound).Send() } isMember, err := c.workspaceService.IsMember(ctx.Context(), user.ID, workspace) if err != nil { return err } if !isMember { return dto.Ctx(ctx).Error(errs.ErrNoPermission).Status(fiber.StatusForbidden).Send() } // 如果是子文档,检查父文档是否存在且属于同一个集合 if req.ParentID != nil { parentDoc, err := c.documentService.Get(ctx.Context(), *req.ParentID) if err != nil { return err } if parentDoc == nil { return dto.Ctx(ctx).Error(errs.ErrDocumentNotExists).Status(fiber.StatusNotFound).Send() } if parentDoc.CollectionId != req.CollectionID { return dto.Ctx(ctx).Error(errs.ErrInvalidParentDocument).Status(fiber.StatusBadRequest).Send() } } doc, err := c.documentService.Create(ctx.Context(), req.Name, req.WorkspaceID, req.CollectionID, req.ParentID) if err != nil { return err } docDTO, err := c.documentService.ToDTO(ctx.Context(), doc) if err != nil { return err } return dto.Ctx(ctx).Success(docDTO).Send() } // GetDocument 获取文档 // @Summary 获取文档 // @Description 根据文档ID获取文档详情 // @Tags Document // @Accept json // @Produce json // @Param id path int true "文档ID" // @Success 200 {object} dto.Response{data=dto.DocumentDTO} // @Failure 403 {object} dto.Response "无权访问" // @Failure 404 {object} dto.Response "文档不存在" // @Router /documents/{id} [get] func (c *DocumentController) GetDocument(ctx *fiber.Ctx) error { var params dto.DocumentIDParam if err := ctx.ParamsParser(¶ms); err != nil { return err } doc, err := c.documentService.Get(ctx.Context(), params.ID) if err != nil { return err } if doc == nil { return dto.Ctx(ctx).Error(errs.ErrDocumentNotExists).Status(fiber.StatusNotFound).Send() } // 检查用户是否有权限访问该文档 user := c.authService.GetUser(ctx) collection, err := c.collectionService.Get(ctx.Context(), doc.CollectionId) if err != nil { return err } workspace, err := c.workspaceService.Get(ctx.Context(), collection.WorkspaceId) if err != nil { return err } isMember, err := c.workspaceService.IsMember(ctx.Context(), user.ID, workspace) if err != nil { return err } if !isMember { return dto.Ctx(ctx).Error(errs.ErrNoPermission).Status(fiber.StatusForbidden).Send() } docDTO, err := c.documentService.ToDTO(ctx.Context(), doc) if err != nil { return err } return dto.Ctx(ctx).Success(docDTO).Send() } // ListDocuments 列出集合或父文档下的所有文档 // @Summary 列出文档树 // @Description 获取指定集合下的文档树,如果指定了父文档ID则获取该文档下的子树 // @Tags Document // @Accept json // @Produce json // @Param collection_id path int true "集合ID" // @Param parent_id query int false "父文档ID,不传则获取根文档" // @Success 200 {object} dto.Response{data=[]dto.DocumentDTO} // @Failure 403 {object} dto.Response "无权访问" // @Failure 404 {object} dto.Response "集合不存在或父文档不存在" // @Router /collections/{collection_id}/documents [get] func (c *DocumentController) ListDocuments(ctx *fiber.Ctx) error { var pathParams dto.ListDocumentsPathParam if err := ctx.ParamsParser(&pathParams); err != nil { return err } var queryParams dto.ListDocumentsQueryParam if err := ctx.QueryParser(&queryParams); err != nil { return err } // 检查集合是否存在 collection, err := c.collectionService.Get(ctx.Context(), pathParams.CollectionID) if err != nil { return err } if collection == nil { return dto.Ctx(ctx).Error(errs.ErrCollectionNotExists).Status(fiber.StatusNotFound).Send() } // 检查用户是否有权限访问该集合 user := c.authService.GetUser(ctx) workspace, err := c.workspaceService.Get(ctx.Context(), collection.WorkspaceId) if err != nil { return err } isMember, err := c.workspaceService.IsMember(ctx.Context(), user.ID, workspace) if err != nil { return err } if !isMember { return dto.Ctx(ctx).Error(errs.ErrNoPermission).Status(fiber.StatusForbidden).Send() } // 如果指定了父文档,检查父文档是否存在且属于同一个集合 if queryParams.ParentID != nil { parentDoc, err := c.documentService.Get(ctx.Context(), *queryParams.ParentID) if err != nil { return err } if parentDoc == nil { return dto.Ctx(ctx).Error(errs.ErrDocumentNotExists).Status(fiber.StatusNotFound).Send() } if parentDoc.CollectionId != pathParams.CollectionID { return dto.Ctx(ctx).Error(errs.ErrInvalidParentDocument).Status(fiber.StatusBadRequest).Send() } } // 获取文档列表 docs, err := c.documentService.List(ctx.Context(), pathParams.CollectionID, queryParams.ParentID) if err != nil { return err } // 转换为 DTO dtos := make([]*dto.DocumentDTO, len(docs)) for i, doc := range docs { docDTO, err := c.documentService.ToDTO(ctx.Context(), doc) if err != nil { return err } dtos[i] = docDTO } return dto.Ctx(ctx).Success(dtos).Send() } // UpdateDocument 更新文档 // @Summary 更新文档 // @Description 更新文档的名称等信息 // @Tags Document // @Accept json // @Produce json // @Param id path int true "文档ID" // @Param request body dto.UpdateDocumentRequest true "更新文档请求" // @Success 200 {object} dto.Response{data=entity.Document} // @Failure 400 {object} dto.Response{data=[]dto.ValidateError} "参数验证失败" // @Failure 404 {object} dto.Response "文档不存在" // @Router /documents/{id} [put] func (c *DocumentController) UpdateDocument(ctx *fiber.Ctx) error { var params dto.DocumentIDParam if err := ctx.ParamsParser(¶ms); err != nil { return err } var req dto.UpdateDocumentRequest if err := ctx.BodyParser(&req); err != nil { return err } if validationErrors, ok, err := validator.Struct(req); !ok { if err != nil { return err } return dto.Ctx(ctx).Data(validationErrors).Status(fiber.StatusBadRequest).Send() } doc, err := c.documentService.Update(ctx.Context(), params.ID, req.Name) if err != nil { return err } docDTO, err := c.documentService.ToDTO(ctx.Context(), doc) if err != nil { return err } return dto.Ctx(ctx).Success(docDTO).Send() } // DeleteDocument 删除文档 // @Summary 删除文档 // @Description 删除指定的文档 // @Tags Document // @Accept json // @Produce json // @Param id path int true "文档ID" // @Success 200 {object} dto.Response // @Failure 404 {object} dto.Response "文档不存在" // @Router /documents/{id} [delete] func (c *DocumentController) DeleteDocument(ctx *fiber.Ctx) error { var params dto.DocumentIDParam if err := ctx.ParamsParser(¶ms); err != nil { return err } err := c.documentService.Delete(ctx.Context(), params.ID) if err != nil { return err } return dto.Ctx(ctx).Success(nil).Send() } // CreateBlock 创建文档块 // @Summary 创建文档块 // @Description 在文档中创建新的文档块 // @Tags Document // @Accept json // @Produce json // @Param document_id path int true "文档ID" // @Param request body dto.CreateBlockRequest true "创建文档块请求" // @Success 200 {object} dto.Response{data=entity.DocumentBlock} // @Failure 400 {object} dto.Response{data=[]dto.ValidateError} "参数验证失败" // @Failure 404 {object} dto.Response "文档不存在" // @Router /documents/{document_id}/blocks [post] func (c *DocumentController) CreateBlock(ctx *fiber.Ctx) error { var params dto.ListBlocksRequest if err := ctx.ParamsParser(¶ms); err != nil { return err } var req dto.CreateBlockRequest if err := ctx.BodyParser(&req); err != nil { return err } if validationErrors, ok, err := validator.Struct(req); !ok { if err != nil { return err } return dto.Ctx(ctx).Data(validationErrors).Status(fiber.StatusBadRequest).Send() } // 检查文档是否存在 doc, err := c.documentService.Get(ctx.Context(), params.DocumentID) if err != nil { return err } if doc == nil { return dto.Ctx(ctx).Error(errs.ErrDocumentNotExists).Status(fiber.StatusNotFound).Send() } // 检查用户是否有权限访问该文档 user := c.authService.GetUser(ctx) collection, err := c.collectionService.Get(ctx.Context(), doc.CollectionId) if err != nil { return err } workspace, err := c.workspaceService.Get(ctx.Context(), collection.WorkspaceId) if err != nil { return err } isMember, err := c.workspaceService.IsMember(ctx.Context(), user.ID, workspace) if err != nil { return err } if !isMember { return dto.Ctx(ctx).Error(errs.ErrNoPermission).Status(fiber.StatusForbidden).Send() } block, err := c.documentService.CreateBlock(ctx.Context(), params.DocumentID, req.Type, req.Content, req.AfterBlockID) if err != nil { return err } return dto.Ctx(ctx).Success(block).Send() } // UpdateBlock 更新文档块 // @Summary 更新文档块 // @Description 更新文档块的内容 // @Tags Document // @Accept json // @Produce json // @Param block_id path int true "文档块ID" // @Param request body dto.UpdateBlockRequest true "更新文档块请求" // @Success 200 {object} dto.Response{data=entity.DocumentBlock} // @Failure 400 {object} dto.Response{data=[]dto.ValidateError} "参数验证失败" // @Failure 404 {object} dto.Response "文档块不存在" // @Router /blocks/{block_id} [put] func (c *DocumentController) UpdateBlock(ctx *fiber.Ctx) error { var params dto.BlockIDParam if err := ctx.ParamsParser(¶ms); err != nil { return err } var req dto.UpdateBlockRequest if err := ctx.BodyParser(&req); err != nil { return err } if validationErrors, ok, err := validator.Struct(req); !ok { if err != nil { return err } return dto.Ctx(ctx).Data(validationErrors).Status(fiber.StatusBadRequest).Send() } // 获取块信息 block, err := c.documentService.GetBlock(ctx.Context(), params.ID) if err != nil { return err } if block == nil { return dto.Ctx(ctx).Error(errs.ErrBlockNotExists).Status(fiber.StatusNotFound).Send() } // 检查文档是否存在 doc, err := c.documentService.Get(ctx.Context(), block.DocumentId) if err != nil { return err } if doc == nil { return dto.Ctx(ctx).Error(errs.ErrDocumentNotExists).Status(fiber.StatusNotFound).Send() } // 检查用户是否有权限访问该文档 user := c.authService.GetUser(ctx) collection, err := c.collectionService.Get(ctx.Context(), doc.CollectionId) if err != nil { return err } workspace, err := c.workspaceService.Get(ctx.Context(), collection.WorkspaceId) if err != nil { return err } isMember, err := c.workspaceService.IsMember(ctx.Context(), user.ID, workspace) if err != nil { return err } if !isMember { return dto.Ctx(ctx).Error(errs.ErrNoPermission).Status(fiber.StatusForbidden).Send() } block, err = c.documentService.UpdateBlock(ctx.Context(), params.ID, req.Content) if err != nil { return err } return dto.Ctx(ctx).Success(block).Send() } // DeleteBlock ���除文档块 // @Summary 删除文档块 // @Description 删除指定的文档块 // @Tags Document // @Accept json // @Produce json // @Param block_id path int true "文档块ID" // @Success 200 {object} dto.Response // @Failure 404 {object} dto.Response "文档块不存在" // @Router /blocks/{block_id} [delete] func (c *DocumentController) DeleteBlock(ctx *fiber.Ctx) error { var params dto.BlockIDParam if err := ctx.ParamsParser(¶ms); err != nil { return err } // 获取块信息 block, err := c.documentService.GetBlock(ctx.Context(), params.ID) if err != nil { return err } if block == nil { return dto.Ctx(ctx).Error(errs.ErrBlockNotExists).Status(fiber.StatusNotFound).Send() } // 检查文档是否存在 doc, err := c.documentService.Get(ctx.Context(), block.DocumentId) if err != nil { return err } if doc == nil { return dto.Ctx(ctx).Error(errs.ErrDocumentNotExists).Status(fiber.StatusNotFound).Send() } // 检查用户是否有权限访问该文档 user := c.authService.GetUser(ctx) collection, err := c.collectionService.Get(ctx.Context(), doc.CollectionId) if err != nil { return err } workspace, err := c.workspaceService.Get(ctx.Context(), collection.WorkspaceId) if err != nil { return err } isMember, err := c.workspaceService.IsMember(ctx.Context(), user.ID, workspace) if err != nil { return err } if !isMember { return dto.Ctx(ctx).Error(errs.ErrNoPermission).Status(fiber.StatusForbidden).Send() } err = c.documentService.DeleteBlock(ctx.Context(), params.ID) if err != nil { return err } return dto.Ctx(ctx).Success(nil).Send() } // ListBlocks 列出文档下的所有块 // @Summary 列出文档块列表 // @Description 获取指定文档下的所有文档块 // @Tags Document // @Accept json // @Produce json // @Param document_id path int true "文档ID" // @Success 200 {object} dto.Response{data=[]entity.DocumentBlock} // @Failure 404 {object} dto.Response "文档不存在" // @Router /documents/{document_id}/blocks [get] func (c *DocumentController) ListBlocks(ctx *fiber.Ctx) error { var params dto.ListBlocksRequest if err := ctx.ParamsParser(¶ms); err != nil { return err } // 检查文档是否存在 doc, err := c.documentService.Get(ctx.Context(), params.DocumentID) if err != nil { return err } if doc == nil { return dto.Ctx(ctx).Error(errs.ErrDocumentNotExists).Status(fiber.StatusNotFound).Send() } // 检查用户是否有权限访问该文档 user := c.authService.GetUser(ctx) collection, err := c.collectionService.Get(ctx.Context(), doc.CollectionId) if err != nil { return err } workspace, err := c.workspaceService.Get(ctx.Context(), collection.WorkspaceId) if err != nil { return err } isMember, err := c.workspaceService.IsMember(ctx.Context(), user.ID, workspace) if err != nil { return err } if !isMember { return dto.Ctx(ctx).Error(errs.ErrNoPermission).Status(fiber.StatusForbidden).Send() } blocks, err := c.documentService.ListBlocks(ctx.Context(), params.DocumentID) if err != nil { return err } return dto.Ctx(ctx).Success(blocks).Send() } // MoveBlock 移动文档块 // @Summary 移动文档块 // @Description 移动文档块的位置 // @Tags Document // @Accept json // @Produce json // @Param block_id path int true "文档块ID" // @Param request body dto.MoveBlockRequest true "移动文档块请求" // @Success 200 {object} dto.Response // @Failure 400 {object} dto.Response{data=[]dto.ValidateError} "参数验证失败" // @Failure 404 {object} dto.Response "文档块不存在" // @Router /blocks/{block_id}/move [post] func (c *DocumentController) MoveBlock(ctx *fiber.Ctx) error { var params dto.BlockIDParam if err := ctx.ParamsParser(¶ms); err != nil { return err } var req dto.MoveBlockRequest if err := ctx.BodyParser(&req); err != nil { return err } // 获取块信息 block, err := c.documentService.GetBlock(ctx.Context(), params.ID) if err != nil { return err } if block == nil { return dto.Ctx(ctx).Error(errs.ErrBlockNotExists).Status(fiber.StatusNotFound).Send() } // 检查文档是否存在 doc, err := c.documentService.Get(ctx.Context(), block.DocumentId) if err != nil { return err } if doc == nil { return dto.Ctx(ctx).Error(errs.ErrDocumentNotExists).Status(fiber.StatusNotFound).Send() } // 检查用户是否有权限访问该文档 user := c.authService.GetUser(ctx) collection, err := c.collectionService.Get(ctx.Context(), doc.CollectionId) if err != nil { return err } workspace, err := c.workspaceService.Get(ctx.Context(), collection.WorkspaceId) if err != nil { return err } isMember, err := c.workspaceService.IsMember(ctx.Context(), user.ID, workspace) if err != nil { return err } if !isMember { return dto.Ctx(ctx).Error(errs.ErrNoPermission).Status(fiber.StatusForbidden).Send() } err = c.documentService.MoveBlock(ctx.Context(), params.ID, req.AfterBlockID) if err != nil { return err } return dto.Ctx(ctx).Success(nil).Send() }