This commit is contained in:
ivamp 2024-12-07 02:44:32 +08:00
parent d9ccbf13f7
commit c30ce8f0dd
40 changed files with 5288 additions and 407 deletions

View File

@ -13,7 +13,7 @@ import (
"leafdev.top/Leaf/leaf-library-3/internal/api/grpc/interceptor"
"leafdev.top/Leaf/leaf-library-3/internal/api/grpc/v1/documents"
"leafdev.top/Leaf/leaf-library-3/internal/api/http"
"leafdev.top/Leaf/leaf-library-3/internal/api/http/v1"
"leafdev.top/Leaf/leaf-library-3/internal/api/http/controller"
"leafdev.top/Leaf/leaf-library-3/internal/base"
"leafdev.top/Leaf/leaf-library-3/internal/base/conf"
"leafdev.top/Leaf/leaf-library-3/internal/base/logger"
@ -27,8 +27,11 @@ import (
"leafdev.top/Leaf/leaf-library-3/internal/router"
"leafdev.top/Leaf/leaf-library-3/internal/services"
"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/jwks"
"leafdev.top/Leaf/leaf-library-3/internal/services/stream"
"leafdev.top/Leaf/leaf-library-3/internal/services/workspace"
)
// Injectors from wire.go:
@ -36,24 +39,29 @@ import (
func CreateApp() (*base.Application, error) {
config := conf.NewConfig()
loggerLogger := logger.NewZapLogger(config)
db := orm.NewGORM(config, loggerLogger)
query := dao.NewQuery(db)
service := workspace.NewService(config, query)
jwksJWKS := jwks.NewJWKS(config, loggerLogger)
service := auth.NewService(config, jwksJWKS, loggerLogger)
userController := v1.NewUserController(service)
handlers := http.NewHandler(userController)
middleware := http.NewMiddleware(config, loggerLogger, service)
authService := auth.NewService(config, jwksJWKS, loggerLogger)
workspaceController := controller.NewWorkspaceController(service, authService)
collectionService := collection.NewService(config, query)
collectionController := controller.NewCollectionController(collectionService, service, authService)
documentService := document.NewService(config, query)
documentController := controller.NewDocumentController(documentService, collectionService, service, authService)
handlers := http.NewHandler(workspaceController, collectionController, documentController)
middleware := http.NewMiddleware(config, loggerLogger, authService)
routerApi := router.NewApiRoute(handlers, middleware)
swaggerRouter := router.NewSwaggerRoute()
httpServer := server.NewHTTPServer(config, routerApi, swaggerRouter, middleware, loggerLogger)
db := orm.NewGORM(config, loggerLogger)
query := dao.NewQuery(db)
handler := documents.NewHandler(query)
interceptorAuth := interceptor.NewAuth(service, loggerLogger, config)
interceptorAuth := interceptor.NewAuth(authService, loggerLogger, config)
interceptorLogger := interceptor.NewLogger(loggerLogger)
grpcInterceptor := grpc.NewInterceptor(interceptorAuth, interceptorLogger)
grpcHandlers := grpc.NewHandler(handler, grpcInterceptor)
apiApi := api.NewApi(grpcHandlers, handlers)
streamService := stream.NewService(config)
servicesService := services.NewService(loggerLogger, jwksJWKS, service, streamService)
servicesService := services.NewService(loggerLogger, jwksJWKS, authService, streamService, service, collectionService, documentService)
redisRedis := redis.NewRedis(config)
batchBatch := batch.NewBatch(loggerLogger)
s3S3 := s3.NewS3(config)

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -1,38 +1,218 @@
definitions:
response.Body:
dto.AddWorkspaceMemberRequest:
properties:
data: {}
error:
user_id:
$ref: '#/definitions/user.ID'
required:
- user_id
type: object
dto.CreateBlockRequest:
properties:
after_block_id:
type: integer
content:
type: string
type:
type: string
required:
- content
- type
type: object
dto.CreateCollectionRequest:
properties:
name:
type: string
workspace_id:
type: integer
required:
- workspace_id
type: object
dto.CreateDocumentRequest:
properties:
collection_id:
type: integer
name:
type: string
parent_id:
type: integer
workspace_id:
type: integer
required:
- collection_id
- workspace_id
type: object
dto.CreateWorkspaceRequest:
properties:
name:
type: string
type: object
dto.DocumentDTO:
properties:
collection_id:
type: integer
created_at:
type: string
has_children:
type: boolean
id:
type: integer
name:
type: string
parent_id:
type: integer
updated_at:
type: string
workspace_id:
type: integer
type: object
dto.MoveBlockRequest:
properties:
after_block_id:
type: integer
type: object
dto.Response:
type: object
dto.UpdateBlockRequest:
properties:
content:
type: string
required:
- content
type: object
dto.UpdateDocumentRequest:
properties:
name:
type: string
type: object
dto.ValidateError:
properties:
message:
type: string
success:
type: boolean
type: object
schema.CurrentUserResponse:
entity.Document:
properties:
ip:
collection:
$ref: '#/definitions/leafdev_top_Leaf_leaf-library-3_internal_entity.Collection'
collection_id:
type: integer
created_at:
type: string
userEmail:
deleted_at:
$ref: '#/definitions/gorm.DeletedAt'
id:
type: integer
name:
type: string
userId:
parent:
$ref: '#/definitions/entity.Document'
parent_id:
type: integer
updated_at:
type: string
userName:
workspace:
$ref: '#/definitions/entity.Workspace'
workspace_id:
type: integer
type: object
entity.DocumentBlock:
properties:
content:
type: string
created_at:
type: string
document:
$ref: '#/definitions/entity.Document'
document_id:
type: integer
hash:
type: string
id:
type: integer
order:
type: number
type:
type: string
updated_at:
type: string
type: object
entity.Workspace:
properties:
created_at:
type: string
deleted_at:
$ref: '#/definitions/gorm.DeletedAt'
id:
type: integer
name:
type: string
updated_at:
type: string
user_id:
$ref: '#/definitions/user.ID'
type: object
entity.WorkspaceMember:
properties:
created_at:
type: string
id:
type: integer
updated_at:
type: string
user_id:
$ref: '#/definitions/user.ID'
workspace:
$ref: '#/definitions/entity.Workspace'
workspace_id:
type: integer
type: object
gorm.DeletedAt:
properties:
time:
type: string
valid:
description: Valid is true if Time is not NULL
type: boolean
type: object
leafdev_top_Leaf_leaf-library-3_internal_entity.Collection:
properties:
created_at:
type: string
deleted_at:
$ref: '#/definitions/gorm.DeletedAt'
id:
type: integer
name:
type: string
updated_at:
type: string
workspace:
$ref: '#/definitions/entity.Workspace'
workspace_id:
type: integer
type: object
user.ID:
enum:
- anonymous
type: string
x-enum-varnames:
- AnonymousUser
info:
contact: {}
title: API Docs
version: "1.0"
paths:
/api/v1/ping:
get:
/collections:
post:
consumes:
- application/json
deprecated: true
description: 测试接口,将会返回当前用户的信息
description: 在工作空间下创建新的集合
parameters:
- description: 创建集合请求
in: body
name: request
required: true
schema:
$ref: '#/definitions/dto.CreateCollectionRequest'
produces:
- application/json
responses:
@ -40,20 +220,705 @@ paths:
description: OK
schema:
allOf:
- $ref: '#/definitions/response.Body'
- $ref: '#/definitions/dto.Response'
- properties:
data:
$ref: '#/definitions/schema.CurrentUserResponse'
$ref: '#/definitions/leafdev_top_Leaf_leaf-library-3_internal_entity.Collection'
type: object
"400":
description: Bad Request
description: 参数验证失败
schema:
$ref: '#/definitions/response.Body'
security:
- ApiKeyAuth: []
summary: Greet
allOf:
- $ref: '#/definitions/dto.Response'
- properties:
data:
items:
$ref: '#/definitions/dto.ValidateError'
type: array
type: object
"404":
description: 工作空间不存在
schema:
$ref: '#/definitions/dto.Response'
summary: 创建集合
tags:
- ping
- Collection
/collections/{collection_id}/documents:
get:
consumes:
- application/json
description: 获取指定集合下的文档树如果指定了父文档ID则获取该文档下的子树
parameters:
- description: 集合ID
in: path
name: collection_id
required: true
type: integer
- description: 父文档ID不传则获取根文档
in: query
name: parent_id
type: integer
produces:
- application/json
responses:
"200":
description: OK
schema:
allOf:
- $ref: '#/definitions/dto.Response'
- properties:
data:
items:
$ref: '#/definitions/dto.DocumentDTO'
type: array
type: object
"403":
description: 无权访问
schema:
$ref: '#/definitions/dto.Response'
"404":
description: 集合不存在或父文档不存在
schema:
$ref: '#/definitions/dto.Response'
summary: 列出文档树
tags:
- Document
/collections/{id}:
delete:
consumes:
- application/json
description: 删除指定的集合
parameters:
- description: 集合ID
in: path
name: id
required: true
type: integer
produces:
- application/json
responses:
"200":
description: OK
schema:
$ref: '#/definitions/dto.Response'
"404":
description: 集合不存在
schema:
$ref: '#/definitions/dto.Response'
summary: 删除集合
tags:
- Collection
get:
consumes:
- application/json
description: 根据集合ID获取集合详情
parameters:
- description: 集合ID
in: path
name: id
required: true
type: integer
produces:
- application/json
responses:
"200":
description: OK
schema:
allOf:
- $ref: '#/definitions/dto.Response'
- properties:
data:
$ref: '#/definitions/leafdev_top_Leaf_leaf-library-3_internal_entity.Collection'
type: object
"404":
description: 集合不存在
schema:
$ref: '#/definitions/dto.Response'
summary: 获取集合
tags:
- Collection
/documents:
post:
consumes:
- application/json
description: 创建一个新的文档,可以是根文档或子文档
parameters:
- description: 创建文档请求
in: body
name: request
required: true
schema:
$ref: '#/definitions/dto.CreateDocumentRequest'
produces:
- application/json
responses:
"200":
description: OK
schema:
allOf:
- $ref: '#/definitions/dto.Response'
- properties:
data:
$ref: '#/definitions/dto.DocumentDTO'
type: object
"400":
description: 参数验证失败
schema:
allOf:
- $ref: '#/definitions/dto.Response'
- properties:
data:
items:
$ref: '#/definitions/dto.ValidateError'
type: array
type: object
"403":
description: 无权访问
schema:
$ref: '#/definitions/dto.Response'
"404":
description: 集合不存在或父文档不存在
schema:
$ref: '#/definitions/dto.Response'
summary: 创建文档
tags:
- Document
/documents/{document_id}/blocks:
get:
consumes:
- application/json
description: 获取指文档下的所有文档块
parameters:
- description: 文档ID
in: path
name: document_id
required: true
type: integer
produces:
- application/json
responses:
"200":
description: OK
schema:
allOf:
- $ref: '#/definitions/dto.Response'
- properties:
data:
items:
$ref: '#/definitions/entity.DocumentBlock'
type: array
type: object
"404":
description: 文档不存在
schema:
$ref: '#/definitions/dto.Response'
summary: 列出文档块列表
tags:
- Document
post:
consumes:
- application/json
description: 在文档中创建新的文档块
parameters:
- description: 文档ID
in: path
name: document_id
required: true
type: integer
- description: 创建文档块请求
in: body
name: request
required: true
schema:
$ref: '#/definitions/dto.CreateBlockRequest'
produces:
- application/json
responses:
"200":
description: OK
schema:
allOf:
- $ref: '#/definitions/dto.Response'
- properties:
data:
$ref: '#/definitions/entity.DocumentBlock'
type: object
"400":
description: 参数验证失败
schema:
allOf:
- $ref: '#/definitions/dto.Response'
- properties:
data:
items:
$ref: '#/definitions/dto.ValidateError'
type: array
type: object
"404":
description: 文档不存在
schema:
$ref: '#/definitions/dto.Response'
summary: 创建文档块
tags:
- Document
/documents/{document_id}/blocks/{block_id}:
delete:
consumes:
- application/json
description: 删除指定的文档块
parameters:
- description: 文档ID
in: path
name: document_id
required: true
type: integer
- description: 文档块ID
in: path
name: block_id
required: true
type: integer
produces:
- application/json
responses:
"200":
description: OK
schema:
$ref: '#/definitions/dto.Response'
"404":
description: 文档块不存在
schema:
$ref: '#/definitions/dto.Response'
summary: 删除文档块
tags:
- Document
put:
consumes:
- application/json
description: 更新文档块的内容
parameters:
- description: 文档ID
in: path
name: document_id
required: true
type: integer
- description: 文档块ID
in: path
name: block_id
required: true
type: integer
- description: 更新文档块请求
in: body
name: request
required: true
schema:
$ref: '#/definitions/dto.UpdateBlockRequest'
produces:
- application/json
responses:
"200":
description: OK
schema:
allOf:
- $ref: '#/definitions/dto.Response'
- properties:
data:
$ref: '#/definitions/entity.DocumentBlock'
type: object
"400":
description: 参数验证失败
schema:
allOf:
- $ref: '#/definitions/dto.Response'
- properties:
data:
items:
$ref: '#/definitions/dto.ValidateError'
type: array
type: object
"404":
description: 文档块不存在
schema:
$ref: '#/definitions/dto.Response'
summary: 更新文档块
tags:
- Document
/documents/{document_id}/blocks/{block_id}/move:
post:
consumes:
- application/json
description: 移动文档块的位置
parameters:
- description: 文档ID
in: path
name: document_id
required: true
type: integer
- description: 文档块ID
in: path
name: block_id
required: true
type: integer
- description: 移动文档块请求
in: body
name: request
required: true
schema:
$ref: '#/definitions/dto.MoveBlockRequest'
produces:
- application/json
responses:
"200":
description: OK
schema:
$ref: '#/definitions/dto.Response'
"400":
description: 参数验证失败
schema:
allOf:
- $ref: '#/definitions/dto.Response'
- properties:
data:
items:
$ref: '#/definitions/dto.ValidateError'
type: array
type: object
"404":
description: 文档块不存在
schema:
$ref: '#/definitions/dto.Response'
summary: 移动文档块
tags:
- Document
/documents/{id}:
delete:
consumes:
- application/json
description: 删除指定的文档
parameters:
- description: 文档ID
in: path
name: id
required: true
type: integer
produces:
- application/json
responses:
"200":
description: OK
schema:
$ref: '#/definitions/dto.Response'
"404":
description: 文档不存在
schema:
$ref: '#/definitions/dto.Response'
summary: 删除文档
tags:
- Document
get:
consumes:
- application/json
description: 根据文档ID获取文档详情
parameters:
- description: 文档ID
in: path
name: id
required: true
type: integer
produces:
- application/json
responses:
"200":
description: OK
schema:
allOf:
- $ref: '#/definitions/dto.Response'
- properties:
data:
$ref: '#/definitions/dto.DocumentDTO'
type: object
"403":
description: 无权访问
schema:
$ref: '#/definitions/dto.Response'
"404":
description: 文档不存在
schema:
$ref: '#/definitions/dto.Response'
summary: 获取文档
tags:
- Document
put:
consumes:
- application/json
description: 更新文档的名称等信息
parameters:
- description: 文档ID
in: path
name: id
required: true
type: integer
- description: 更新文档请求
in: body
name: request
required: true
schema:
$ref: '#/definitions/dto.UpdateDocumentRequest'
produces:
- application/json
responses:
"200":
description: OK
schema:
allOf:
- $ref: '#/definitions/dto.Response'
- properties:
data:
$ref: '#/definitions/entity.Document'
type: object
"400":
description: 参数验证失败
schema:
allOf:
- $ref: '#/definitions/dto.Response'
- properties:
data:
items:
$ref: '#/definitions/dto.ValidateError'
type: array
type: object
"404":
description: 文档不存在
schema:
$ref: '#/definitions/dto.Response'
summary: 更新文档
tags:
- Document
/workspaces:
get:
consumes:
- application/json
description: 获取当前用户的所有工作空间
produces:
- application/json
responses:
"200":
description: OK
schema:
allOf:
- $ref: '#/definitions/dto.Response'
- properties:
data:
items:
$ref: '#/definitions/entity.Workspace'
type: array
type: object
summary: 列出工作空间列表
tags:
- Workspace
post:
consumes:
- application/json
description: 创建一个新的工作空间
parameters:
- description: 创建工作空间请求
in: body
name: request
required: true
schema:
$ref: '#/definitions/dto.CreateWorkspaceRequest'
produces:
- application/json
responses:
"200":
description: OK
schema:
allOf:
- $ref: '#/definitions/dto.Response'
- properties:
data:
$ref: '#/definitions/entity.Workspace'
type: object
"400":
description: 参数验证失败
schema:
allOf:
- $ref: '#/definitions/dto.Response'
- properties:
data:
items:
$ref: '#/definitions/dto.ValidateError'
type: array
type: object
summary: 创建工作空间
tags:
- Workspace
/workspaces/{id}:
delete:
consumes:
- application/json
description: 删除指定的工作空间
parameters:
- description: 工作空间ID
in: path
name: id
required: true
type: integer
produces:
- application/json
responses:
"200":
description: OK
schema:
$ref: '#/definitions/dto.Response'
"404":
description: 工作空间不存在
schema:
$ref: '#/definitions/dto.Response'
summary: 删除工作空间
tags:
- Workspace
get:
consumes:
- application/json
description: 根据工作空间ID获取工作空间详情
parameters:
- description: 工作空间ID
in: path
name: id
required: true
type: integer
produces:
- application/json
responses:
"200":
description: OK
schema:
allOf:
- $ref: '#/definitions/dto.Response'
- properties:
data:
$ref: '#/definitions/entity.Workspace'
type: object
"404":
description: 工作空间不存在
schema:
$ref: '#/definitions/dto.Response'
summary: 获取工作空间
tags:
- Workspace
/workspaces/{id}/members:
post:
consumes:
- application/json
description: 向工作空间添加新成员
parameters:
- description: 工作空间ID
in: path
name: id
required: true
type: integer
- description: 添加成员请求
in: body
name: request
required: true
schema:
$ref: '#/definitions/dto.AddWorkspaceMemberRequest'
produces:
- application/json
responses:
"200":
description: OK
schema:
allOf:
- $ref: '#/definitions/dto.Response'
- properties:
data:
$ref: '#/definitions/entity.WorkspaceMember'
type: object
"400":
description: 参数验证失败
schema:
allOf:
- $ref: '#/definitions/dto.Response'
- properties:
data:
items:
$ref: '#/definitions/dto.ValidateError'
type: array
type: object
"404":
description: 工作空间不存在
schema:
$ref: '#/definitions/dto.Response'
"409":
description: 成员已存在
schema:
$ref: '#/definitions/dto.Response'
summary: 添加工作空间成员
tags:
- Workspace
/workspaces/{id}/members/{user_id}:
delete:
consumes:
- application/json
description: 从工作空间移除成员
parameters:
- description: 工作空间ID
in: path
name: id
required: true
type: integer
- description: 用户ID
in: path
name: user_id
required: true
type: string
produces:
- application/json
responses:
"200":
description: OK
schema:
$ref: '#/definitions/dto.Response'
"404":
description: 工作空间不存在或成员不存在
schema:
$ref: '#/definitions/dto.Response'
summary: 移除工作空间成员
tags:
- Workspace
/workspaces/{workspace_id}/collections:
get:
consumes:
- application/json
description: 获取指定工作空间下的所有集合
parameters:
- description: 工作空间ID
in: path
name: workspace_id
required: true
type: integer
produces:
- application/json
responses:
"200":
description: OK
schema:
allOf:
- $ref: '#/definitions/dto.Response'
- properties:
data:
items:
$ref: '#/definitions/leafdev_top_Leaf_leaf-library-3_internal_entity.Collection'
type: array
type: object
"404":
description: 工作空间不存在
schema:
$ref: '#/definitions/dto.Response'
summary: 列出集合列表
tags:
- Collection
securityDefinitions:
ApiKeyAuth:
in: header

1
go.sum
View File

@ -128,6 +128,7 @@ github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/subcommands v1.2.0 h1:vWQspBTo2nEqTUFita5/KeEWlUL8kQObDFbub/EN9oE=
github.com/google/subcommands v1.2.0/go.mod h1:ZjhPrFU+Olkh9WazFPsl27BQ4UPiG37m3yTrtFlrHVk=
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=

View File

@ -0,0 +1,168 @@
package controller
import (
"github.com/gofiber/fiber/v2"
_ "leafdev.top/Leaf/leaf-library-3/internal/entity"
"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/workspace"
"leafdev.top/Leaf/leaf-library-3/internal/types/dto"
"leafdev.top/Leaf/leaf-library-3/internal/types/errs"
)
type CollectionController struct {
collectionService *collection.Service
workspaceService *workspace.Service
authService *auth.Service
}
func NewCollectionController(
collectionService *collection.Service,
workspaceService *workspace.Service,
authService *auth.Service,
) *CollectionController {
return &CollectionController{
collectionService: collectionService,
workspaceService: workspaceService,
authService: authService,
}
}
// CreateCollection 创建集合
// @Summary 创建集合
// @Description 在工作空间下创建新的集合
// @Tags Collection
// @Accept json
// @Produce json
// @Param request body dto.CreateCollectionRequest true "创建集合请求"
// @Success 200 {object} dto.Response{data=entity.Collection}
// @Failure 400 {object} dto.Response{data=[]dto.ValidateError} "参数验证失败"
// @Failure 404 {object} dto.Response "工作空间不存在"
// @Router /collections [post]
func (c *CollectionController) CreateCollection(ctx *fiber.Ctx) error {
var req dto.CreateCollectionRequest
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).Message((*validationErrors)[0].Message).Status(fiber.StatusBadRequest).Send()
}
// 检查工作空间是否存在
exists, err := c.workspaceService.Exists(ctx.Context(), req.WorkspaceID)
if err != nil {
return err
}
if !exists {
return dto.Ctx(ctx).Error(errs.ErrWorkspaceNotExists).Status(fiber.StatusNotFound).Send()
}
collection, err := c.collectionService.Create(ctx.Context(), req.WorkspaceID, req.Name)
if err != nil {
return err
}
return dto.Ctx(ctx).Success(collection).Send()
}
// GetCollection 获取集合
// @Summary 获取集合
// @Description 根据集合ID获取集合详情
// @Tags Collection
// @Accept json
// @Produce json
// @Param id path int true "集合ID"
// @Success 200 {object} dto.Response{data=entity.Collection}
// @Failure 404 {object} dto.Response "集合不存在"
// @Router /collections/{id} [get]
func (c *CollectionController) GetCollection(ctx *fiber.Ctx) error {
var req dto.GetCollectionRequest
if err := ctx.ParamsParser(&req); err != nil {
return err
}
exists, err := c.collectionService.Exists(ctx.Context(), req.ID)
if err != nil {
return err
}
if !exists {
return dto.Ctx(ctx).Error(errs.ErrCollectionNotExists).Status(fiber.StatusNotFound).Send()
}
collection, err := c.collectionService.Get(ctx.Context(), req.ID)
if err != nil {
return err
}
return dto.Ctx(ctx).Success(collection).Send()
}
// ListCollections 列出工作空间下的所有集合
// @Summary 列出集合列表
// @Description 获取指定工作空间下的所有集合
// @Tags Collection
// @Accept json
// @Produce json
// @Param workspace_id path int true "工作空间ID"
// @Success 200 {object} dto.Response{data=[]entity.Collection}
// @Failure 404 {object} dto.Response "工作空间不存在"
// @Router /workspaces/{workspace_id}/collections [get]
func (c *CollectionController) ListCollections(ctx *fiber.Ctx) error {
var req dto.ListCollectionsRequest
if err := ctx.ParamsParser(&req); err != nil {
return err
}
// 检查工作空间是否存在
exists, err := c.workspaceService.Exists(ctx.Context(), req.WorkspaceID)
if err != nil {
return err
}
if !exists {
return dto.Ctx(ctx).Error(errs.ErrWorkspaceNotExists).Status(fiber.StatusNotFound).Send()
}
collections, err := c.collectionService.List(ctx.Context(), req.WorkspaceID)
if err != nil {
return err
}
return dto.Ctx(ctx).Success(collections).Send()
}
// DeleteCollection 删除集合
// @Summary 删除集合
// @Description 删除指定的集合
// @Tags Collection
// @Accept json
// @Produce json
// @Param id path int true "集合ID"
// @Success 200 {object} dto.Response
// @Failure 404 {object} dto.Response "集合不存在"
// @Router /collections/{id} [delete]
func (c *CollectionController) DeleteCollection(ctx *fiber.Ctx) error {
var req dto.DeleteCollectionRequest
if err := ctx.ParamsParser(&req); err != nil {
return err
}
exists, err := c.collectionService.Exists(ctx.Context(), req.ID)
if err != nil {
return err
}
if !exists {
return dto.Ctx(ctx).Error(errs.ErrCollectionNotExists).Status(fiber.StatusNotFound).Send()
}
err = c.collectionService.Delete(ctx.Context(), req.ID)
if err != nil {
return err
}
return dto.Ctx(ctx).Success(nil).Send()
}

View File

@ -0,0 +1,476 @@
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()
}

View File

@ -0,0 +1,240 @@
package controller
import (
"github.com/gofiber/fiber/v2"
_ "leafdev.top/Leaf/leaf-library-3/internal/entity"
"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/workspace"
"leafdev.top/Leaf/leaf-library-3/internal/types/dto"
"leafdev.top/Leaf/leaf-library-3/internal/types/errs"
"leafdev.top/Leaf/leaf-library-3/internal/types/user"
)
type WorkspaceController struct {
workspaceService *workspace.Service
authService *auth.Service
}
func NewWorkspaceController(workspaceService *workspace.Service, authService *auth.Service) *WorkspaceController {
return &WorkspaceController{
workspaceService: workspaceService,
authService: authService,
}
}
// CreateWorkspace 创建工作空间
// @Summary 创建工作空间
// @Description 创建一个新的工作空间
// @Tags Workspace
// @Accept json
// @Produce json
// @Param request body dto.CreateWorkspaceRequest true "创建工作空间请求"
// @Success 200 {object} dto.Response{data=entity.Workspace}
// @Failure 400 {object} dto.Response{data=[]dto.ValidateError} "参数验证失败"
// @Router /workspaces [post]
func (c *WorkspaceController) CreateWorkspace(ctx *fiber.Ctx) error {
var req dto.CreateWorkspaceRequest
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()
}
// 从上下文获取用户ID
user := c.authService.GetUser(ctx)
userId := user.ID
workspace, err := c.workspaceService.Create(ctx.Context(), userId, req.Name)
if err != nil {
return err
}
return dto.Ctx(ctx).Success(workspace).Send()
}
// GetWorkspace 获取工作空间
// @Summary 获取工作空间
// @Description 根据工作空间ID获取工作空间详情
// @Tags Workspace
// @Accept json
// @Produce json
// @Param id path int true "工作空间ID"
// @Success 200 {object} dto.Response{data=entity.Workspace}
// @Failure 404 {object} dto.Response "工作空间不存在"
// @Router /workspaces/{id} [get]
func (c *WorkspaceController) GetWorkspace(ctx *fiber.Ctx) error {
var params dto.WorkspaceIDParam
if err := ctx.ParamsParser(&params); err != nil {
return err
}
exists, err := c.workspaceService.Exists(ctx.Context(), params.ID)
if err != nil {
return err
}
if !exists {
return dto.Ctx(ctx).Error(errs.ErrWorkspaceNotExists).Status(fiber.StatusNotFound).Send()
}
workspace, err := c.workspaceService.Get(ctx.Context(), params.ID)
if err != nil {
return err
}
return dto.Ctx(ctx).Success(workspace).Send()
}
// ListWorkspaces 列出用户的所有工作空间
// @Summary 列出工作空间列表
// @Description 获取当前用户的所有工作空间
// @Tags Workspace
// @Accept json
// @Produce json
// @Success 200 {object} dto.Response{data=[]entity.Workspace}
// @Router /workspaces [get]
func (c *WorkspaceController) ListWorkspaces(ctx *fiber.Ctx) error {
// 从上下文获取用户ID
user := c.authService.GetUser(ctx)
userId := user.ID
workspaces, err := c.workspaceService.List(ctx.Context(), userId)
if err != nil {
return err
}
return dto.Ctx(ctx).Success(workspaces).Send()
}
// DeleteWorkspace 删除工作空间
// @Summary 删除工作空间
// @Description 删除指定的工作空间
// @Tags Workspace
// @Accept json
// @Produce json
// @Param id path int true "工作空间ID"
// @Success 200 {object} dto.Response
// @Failure 404 {object} dto.Response "工作空间不存在"
// @Router /workspaces/{id} [delete]
func (c *WorkspaceController) DeleteWorkspace(ctx *fiber.Ctx) error {
var params dto.WorkspaceIDParam
if err := ctx.ParamsParser(&params); err != nil {
return err
}
exists, err := c.workspaceService.Exists(ctx.Context(), params.ID)
if err != nil {
return err
}
if !exists {
return dto.Ctx(ctx).Error(errs.ErrWorkspaceNotExists).Status(fiber.StatusNotFound).Send()
}
err = c.workspaceService.DeleteWorkspace(ctx.Context(), params.ID)
if err != nil {
return err
}
return dto.Ctx(ctx).Success(nil).Send()
}
// AddMember 添加工作空间成员
// @Summary 添加工作空间成员
// @Description 向工作空间添加新成员
// @Tags Workspace
// @Accept json
// @Produce json
// @Param id path int true "工作空间ID"
// @Param request body dto.AddWorkspaceMemberRequest true "添加成员请求"
// @Success 200 {object} dto.Response{data=entity.WorkspaceMember}
// @Failure 400 {object} dto.Response{data=[]dto.ValidateError} "参数验证失败"
// @Failure 404 {object} dto.Response "工作空间不存在"
// @Failure 409 {object} dto.Response "成员已存在"
// @Router /workspaces/{id}/members [post]
func (c *WorkspaceController) AddMember(ctx *fiber.Ctx) error {
var params dto.WorkspaceIDParam
if err := ctx.ParamsParser(&params); err != nil {
return err
}
exists, err := c.workspaceService.Exists(ctx.Context(), params.ID)
if err != nil {
return err
}
if !exists {
return dto.Ctx(ctx).Error(errs.ErrWorkspaceNotExists).Status(fiber.StatusNotFound).Send()
}
var req dto.AddWorkspaceMemberRequest
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()
}
workspace, err := c.workspaceService.Get(ctx.Context(), params.ID)
if err != nil {
return err
}
member, err := c.workspaceService.AddMember(ctx.Context(), req.UserID, workspace)
if err != nil {
if err == errs.ErrMemberAlreadyExists {
return dto.Ctx(ctx).Error(err).Status(fiber.StatusConflict).Send()
}
return err
}
return dto.Ctx(ctx).Success(member).Send()
}
// RemoveMember 移除工作空间成员
// @Summary 移除工作空间成员
// @Description 从工作空间移除成员
// @Tags Workspace
// @Accept json
// @Produce json
// @Param id path int true "工作空间ID"
// @Param user_id path string true "用户ID"
// @Success 200 {object} dto.Response
// @Failure 404 {object} dto.Response "工作空间不存在或成员不存在"
// @Router /workspaces/{id}/members/{user_id} [delete]
func (c *WorkspaceController) RemoveMember(ctx *fiber.Ctx) error {
var params dto.WorkspaceMemberParam
if err := ctx.ParamsParser(&params); err != nil {
return err
}
exists, err := c.workspaceService.Exists(ctx.Context(), params.WorkspaceID)
if err != nil {
return err
}
if !exists {
return dto.Ctx(ctx).Error(errs.ErrWorkspaceNotExists).Status(fiber.StatusNotFound).Send()
}
workspace, err := c.workspaceService.Get(ctx.Context(), params.WorkspaceID)
if err != nil {
return err
}
err = c.workspaceService.RemoveMember(ctx.Context(), user.ID(params.UserID), workspace)
if err != nil {
if err == errs.ErrMemberNotExists {
return dto.Ctx(ctx).Error(err).Status(fiber.StatusNotFound).Send()
}
return err
}
return dto.Ctx(ctx).Success(nil).Send()
}

View File

@ -3,8 +3,8 @@ package http
import (
"github.com/gofiber/fiber/v2"
"github.com/google/wire"
"leafdev.top/Leaf/leaf-library-3/internal/api/http/controller"
"leafdev.top/Leaf/leaf-library-3/internal/api/http/middleware"
v1 "leafdev.top/Leaf/leaf-library-3/internal/api/http/v1"
"leafdev.top/Leaf/leaf-library-3/internal/base/conf"
"leafdev.top/Leaf/leaf-library-3/internal/base/logger"
"leafdev.top/Leaf/leaf-library-3/internal/services/auth"
@ -22,14 +22,21 @@ type Middleware struct {
}
type Handlers struct {
User *v1.UserController
// User *v1.UserController
Workspace *controller.WorkspaceController
Collection *controller.CollectionController
Document *controller.DocumentController
}
func NewHandler(
user *v1.UserController,
workspace *controller.WorkspaceController,
collection *controller.CollectionController,
document *controller.DocumentController,
) *Handlers {
return &Handlers{
User: user,
Workspace: workspace,
Collection: collection,
Document: document,
}
}
@ -51,7 +58,10 @@ var ProviderSet = wire.NewSet(
NewMiddleware,
// Init Controller
v1.NewUserController,
// controller.NewUserController,
controller.NewWorkspaceController,
controller.NewCollectionController,
controller.NewDocumentController,
// Init Handler
NewHandler,

View File

@ -1,56 +0,0 @@
package v1
import (
"net/http"
"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/types/dto"
)
type UserController struct {
authService *auth.Service
}
func NewUserController(authService *auth.Service) *UserController {
return &UserController{authService}
}
// Test godoc
// @Summary Greet
// @Description 测试接口,将会返回当前用户的信息
// @Tags ping
// @Accept json
// @Produce json
// @Security ApiKeyAuth
// @deprecated true
// @Success 200 {object} response.Body{data=schema.CurrentUserResponse}
// @Failure 400 {object} response.Body
// @Router /api/v1/ping [get]
func (u *UserController) Test(c *fiber.Ctx) error {
user := u.authService.GetUser(c)
// bind
var testRequest = &dto.TestRequest{}
err := c.QueryParser(testRequest)
if err != nil {
return err
}
// 验证
validationErrors, err := validator.Struct(testRequest)
if err != nil {
return dto.Ctx(c).Error(err).Data(validationErrors).Send()
}
var currentUserResponse = &dto.CurrentUserResponse{
IP: c.IP(),
Valid: user.Valid,
UserEmail: user.Token.Email,
UserId: user.Token.Sub,
UserName: user.Token.Name,
}
return dto.Ctx(c).Status(http.StatusOK).Data(currentUserResponse).Send()
}

View File

@ -39,6 +39,7 @@ func errorConverter(logger *logger.Logger, ctx *fiber.Ctx, err error) error {
case errors.Is(err, gorm.ErrRecordNotFound):
errorMsg = errs.ErrNotFound
status = http.StatusNotFound
default:
logger.Sugar.Errorf("fiber error: %s", err)

View File

@ -27,7 +27,7 @@ func newBlockChunk(db *gorm.DB, opts ...gen.DOOption) blockChunk {
tableName := _blockChunk.blockChunkDo.TableName()
_blockChunk.ALL = field.NewAsterisk(tableName)
_blockChunk.Id = field.NewUint(tableName, "id")
_blockChunk.ID = field.NewUint(tableName, "id")
_blockChunk.CreatedAt = field.NewTime(tableName, "created_at")
_blockChunk.UpdatedAt = field.NewTime(tableName, "updated_at")
_blockChunk.DocumentBlockId = field.NewUint(tableName, "document_block_id")
@ -87,7 +87,7 @@ type blockChunk struct {
blockChunkDo
ALL field.Asterisk
Id field.Uint
ID field.Uint
CreatedAt field.Time
UpdatedAt field.Time
DocumentBlockId field.Uint
@ -109,7 +109,7 @@ func (b blockChunk) As(alias string) *blockChunk {
func (b *blockChunk) updateTableName(table string) *blockChunk {
b.ALL = field.NewAsterisk(table)
b.Id = field.NewUint(table, "id")
b.ID = field.NewUint(table, "id")
b.CreatedAt = field.NewTime(table, "created_at")
b.UpdatedAt = field.NewTime(table, "updated_at")
b.DocumentBlockId = field.NewUint(table, "document_block_id")
@ -131,7 +131,7 @@ func (b *blockChunk) GetFieldByName(fieldName string) (field.OrderExpr, bool) {
func (b *blockChunk) fillFieldMap() {
b.fieldMap = make(map[string]field.Expr, 6)
b.fieldMap["id"] = b.Id
b.fieldMap["id"] = b.ID
b.fieldMap["created_at"] = b.CreatedAt
b.fieldMap["updated_at"] = b.UpdatedAt
b.fieldMap["document_block_id"] = b.DocumentBlockId

View File

@ -27,7 +27,7 @@ func newCollection(db *gorm.DB, opts ...gen.DOOption) collection {
tableName := _collection.collectionDo.TableName()
_collection.ALL = field.NewAsterisk(tableName)
_collection.Id = field.NewUint(tableName, "id")
_collection.ID = field.NewUint(tableName, "id")
_collection.CreatedAt = field.NewTime(tableName, "created_at")
_collection.UpdatedAt = field.NewTime(tableName, "updated_at")
_collection.Name = field.NewString(tableName, "name")
@ -48,7 +48,7 @@ type collection struct {
collectionDo
ALL field.Asterisk
Id field.Uint
ID field.Uint
CreatedAt field.Time
UpdatedAt field.Time
Name field.String
@ -71,7 +71,7 @@ func (c collection) As(alias string) *collection {
func (c *collection) updateTableName(table string) *collection {
c.ALL = field.NewAsterisk(table)
c.Id = field.NewUint(table, "id")
c.ID = field.NewUint(table, "id")
c.CreatedAt = field.NewTime(table, "created_at")
c.UpdatedAt = field.NewTime(table, "updated_at")
c.Name = field.NewString(table, "name")
@ -94,7 +94,7 @@ func (c *collection) GetFieldByName(fieldName string) (field.OrderExpr, bool) {
func (c *collection) fillFieldMap() {
c.fieldMap = make(map[string]field.Expr, 7)
c.fieldMap["id"] = c.Id
c.fieldMap["id"] = c.ID
c.fieldMap["created_at"] = c.CreatedAt
c.fieldMap["updated_at"] = c.UpdatedAt
c.fieldMap["name"] = c.Name

View File

@ -27,13 +27,14 @@ func newDocumentBlock(db *gorm.DB, opts ...gen.DOOption) documentBlock {
tableName := _documentBlock.documentBlockDo.TableName()
_documentBlock.ALL = field.NewAsterisk(tableName)
_documentBlock.Id = field.NewUint(tableName, "id")
_documentBlock.ID = field.NewUint(tableName, "id")
_documentBlock.CreatedAt = field.NewTime(tableName, "created_at")
_documentBlock.UpdatedAt = field.NewTime(tableName, "updated_at")
_documentBlock.DocumentId = field.NewUint(tableName, "document_id")
_documentBlock.Type = field.NewString(tableName, "type")
_documentBlock.Content = field.NewString(tableName, "content")
_documentBlock.Hash = field.NewString(tableName, "hash")
_documentBlock.Order_ = field.NewFloat64(tableName, "order")
_documentBlock.Document = documentBlockBelongsToDocument{
db: db.Session(&gorm.Session{}),
@ -72,13 +73,14 @@ type documentBlock struct {
documentBlockDo
ALL field.Asterisk
Id field.Uint
ID field.Uint
CreatedAt field.Time
UpdatedAt field.Time
DocumentId field.Uint
Type field.String
Content field.String
Hash field.String
Order_ field.Float64
Document documentBlockBelongsToDocument
fieldMap map[string]field.Expr
@ -96,13 +98,14 @@ func (d documentBlock) As(alias string) *documentBlock {
func (d *documentBlock) updateTableName(table string) *documentBlock {
d.ALL = field.NewAsterisk(table)
d.Id = field.NewUint(table, "id")
d.ID = field.NewUint(table, "id")
d.CreatedAt = field.NewTime(table, "created_at")
d.UpdatedAt = field.NewTime(table, "updated_at")
d.DocumentId = field.NewUint(table, "document_id")
d.Type = field.NewString(table, "type")
d.Content = field.NewString(table, "content")
d.Hash = field.NewString(table, "hash")
d.Order_ = field.NewFloat64(table, "order")
d.fillFieldMap()
@ -119,14 +122,15 @@ func (d *documentBlock) GetFieldByName(fieldName string) (field.OrderExpr, bool)
}
func (d *documentBlock) fillFieldMap() {
d.fieldMap = make(map[string]field.Expr, 8)
d.fieldMap["id"] = d.Id
d.fieldMap = make(map[string]field.Expr, 9)
d.fieldMap["id"] = d.ID
d.fieldMap["created_at"] = d.CreatedAt
d.fieldMap["updated_at"] = d.UpdatedAt
d.fieldMap["document_id"] = d.DocumentId
d.fieldMap["type"] = d.Type
d.fieldMap["content"] = d.Content
d.fieldMap["hash"] = d.Hash
d.fieldMap["order"] = d.Order_
}

View File

@ -27,7 +27,7 @@ func newDocument(db *gorm.DB, opts ...gen.DOOption) document {
tableName := _document.documentDo.TableName()
_document.ALL = field.NewAsterisk(tableName)
_document.Id = field.NewUint(tableName, "id")
_document.ID = field.NewUint(tableName, "id")
_document.CreatedAt = field.NewTime(tableName, "created_at")
_document.UpdatedAt = field.NewTime(tableName, "updated_at")
_document.Name = field.NewString(tableName, "name")
@ -82,7 +82,7 @@ type document struct {
documentDo
ALL field.Asterisk
Id field.Uint
ID field.Uint
CreatedAt field.Time
UpdatedAt field.Time
Name field.String
@ -111,7 +111,7 @@ func (d document) As(alias string) *document {
func (d *document) updateTableName(table string) *document {
d.ALL = field.NewAsterisk(table)
d.Id = field.NewUint(table, "id")
d.ID = field.NewUint(table, "id")
d.CreatedAt = field.NewTime(table, "created_at")
d.UpdatedAt = field.NewTime(table, "updated_at")
d.Name = field.NewString(table, "name")
@ -136,7 +136,7 @@ func (d *document) GetFieldByName(fieldName string) (field.OrderExpr, bool) {
func (d *document) fillFieldMap() {
d.fieldMap = make(map[string]field.Expr, 11)
d.fieldMap["id"] = d.Id
d.fieldMap["id"] = d.ID
d.fieldMap["created_at"] = d.CreatedAt
d.fieldMap["updated_at"] = d.UpdatedAt
d.fieldMap["name"] = d.Name

View File

@ -27,7 +27,7 @@ func newWorkspaceMember(db *gorm.DB, opts ...gen.DOOption) workspaceMember {
tableName := _workspaceMember.workspaceMemberDo.TableName()
_workspaceMember.ALL = field.NewAsterisk(tableName)
_workspaceMember.Id = field.NewUint(tableName, "id")
_workspaceMember.ID = field.NewUint(tableName, "id")
_workspaceMember.CreatedAt = field.NewTime(tableName, "created_at")
_workspaceMember.UpdatedAt = field.NewTime(tableName, "updated_at")
_workspaceMember.WorkspaceId = field.NewUint(tableName, "workspace_id")
@ -47,7 +47,7 @@ type workspaceMember struct {
workspaceMemberDo
ALL field.Asterisk
Id field.Uint
ID field.Uint
CreatedAt field.Time
UpdatedAt field.Time
WorkspaceId field.Uint
@ -69,7 +69,7 @@ func (w workspaceMember) As(alias string) *workspaceMember {
func (w *workspaceMember) updateTableName(table string) *workspaceMember {
w.ALL = field.NewAsterisk(table)
w.Id = field.NewUint(table, "id")
w.ID = field.NewUint(table, "id")
w.CreatedAt = field.NewTime(table, "created_at")
w.UpdatedAt = field.NewTime(table, "updated_at")
w.WorkspaceId = field.NewUint(table, "workspace_id")
@ -91,7 +91,7 @@ func (w *workspaceMember) GetFieldByName(fieldName string) (field.OrderExpr, boo
func (w *workspaceMember) fillFieldMap() {
w.fieldMap = make(map[string]field.Expr, 6)
w.fieldMap["id"] = w.Id
w.fieldMap["id"] = w.ID
w.fieldMap["created_at"] = w.CreatedAt
w.fieldMap["updated_at"] = w.UpdatedAt
w.fieldMap["workspace_id"] = w.WorkspaceId

View File

@ -27,7 +27,7 @@ func newWorkspace(db *gorm.DB, opts ...gen.DOOption) workspace {
tableName := _workspace.workspaceDo.TableName()
_workspace.ALL = field.NewAsterisk(tableName)
_workspace.Id = field.NewUint(tableName, "id")
_workspace.ID = field.NewUint(tableName, "id")
_workspace.CreatedAt = field.NewTime(tableName, "created_at")
_workspace.UpdatedAt = field.NewTime(tableName, "updated_at")
_workspace.Name = field.NewString(tableName, "name")
@ -43,7 +43,7 @@ type workspace struct {
workspaceDo
ALL field.Asterisk
Id field.Uint
ID field.Uint
CreatedAt field.Time
UpdatedAt field.Time
Name field.String
@ -65,7 +65,7 @@ func (w workspace) As(alias string) *workspace {
func (w *workspace) updateTableName(table string) *workspace {
w.ALL = field.NewAsterisk(table)
w.Id = field.NewUint(table, "id")
w.ID = field.NewUint(table, "id")
w.CreatedAt = field.NewTime(table, "created_at")
w.UpdatedAt = field.NewTime(table, "updated_at")
w.Name = field.NewString(table, "name")
@ -88,7 +88,7 @@ func (w *workspace) GetFieldByName(fieldName string) (field.OrderExpr, bool) {
func (w *workspace) fillFieldMap() {
w.fieldMap = make(map[string]field.Expr, 6)
w.fieldMap["id"] = w.Id
w.fieldMap["id"] = w.ID
w.fieldMap["created_at"] = w.CreatedAt
w.fieldMap["updated_at"] = w.UpdatedAt
w.fieldMap["name"] = w.Name

View File

@ -1,6 +1,6 @@
-- +goose Up
ALTER TABLE documents
ADD COLUMN parent_id BIGINT REFERENCES documents (id);
ADD COLUMN parent_id BIGINT REFERENCES documents (id) NULL;
-- +goose Down
ALTER TABLE documents

View File

@ -0,0 +1,10 @@
-- +goose Up
ALTER TABLE document_blocks
ADD COLUMN "order" DOUBLE PRECISION NOT NULL DEFAULT 1000.0;
CREATE INDEX idx_document_blocks_order ON document_blocks ("order");
-- +goose Down
DROP INDEX IF EXISTS idx_document_blocks_order;
ALTER TABLE document_blocks
DROP COLUMN "order";

View File

@ -16,8 +16,8 @@ type Document struct {
CollectionId dto.EntityId `json:"collection_id"`
Collection *Collection `json:"collection"`
ParentId dto.EntityId `json:"parent_id"`
Parent *Document `json:"parent"`
ParentId *dto.EntityId `json:"parent_id"`
Parent *Document `json:"parent"`
DeletedAt gorm.DeletedAt `json:"deleted_at"`
}
@ -32,9 +32,10 @@ type DocumentBlock struct {
DocumentId dto.EntityId `json:"document_id"`
Document *Document `json:"document"`
Type string `json:"type"`
Content string `json:"content"`
Hash string `json:"hash"`
Type string `json:"type"`
Content string `json:"content"`
Hash string `json:"hash"`
Order float64 `json:"order" gorm:"index"`
}
func (*DocumentBlock) TableName() string {

View File

@ -8,7 +8,7 @@ import (
// Model 是所有 entity 的基类,后期要将所有的 Base 改成这种形式
type Model struct {
Id dto.EntityId `gorm:"primarykey" json:"id"`
ID dto.EntityId `gorm:"primarykey" json:"id"`
CreatedAt time.Time `gorm:"autoUpdateTime:milli" json:"created_at"`
UpdatedAt time.Time `gorm:"autoUpdateTime:milli" json:"updated_at"`
//DeletedAt gorm.DeletedAt `gorm:"index"`

View File

@ -9,7 +9,7 @@ import (
type Workspace struct {
Model
Name string `json:"name"`
UserId user.Id `json:"user_id"`
UserId user.ID `json:"user_id"`
DeletedAt gorm.DeletedAt `json:"deleted_at"`
}
@ -20,7 +20,7 @@ func (*Workspace) TableName() string {
type WorkspaceMember struct {
Model
WorkspaceId dto.EntityId `json:"workspace_id"`
UserId user.Id `json:"user_id"`
UserId user.ID `json:"user_id"`
Workspace *Workspace `json:"workspace"`
}

View File

@ -27,14 +27,14 @@ func init() {
})
}
func Struct(data interface{}) (validationErrors *[]dto.ValidateError, err error) {
func Struct(data interface{}) (validationErrors *[]dto.ValidateError, ok bool, err error) {
v := validate.Struct(data)
var e error
var ves []dto.ValidateError
if v.Validate() {
return &ves, e // 返回指针
return &ves, true, nil // 返回指针
} else {
e = ErrValidationFailed
@ -60,5 +60,5 @@ func Struct(data interface{}) (validationErrors *[]dto.ValidateError, err error)
// //}
//}
return &ves, e
return &ves, false, e
}

View File

@ -5,25 +5,15 @@ import (
"leafdev.top/Leaf/leaf-library-3/internal/api/http"
)
// 两种方法都可以
//type Api struct {
// User *v1.UserController
//}
type Api struct {
HttpHandler *http.Handlers
Middleware *http.Middleware
}
func NewApiRoute(
//User *v1.UserController,
HttpHandler *http.Handlers,
Middleware *http.Middleware,
) *Api {
//return &Api{
// User,
//}
return &Api{
HttpHandler,
Middleware,
@ -31,20 +21,61 @@ func NewApiRoute(
}
func (a *Api) V1(r fiber.Router) {
auth := r.Group("/api/v1")
auth := r.Group("")
{
// 要求认证
auth.Use(a.Middleware.Auth.Handler())
// RoutePermission 为权限验证
//auth.Get("/ping", a.Middleware.RBAC.RoutePermission(), a.HttpHandler.User.Test)
// 工作空间路由
workspaces := auth.Group("/workspaces")
{
workspaces.Post("/", a.HttpHandler.Workspace.CreateWorkspace)
workspaces.Get("/", a.HttpHandler.Workspace.ListWorkspaces)
workspaces.Get("/:id", a.HttpHandler.Workspace.GetWorkspace)
workspaces.Delete("/:id", a.HttpHandler.Workspace.DeleteWorkspace)
auth.Get("/ping", a.HttpHandler.User.Test)
}
guest := r.Group("/api/v1")
{
guest.Get("/guest_ping", a.HttpHandler.User.Test)
// 工作空间成员管理
workspaces.Post("/:id/members", a.HttpHandler.Workspace.AddMember)
workspaces.Delete("/:id/members/:user_id", a.HttpHandler.Workspace.RemoveMember)
// 工作空间下的集合
workspaces.Get("/:workspace_id/collections", a.HttpHandler.Collection.ListCollections)
}
// 集合路由
collections := auth.Group("/collections")
{
collections.Post("/", a.HttpHandler.Collection.CreateCollection)
collections.Get("/:id", a.HttpHandler.Collection.GetCollection)
// 集合下的文档
collections.Get("/:collection_id/documents", a.HttpHandler.Document.ListDocuments)
}
// 文档路由
documents := auth.Group("/documents")
{
documents.Post("/", a.HttpHandler.Document.CreateDocument)
documents.Get("/:id", a.HttpHandler.Document.GetDocument)
documents.Put("/:id", a.HttpHandler.Document.UpdateDocument)
documents.Delete("/:id", a.HttpHandler.Document.DeleteDocument)
// 文档块
documents.Post("/:document_id/blocks", a.HttpHandler.Document.CreateBlock)
documents.Get("/:document_id/blocks", a.HttpHandler.Document.ListBlocks)
}
// 文档块路由
blocks := auth.Group("/blocks")
{
blocks.Put("/:block_id", a.HttpHandler.Document.UpdateBlock)
blocks.Delete("/:block_id", a.HttpHandler.Document.DeleteBlock)
blocks.Post("/:block_id/move", a.HttpHandler.Document.MoveBlock)
}
}
// guest := r.Group("/api/v1")
// {
// guest.Get("/guest_ping", a.HttpHandler.User.Test)
// }
}

View File

@ -27,7 +27,7 @@ func (a *Service) GetUser(ctx *fiber.Ctx) *user.User {
userCtx := ctx.Locals(constants.AuthMiddlewareKey)
u, ok := userCtx.(*user.User)
u.Id = u.Token.Sub
u.ID = u.Token.Sub
if !ok {
panic("User context is not valid")
@ -40,7 +40,7 @@ func (a *Service) GetCtx(ctx context.Context) *user.User {
userCtx := ctx.Value(constants.AuthMiddlewareKey)
u, ok := userCtx.(*user.User)
u.Id = u.Token.Sub
u.ID = u.Token.Sub
if !ok {
panic("User context is not valid")
@ -53,7 +53,7 @@ func (a *Service) GetUserSafe(ctx *fiber.Ctx) (*user.User, bool) {
userCtx := ctx.Locals(constants.AuthMiddlewareKey)
u, ok := userCtx.(*user.User)
u.Id = u.Token.Sub
u.ID = u.Token.Sub
return u, ok
}
@ -62,7 +62,7 @@ func (a *Service) GetCtxSafe(ctx context.Context) (*user.User, bool) {
userCtx := ctx.Value(constants.AuthMiddlewareKey)
u, ok := userCtx.(*user.User)
u.Id = u.Token.Sub
u.ID = u.Token.Sub
return u, ok
}
@ -90,7 +90,7 @@ func (a *Service) parseUserJWT(tokenType constants.JwtTokenTypes, jwtToken strin
return nil, errs.NotValidToken
}
sub = user.Id(subStr)
sub = user.ID(subStr)
// 如果 token.Header 中没有 typ
if token.Header["typ"] == "" {

View File

@ -19,11 +19,11 @@ func (s *Service) Get(ctx context.Context, collectionId dto.EntityId) (*entity.C
return nil, errs.ErrCollectionNotExists
}
return s.dao.WithContext(ctx).Collection.Where(s.dao.Collection.Id.Eq(collectionId.Uint())).First()
return s.dao.WithContext(ctx).Collection.Where(s.dao.Collection.ID.Eq(collectionId.Uint())).First()
}
func (s *Service) Exists(ctx context.Context, CollectionId dto.EntityId) (bool, error) {
num, err := s.dao.WithContext(ctx).Collection.Where(s.dao.Collection.Id.Eq(CollectionId.Uint())).Count()
num, err := s.dao.WithContext(ctx).Collection.Where(s.dao.Collection.ID.Eq(CollectionId.Uint())).Count()
return num > 0, err
}
@ -47,59 +47,32 @@ func (s *Service) Create(ctx context.Context, workspaceId dto.EntityId, name str
return collection, nil
}
//
//func (s *Service) AddMember(ctx context.Context, userId schema.UserId, CollectionEntity *entity.Collection) (*entity.CollectionMember, error) {
// memberExists, err := s.MemberExists(ctx, userId, CollectionEntity)
// if err != nil {
// return nil, err
// }
//
// if memberExists {
// return nil, errs.ErrMemberAlreadyExists
// }
//
// var CollectionMember = &entity.CollectionMember{
// UserId: userId,
// CollectionId: CollectionEntity.Id,
// }
//
// err = s.dao.WithContext(ctx).CollectionMember.Create(CollectionMember)
//
// return CollectionMember, err
//}
//
//func (s *Service) RemoveMember(ctx context.Context, userId schema.UserId, CollectionEntity *entity.Collection) error {
// memberExists, err := s.MemberExists(ctx, userId, CollectionEntity)
// if err != nil {
// return err
// }
//
// if !memberExists {
// return errs.ErrMemberNotExists
// }
//
// _, err = s.dao.WithContext(ctx).CollectionMember.
// Where(
// s.dao.CollectionMember.UserId.Eq(userId.String()),
// s.dao.CollectionMember.CollectionId.Eq(
// CollectionEntity.Id.Uint()),
// ).
// Delete()
//
// return err
//}
//
//func (s *Service) MemberExists(ctx context.Context, userId schema.UserId, CollectionEntity *entity.Collection) (bool, error) {
// if num, err := s.dao.WithContext(ctx).CollectionMember.Where(s.dao.CollectionMember.UserId.Eq(userId.String()), s.dao.CollectionMember.CollectionId.Eq(CollectionEntity.Id.Uint())).Count(); err != nil {
// return false, err
// } else {
// return num > 0, nil
// }
//}
//
//func (s *Service) DeleteCollection(ctx context.Context, CollectionId dto.EntityId) error {
// // 删除始终是一个复杂的工程,所以先标记为软删除
// _, err := s.dao.WithContext(ctx).Collection.Where(s.dao.Collection.Id.Eq(CollectionId.Uint())).Delete()
//
// return err
//}
func (s *Service) Update(ctx context.Context, collectionId dto.EntityId, name string) (*entity.Collection, error) {
collection, err := s.Get(ctx, collectionId)
if err != nil {
return nil, err
}
collection.Name = name
err = s.dao.WithContext(ctx).Collection.Save(collection)
if err != nil {
return nil, err
}
return collection, nil
}
func (s *Service) Delete(ctx context.Context, collectionId dto.EntityId) error {
exists, err := s.Exists(ctx, collectionId)
if err != nil {
return err
}
if !exists {
return errs.ErrCollectionNotExists
}
// 使用软删除
_, err = s.dao.WithContext(ctx).Collection.Where(s.dao.Collection.ID.Eq(collectionId.Uint())).Delete()
return err
}

View File

@ -1,105 +0,0 @@
package collection
import (
"context"
"leafdev.top/Leaf/leaf-library-3/internal/entity"
"leafdev.top/Leaf/leaf-library-3/internal/types/dto"
"leafdev.top/Leaf/leaf-library-3/internal/types/errs"
)
func (s *Service) Get(ctx context.Context, collectionId dto.EntityId) (*entity.Collection, error) {
exists, err := s.Exists(ctx, collectionId)
if err != nil {
return nil, err
}
if !exists {
return nil, errs.ErrCollectionNotExists
}
return s.dao.WithContext(ctx).Collection.Where(s.dao.Collection.Id.Eq(collectionId.Uint())).First()
}
func (s *Service) Exists(ctx context.Context, CollectionId dto.EntityId) (bool, error) {
num, err := s.dao.WithContext(ctx).Collection.Where(s.dao.Collection.Id.Eq(CollectionId.Uint())).Count()
return num > 0, err
}
func (s *Service) List(ctx context.Context, workspaceId dto.EntityId) ([]*entity.Collection, error) {
return s.dao.WithContext(ctx).Collection.Where(s.dao.Collection.WorkspaceId.Eq(workspaceId.Uint())).Find()
}
func (s *Service) Create(ctx context.Context, workspaceId dto.EntityId, name string) (*entity.Collection, error) {
var collection = &entity.Collection{
Name: name,
WorkspaceId: workspaceId,
}
err := s.dao.WithContext(ctx).Collection.Create(collection)
if err != nil {
return nil, err
}
return collection, nil
}
//
//func (s *Service) AddMember(ctx context.Context, userId schema.UserId, CollectionEntity *entity.Collection) (*entity.CollectionMember, error) {
// memberExists, err := s.MemberExists(ctx, userId, CollectionEntity)
// if err != nil {
// return nil, err
// }
//
// if memberExists {
// return nil, errs.ErrMemberAlreadyExists
// }
//
// var CollectionMember = &entity.CollectionMember{
// UserId: userId,
// CollectionId: CollectionEntity.Id,
// }
//
// err = s.dao.WithContext(ctx).CollectionMember.Create(CollectionMember)
//
// return CollectionMember, err
//}
//
//func (s *Service) RemoveMember(ctx context.Context, userId schema.UserId, CollectionEntity *entity.Collection) error {
// memberExists, err := s.MemberExists(ctx, userId, CollectionEntity)
// if err != nil {
// return err
// }
//
// if !memberExists {
// return errs.ErrMemberNotExists
// }
//
// _, err = s.dao.WithContext(ctx).CollectionMember.
// Where(
// s.dao.CollectionMember.UserId.Eq(userId.String()),
// s.dao.CollectionMember.CollectionId.Eq(
// CollectionEntity.Id.Uint()),
// ).
// Delete()
//
// return err
//}
//
//func (s *Service) MemberExists(ctx context.Context, userId schema.UserId, CollectionEntity *entity.Collection) (bool, error) {
// if num, err := s.dao.WithContext(ctx).CollectionMember.Where(s.dao.CollectionMember.UserId.Eq(userId.String()), s.dao.CollectionMember.CollectionId.Eq(CollectionEntity.Id.Uint())).Count(); err != nil {
// return false, err
// } else {
// return num > 0, nil
// }
//}
//
//func (s *Service) DeleteCollection(ctx context.Context, CollectionId dto.EntityId) error {
// // 删除始终是一个复杂的工程,所以先标记为软删除
// _, err := s.dao.WithContext(ctx).Collection.Where(s.dao.Collection.Id.Eq(CollectionId.Uint())).Delete()
//
// return err
//}

View File

@ -0,0 +1,256 @@
package document
import (
"context"
"crypto/sha256"
"encoding/hex"
"fmt"
"time"
"gorm.io/gorm"
"leafdev.top/Leaf/leaf-library-3/internal/entity"
"leafdev.top/Leaf/leaf-library-3/internal/types/dto"
)
// Get 获取文档
func (s *Service) Get(ctx context.Context, id dto.EntityId) (*entity.Document, error) {
return s.dao.Document.Where(s.dao.Document.ID.Eq(uint(id))).First()
}
// Create 创建文档
func (s *Service) Create(ctx context.Context, name string, workspaceId, collectionId dto.EntityId, parentId *dto.EntityId) (*entity.Document, error) {
doc := &entity.Document{
Name: name,
WorkspaceId: workspaceId,
CollectionId: collectionId,
ParentId: parentId,
}
err := s.dao.Document.Create(doc)
return doc, err
}
// Update 更新文档
func (s *Service) Update(ctx context.Context, id dto.EntityId, name string) (*entity.Document, error) {
doc, err := s.Get(ctx, id)
if err != nil {
return nil, err
}
doc.Name = name
err = s.dao.Document.Save(doc)
return doc, err
}
// Delete 删除文档
func (s *Service) Delete(ctx context.Context, id dto.EntityId) error {
_, err := s.dao.Document.Where(s.dao.Document.ID.Eq(uint(id))).Delete()
return err
}
// ListDocuments 列出文档树
func (s *Service) ListDocuments(ctx context.Context, collectionId dto.EntityId, parentId *dto.EntityId) ([]*entity.Document, error) {
q := s.dao.WithContext(ctx).Document.
Where(s.dao.Document.CollectionId.Eq(collectionId.Uint())).
Order(s.dao.Document.CreatedAt)
if parentId == nil {
// 获取根文档
q = q.Where(s.dao.Document.ParentId.IsNull())
} else {
// 获取指定父文档下的子文档
q = q.Where(s.dao.Document.ParentId.Eq(parentId.Uint()))
}
return q.Find()
}
// generateBlockHash 生成块的哈希值
func (s *Service) generateBlockHash(documentId dto.EntityId, blockType string, content string, timestamp time.Time) string {
// 组合所有相关信息
data := fmt.Sprintf("%d:%s:%s:%d", documentId, blockType, content, timestamp.UnixNano())
// 计算 SHA-256 摘要
hash := sha256.Sum256([]byte(data))
// 转换为十六进制字符串
return hex.EncodeToString(hash[:])
}
// CreateBlock 创建文档块
func (s *Service) CreateBlock(ctx context.Context, documentId dto.EntityId, blockType string, content string, afterBlockId *dto.EntityId) (*entity.DocumentBlock, error) {
// 生成块的哈希值
now := time.Now()
hash := s.generateBlockHash(documentId, blockType, content, now)
var order float64
if afterBlockId == nil {
order = 1000.0
} else {
// 获取前一个块
prevBlock, err := s.dao.WithContext(ctx).DocumentBlock.Where(s.dao.DocumentBlock.ID.Eq(afterBlockId.Uint())).First()
if err != nil {
return nil, err
}
// 获取前一个块后面的第一个块
nextBlock, err := s.dao.WithContext(ctx).DocumentBlock.
Where(s.dao.DocumentBlock.DocumentId.Eq(uint(documentId))).
Where(s.dao.DocumentBlock.Order_.Gt(prevBlock.Order)).
Order(s.dao.DocumentBlock.Order_).
First()
if err == gorm.ErrRecordNotFound {
// 如果没有后续块,则在前一个块的序号上加 1000
order = prevBlock.Order + 1000.0
} else if err != nil {
return nil, err
} else {
// 如果有后续块,则取前一个块和后一个块序号的中间值
order = (prevBlock.Order + nextBlock.Order) / 2
}
}
block := &entity.DocumentBlock{
DocumentId: documentId,
Type: blockType,
Content: content,
Order: order,
Hash: hash,
}
err := s.dao.WithContext(ctx).DocumentBlock.Create(block)
return block, err
}
// UpdateBlock 更新文档块
func (s *Service) UpdateBlock(ctx context.Context, blockId dto.EntityId, content string) (*entity.DocumentBlock, error) {
block, err := s.dao.WithContext(ctx).DocumentBlock.Where(s.dao.DocumentBlock.ID.Eq(blockId.Uint())).First()
if err != nil {
return nil, err
}
// 更新内容和哈希
now := time.Now()
block.Content = content
block.Hash = s.generateBlockHash(block.DocumentId, block.Type, content, now)
err = s.dao.WithContext(ctx).DocumentBlock.Save(block)
return block, err
}
// DeleteBlock 删除文档块
func (s *Service) DeleteBlock(ctx context.Context, blockId dto.EntityId) error {
_, err := s.dao.WithContext(ctx).DocumentBlock.Where(s.dao.DocumentBlock.ID.Eq(blockId.Uint())).Delete()
return err
}
// ListBlocks 列出文档的所有块
func (s *Service) ListBlocks(ctx context.Context, documentId dto.EntityId) ([]*entity.DocumentBlock, error) {
return s.dao.WithContext(ctx).DocumentBlock.
Where(s.dao.DocumentBlock.DocumentId.Eq(uint(documentId))).
Order(s.dao.DocumentBlock.Order_).
Find()
}
// MoveBlock 移动文档块
func (s *Service) MoveBlock(ctx context.Context, blockId dto.EntityId, afterBlockId *dto.EntityId) error {
block, err := s.dao.WithContext(ctx).DocumentBlock.Where(s.dao.DocumentBlock.ID.Eq(blockId.Uint())).First()
if err != nil {
return err
}
var newOrder float64
if afterBlockId == nil {
newOrder = 1000.0
} else {
// 获取目标位置的前一个块
prevBlock, err := s.dao.WithContext(ctx).DocumentBlock.Where(s.dao.DocumentBlock.ID.Eq(afterBlockId.Uint())).First()
if err != nil {
return err
}
// 获取目标位置的后一个块
nextBlock, err := s.dao.WithContext(ctx).DocumentBlock.
Where(s.dao.DocumentBlock.DocumentId.Eq(uint(block.DocumentId))).
Where(s.dao.DocumentBlock.Order_.Gt(prevBlock.Order)).
Order(s.dao.DocumentBlock.Order_).
First()
if err == gorm.ErrRecordNotFound {
newOrder = prevBlock.Order + 1000.0
} else if err != nil {
return err
} else {
newOrder = (prevBlock.Order + nextBlock.Order) / 2
}
}
block.Order = newOrder
// 移动后重新生成 Hash
now := time.Now()
block.Hash = s.generateBlockHash(block.DocumentId, block.Type, block.Content, now)
return s.dao.WithContext(ctx).DocumentBlock.Save(block)
}
// ReorderBlocks 重新排序文档块
func (s *Service) ReorderBlocks(ctx context.Context, documentId dto.EntityId) error {
blocks, err := s.ListBlocks(ctx, documentId)
if err != nil {
return err
}
// 重新设置每个块的顺序
for i, block := range blocks {
block.Order = float64((i + 1) * 1000)
// 重新生成 Hash
now := time.Now()
block.Hash = s.generateBlockHash(block.DocumentId, block.Type, block.Content, now)
if err := s.dao.WithContext(ctx).DocumentBlock.Save(block); err != nil {
return err
}
}
return nil
}
// HasChildren 检查文档是否有子文档
func (s *Service) HasChildren(ctx context.Context, id dto.EntityId) (bool, error) {
count, err := s.dao.Document.Where(s.dao.Document.ParentId.Eq(id.Uint())).Count()
if err != nil {
return false, err
}
return count > 0, nil
}
// ToDTO 将文档实体转换为 DTO
func (s *Service) ToDTO(ctx context.Context, doc *entity.Document) (*dto.DocumentDTO, error) {
hasChildren, err := s.HasChildren(ctx, doc.ID)
if err != nil {
return nil, err
}
return &dto.DocumentDTO{
BaseDTO: dto.BaseDTO{
ID: doc.ID,
CreatedAt: doc.CreatedAt,
UpdatedAt: doc.UpdatedAt,
},
Name: doc.Name,
WorkspaceID: doc.WorkspaceId,
CollectionID: doc.CollectionId,
ParentID: doc.ParentId,
HasChildren: hasChildren,
}, nil
}
// List 列出文档
func (s *Service) List(ctx context.Context, collectionId dto.EntityId, parentId *dto.EntityId) ([]*entity.Document, error) {
q := s.dao.Document.Where(s.dao.Document.CollectionId.Eq(uint(collectionId)))
if parentId != nil {
q = q.Where(s.dao.Document.ParentId.Eq(uint(*parentId)))
} else {
q = q.Where(s.dao.Document.ParentId.IsNull())
}
return q.Find()
}

View File

@ -1,4 +1,4 @@
package collection
package document
import (
"leafdev.top/Leaf/leaf-library-3/internal/base/conf"

View File

@ -3,23 +3,32 @@ package services
import (
"leafdev.top/Leaf/leaf-library-3/internal/base/logger"
"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/jwks"
"leafdev.top/Leaf/leaf-library-3/internal/services/stream"
"leafdev.top/Leaf/leaf-library-3/internal/services/workspace"
"github.com/google/wire"
)
type Service struct {
logger *logger.Logger
Jwks *jwks.JWKS
Auth *auth.Service
Stream *stream.Service
logger *logger.Logger
Jwks *jwks.JWKS
Auth *auth.Service
Stream *stream.Service
Workspace *workspace.Service
Collection *collection.Service
Document *document.Service
}
var Provide = wire.NewSet(
jwks.NewJWKS,
auth.NewService,
stream.NewService,
workspace.NewService,
collection.NewService,
document.NewService,
NewService,
)
@ -28,11 +37,17 @@ func NewService(
jwks *jwks.JWKS,
auth *auth.Service,
stream *stream.Service,
workspace *workspace.Service,
collection *collection.Service,
document *document.Service,
) *Service {
return &Service{
logger,
jwks,
auth,
stream,
workspace,
collection,
document,
}
}

View File

@ -20,20 +20,20 @@ func (s *Service) Get(ctx context.Context, workspaceId dto.EntityId) (*entity.Wo
return nil, errs.ErrWorkspaceNotExists
}
return s.dao.WithContext(ctx).Workspace.Where(s.dao.Workspace.Id.Eq(workspaceId.Uint())).First()
return s.dao.WithContext(ctx).Workspace.Where(s.dao.Workspace.ID.Eq(workspaceId.Uint())).First()
}
func (s *Service) Exists(ctx context.Context, workspaceId dto.EntityId) (bool, error) {
num, err := s.dao.WithContext(ctx).Workspace.Where(s.dao.Workspace.Id.Eq(workspaceId.Uint())).Count()
num, err := s.dao.WithContext(ctx).Workspace.Where(s.dao.Workspace.ID.Eq(workspaceId.Uint())).Count()
return num > 0, err
}
func (s *Service) List(ctx context.Context, userId user.Id) ([]*entity.Workspace, error) {
func (s *Service) List(ctx context.Context, userId user.ID) ([]*entity.Workspace, error) {
return s.dao.WithContext(ctx).Workspace.Where(s.dao.Workspace.UserId.Eq(userId.String())).Find()
}
func (s *Service) Create(ctx context.Context, userId user.Id, name string) (*entity.Workspace, error) {
func (s *Service) Create(ctx context.Context, userId user.ID, name string) (*entity.Workspace, error) {
var workspace = &entity.Workspace{
Name: name,
UserId: userId,
@ -54,7 +54,7 @@ func (s *Service) Create(ctx context.Context, userId user.Id, name string) (*ent
return workspace, nil
}
func (s *Service) AddMember(ctx context.Context, userId user.Id, workspaceEntity *entity.Workspace) (*entity.WorkspaceMember, error) {
func (s *Service) AddMember(ctx context.Context, userId user.ID, workspaceEntity *entity.Workspace) (*entity.WorkspaceMember, error) {
memberExists, err := s.MemberExists(ctx, userId, workspaceEntity)
if err != nil {
return nil, err
@ -66,7 +66,7 @@ func (s *Service) AddMember(ctx context.Context, userId user.Id, workspaceEntity
var workspaceMember = &entity.WorkspaceMember{
UserId: userId,
WorkspaceId: workspaceEntity.Id,
WorkspaceId: workspaceEntity.ID,
}
err = s.dao.WithContext(ctx).WorkspaceMember.Create(workspaceMember)
@ -74,7 +74,7 @@ func (s *Service) AddMember(ctx context.Context, userId user.Id, workspaceEntity
return workspaceMember, err
}
func (s *Service) RemoveMember(ctx context.Context, userId user.Id, workspaceEntity *entity.Workspace) error {
func (s *Service) RemoveMember(ctx context.Context, userId user.ID, workspaceEntity *entity.Workspace) error {
memberExists, err := s.MemberExists(ctx, userId, workspaceEntity)
if err != nil {
return err
@ -88,15 +88,15 @@ func (s *Service) RemoveMember(ctx context.Context, userId user.Id, workspaceEnt
Where(
s.dao.WorkspaceMember.UserId.Eq(userId.String()),
s.dao.WorkspaceMember.WorkspaceId.Eq(
workspaceEntity.Id.Uint()),
workspaceEntity.ID.Uint()),
).
Delete()
return err
}
func (s *Service) MemberExists(ctx context.Context, userId user.Id, workspaceEntity *entity.Workspace) (bool, error) {
if num, err := s.dao.WithContext(ctx).WorkspaceMember.Where(s.dao.WorkspaceMember.UserId.Eq(userId.String()), s.dao.WorkspaceMember.WorkspaceId.Eq(workspaceEntity.Id.Uint())).Count(); err != nil {
func (s *Service) MemberExists(ctx context.Context, userId user.ID, workspaceEntity *entity.Workspace) (bool, error) {
if num, err := s.dao.WithContext(ctx).WorkspaceMember.Where(s.dao.WorkspaceMember.UserId.Eq(userId.String()), s.dao.WorkspaceMember.WorkspaceId.Eq(workspaceEntity.ID.Uint())).Count(); err != nil {
return false, err
} else {
return num > 0, nil
@ -105,7 +105,12 @@ func (s *Service) MemberExists(ctx context.Context, userId user.Id, workspaceEnt
func (s *Service) DeleteWorkspace(ctx context.Context, workspaceId dto.EntityId) error {
// 删除始终是一个复杂的工程,所以先标记为软删除
_, err := s.dao.WithContext(ctx).Workspace.Where(s.dao.Workspace.Id.Eq(workspaceId.Uint())).Delete()
_, err := s.dao.WithContext(ctx).Workspace.Where(s.dao.Workspace.ID.Eq(workspaceId.Uint())).Delete()
return err
}
// IsMember 检查用户是否是工作空间的成员
func (s *Service) IsMember(ctx context.Context, userId user.ID, workspace *entity.Workspace) (bool, error) {
return s.MemberExists(ctx, userId, workspace)
}

View File

@ -0,0 +1,10 @@
package dto
import "time"
// BaseDTO 基础数据传输对象
type BaseDTO struct {
ID EntityId `json:"id"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
}

View File

@ -0,0 +1,22 @@
package dto
// CreateCollectionRequest 创建集合请求
type CreateCollectionRequest struct {
Name string `json:"name" validate:"required|minLen:1"`
WorkspaceID EntityId `json:"workspace_id" validate:"required"`
}
// GetCollectionRequest 获取集合请求
type GetCollectionRequest struct {
ID EntityId `params:"id"`
}
// DeleteCollectionRequest 删除集合请求
type DeleteCollectionRequest struct {
ID EntityId `params:"id"`
}
// ListCollectionsRequest 列出工作空间下的集合请求
type ListCollectionsRequest struct {
WorkspaceID EntityId `params:"workspace_id"`
}

View File

@ -0,0 +1,71 @@
package dto
// CreateDocumentRequest 创建文档请求
type CreateDocumentRequest struct {
Name string `json:"name" validate:"required|minLen:1"`
WorkspaceID EntityId `json:"workspace_id" validate:"required"`
CollectionID EntityId `json:"collection_id" validate:"required"`
ParentID *EntityId `json:"parent_id,omitempty"`
}
// GetDocumentRequest 获取文档请求
type GetDocumentRequest struct {
ID EntityId `params:"id"`
}
// DeleteDocumentRequest 删除文档请求
type DeleteDocumentRequest struct {
ID EntityId `params:"id"`
}
// UpdateDocumentRequest 更新文档请求
type UpdateDocumentRequest struct {
Name string `json:"name" validate:"required|minLen:1"`
}
// ListDocumentsPathParam 列出文档的路径参数
type ListDocumentsPathParam struct {
CollectionID EntityId `params:"collection_id"`
}
// ListDocumentsQueryParam 列出文档的查询参数
type ListDocumentsQueryParam struct {
ParentID *EntityId `query:"parent_id,omitempty"`
}
// CreateBlockRequest 创建文档块请求
type CreateBlockRequest struct {
Type string `json:"type" validate:"required"`
Content string `json:"content" validate:"required"`
AfterBlockID *EntityId `json:"after_block_id,omitempty"`
}
// GetBlockRequest 获取文档块请求
type GetBlockRequest struct {
ID EntityId `params:"block_id"`
}
// UpdateBlockRequest 更新文档块请求
type UpdateBlockRequest struct {
Content string `json:"content" validate:"required"`
}
// MoveBlockRequest 移动文档块请求
type MoveBlockRequest struct {
AfterBlockID *EntityId `json:"after_block_id,omitempty"`
}
// ListBlocksRequest 列出文档下的块请求
type ListBlocksRequest struct {
DocumentID EntityId `params:"document_id"`
}
// DocumentDTO 文档数据传输对象
type DocumentDTO struct {
BaseDTO
Name string `json:"name"`
WorkspaceID EntityId `json:"workspace_id"`
CollectionID EntityId `json:"collection_id"`
ParentID *EntityId `json:"parent_id,omitempty"`
HasChildren bool `json:"has_children"`
}

View File

@ -1,8 +1,9 @@
package dto
import (
"github.com/gofiber/fiber/v2"
"net/http"
"github.com/gofiber/fiber/v2"
)
type IError interface {
@ -23,14 +24,14 @@ type Body struct {
Wrap bool `json:"-"`
}
type HttpResponse struct {
type Response struct {
body *Body
httpStatus int
ctx *fiber.Ctx
}
func Ctx(c *fiber.Ctx) *HttpResponse {
return &HttpResponse{
func Ctx(c *fiber.Ctx) *Response {
return &Response{
body: &Body{
Wrap: true,
},
@ -39,7 +40,7 @@ func Ctx(c *fiber.Ctx) *HttpResponse {
}
}
func (r *HttpResponse) Message(message string) *HttpResponse {
func (r *Response) Message(message string) *Response {
r.body.Message = message
if r.httpStatus == 0 {
@ -50,24 +51,24 @@ func (r *HttpResponse) Message(message string) *HttpResponse {
}
// WithoutWrap 将不在 body 中包裹 data
func (r *HttpResponse) WithoutWrap() *HttpResponse {
func (r *Response) WithoutWrap() *Response {
r.body.Wrap = false
return r
}
func (r *HttpResponse) Wrap() *HttpResponse {
func (r *Response) Wrap() *Response {
r.body.Wrap = true
return r
}
func (r *HttpResponse) Data(data any) *HttpResponse {
func (r *Response) Data(data any) *Response {
r.body.Data = data
return r
}
func (r *HttpResponse) Error(err IError) *HttpResponse {
func (r *Response) Error(err IError) *Response {
if err != nil {
r.body.Error = err.Error()
@ -86,13 +87,13 @@ func (r *HttpResponse) Error(err IError) *HttpResponse {
}
func (r *HttpResponse) Status(status int) *HttpResponse {
func (r *Response) Status(status int) *Response {
r.httpStatus = status
return r
}
func (r *HttpResponse) Send() error {
func (r *Response) Send() error {
if r.httpStatus == 0 {
r.httpStatus = http.StatusOK
}
@ -114,7 +115,18 @@ func (r *HttpResponse) Send() error {
return r.ctx.Status(r.httpStatus).JSON(r.body.Data)
}
//func (r *HttpResponse) ValidationError(validationErrors *[]ValidateError) *HttpResponse {
func (r *Response) Success(data any) *Response {
r.body.Data = data
r.body.Success = true
if r.httpStatus == 0 {
r.httpStatus = http.StatusOK
}
return r
}
//func (r *Response) ValidationError(validationErrors *[]ValidateError) *Response {
// if validationErrors == nil || len(*validationErrors) == 0 {
// }
//

View File

@ -0,0 +1,27 @@
package dto
// WorkspaceIDParam 工作空间 ID 参数
type WorkspaceIDParam struct {
ID EntityId `params:"id"`
}
// WorkspaceMemberParam 工作空间成员参数
type WorkspaceMemberParam struct {
WorkspaceID EntityId `params:"id"`
UserID string `params:"user_id"`
}
// CollectionIDParam 集合 ID 参数
type CollectionIDParam struct {
ID EntityId `params:"id"`
}
// DocumentIDParam 文档 ID 参数
type DocumentIDParam struct {
ID EntityId `params:"id"`
}
// BlockIDParam 文档块 ID 参数
type BlockIDParam struct {
ID EntityId `params:"block_id"`
}

View File

@ -8,6 +8,6 @@ type CurrentUserResponse struct {
IP string `json:"ip"`
Valid bool `json:"valid"`
UserEmail string `json:"userEmail"`
UserId user.Id `json:"userId"`
UserId user.ID `json:"userId"`
UserName string `json:"userName"`
}

View File

@ -0,0 +1,29 @@
package dto
import "leafdev.top/Leaf/leaf-library-3/internal/types/user"
// CreateWorkspaceRequest 创建工作空间请求
type CreateWorkspaceRequest struct {
Name string `json:"name" validate:"required|minLen:1"`
}
// GetWorkspaceRequest 获取工作空间请求
type GetWorkspaceRequest struct {
ID EntityId `params:"id"`
}
// DeleteWorkspaceRequest 删除工作空间请求
type DeleteWorkspaceRequest struct {
ID EntityId `params:"id"`
}
// AddWorkspaceMemberRequest 添加工作空间成员请求
type AddWorkspaceMemberRequest struct {
UserID user.ID `json:"user_id" validate:"required"`
}
// RemoveWorkspaceMemberRequest 移除工作空间成员请求
type RemoveWorkspaceMemberRequest struct {
WorkspaceID EntityId `params:"id"`
UserID string `params:"user_id"`
}

View File

@ -3,6 +3,9 @@ package errs
import "errors"
var (
ErrPageNotFound = errors.New("page not found")
ErrNotFound = errors.New("not found")
ErrPageNotFound = errors.New("page not found")
ErrNotFound = errors.New("not found")
ErrNoPermission = errors.New("no permission")
ErrDocumentNotExists = errors.New("document not exists")
ErrInvalidParentDocument = errors.New("invalid parent document")
)

View File

@ -6,14 +6,14 @@ import (
)
// AnonymousUser 调试模式下的用户
const AnonymousUser Id = "anonymous"
const AnonymousUser ID = "anonymous"
type Token struct {
Aud string `json:"aud"`
Iss string `json:"iss"`
Iat float64 `json:"iat"`
Exp float64 `json:"exp"`
Sub Id `json:"sub" mapstructure:"-"`
Sub ID `json:"sub" mapstructure:"-"`
Scopes []string `json:"scopes"`
Roles []Role `json:"roles,omitempty"`
Permissions []Permission `json:"permissions"`
@ -30,7 +30,7 @@ type Token struct {
type User struct {
Token Token
Id Id
ID ID
Valid bool
}
@ -42,9 +42,9 @@ func (r Role) String() string {
type Permission string
type Id string
type ID string
func (u *User) GetId() Id {
func (u *User) GetId() ID {
return u.Token.Sub
}
@ -108,7 +108,7 @@ func (u *User) HasPermissions(permissions ...Permission) bool {
return true
}
func (u Id) String() string {
func (u ID) String() string {
return string(u)
}