leaf-library-3/internal/api/http/controller/document.go
2024-12-07 02:44:32 +08:00

477 lines
14 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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(&params); 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(&params); 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(&params); 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(&params); 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()
}
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 document_id path int true "文档ID"
// @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 /documents/{document_id}/blocks/{block_id} [put]
func (c *DocumentController) UpdateBlock(ctx *fiber.Ctx) error {
var params dto.BlockIDParam
if err := ctx.ParamsParser(&params); 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.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 document_id path int true "文档ID"
// @Param block_id path int true "文档块ID"
// @Success 200 {object} dto.Response
// @Failure 404 {object} dto.Response "文档块不存在"
// @Router /documents/{document_id}/blocks/{block_id} [delete]
func (c *DocumentController) DeleteBlock(ctx *fiber.Ctx) error {
var params dto.BlockIDParam
if err := ctx.ParamsParser(&params); err != nil {
return err
}
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(&params); err != nil {
return err
}
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 document_id path int true "文档ID"
// @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 /documents/{document_id}/blocks/{block_id}/move [post]
func (c *DocumentController) MoveBlock(ctx *fiber.Ctx) error {
var params dto.BlockIDParam
if err := ctx.ParamsParser(&params); err != nil {
return err
}
var req dto.MoveBlockRequest
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()
}
err := c.documentService.MoveBlock(ctx.Context(), params.ID, req.AfterBlockID)
if err != nil {
return err
}
return dto.Ctx(ctx).Success(nil).Send()
}