migrate from github
This commit is contained in:
@@ -0,0 +1,3 @@
|
||||
.gitignore
|
||||
config.example.toml
|
||||
pkg/gen/
|
||||
+20
@@ -0,0 +1,20 @@
|
||||
http_test/
|
||||
|
||||
# binaries
|
||||
*.exe
|
||||
*.exe~
|
||||
*.dll
|
||||
*.so
|
||||
*.dylib
|
||||
*.test
|
||||
*.out
|
||||
|
||||
# debug binaries
|
||||
__debug_bin*
|
||||
|
||||
# config
|
||||
config.yaml
|
||||
config.toml
|
||||
|
||||
# generated gRPC code
|
||||
pkg/gen/
|
||||
+18
@@ -0,0 +1,18 @@
|
||||
FROM golang:1.24-alpine AS dev
|
||||
WORKDIR /app
|
||||
|
||||
ENV GOPROXY=https://goproxy.cn,direct
|
||||
|
||||
RUN apk add --no-cache wget && \
|
||||
go install github.com/go-delve/delve/cmd/dlv@v1.24.2 && \
|
||||
wget -O /usr/local/bin/grpc_health_probe https://github.com/grpc-ecosystem/grpc-health-probe/releases/download/v0.4.22/grpc_health_probe-linux-amd64 && \
|
||||
chmod +x /usr/local/bin/grpc_health_probe
|
||||
|
||||
COPY go.mod ./
|
||||
RUN go mod download
|
||||
|
||||
EXPOSE 8080
|
||||
EXPOSE 9090
|
||||
EXPOSE 2345
|
||||
|
||||
CMD ["sh", "-c", "go mod download && go run ./cmd/migrate && go run ./cmd/db_init && go run ./cmd/es_init && dlv debug --headless --listen=:2345 --api-version=2 --accept-multiclient --continue --log ./cmd/main.go"]
|
||||
@@ -0,0 +1,32 @@
|
||||
FROM golang:1.24-alpine AS builder
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
ENV GOPROXY=https://goproxy.cn,direct
|
||||
|
||||
RUN apk add --no-cache protobuf
|
||||
RUN go install google.golang.org/protobuf/cmd/protoc-gen-go@v1.36.5 && \
|
||||
go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@v1.5.1
|
||||
|
||||
COPY backend/go.mod backend/go.sum ./backend/
|
||||
COPY backend ./backend
|
||||
COPY proto ./proto
|
||||
|
||||
WORKDIR /app/backend
|
||||
|
||||
RUN go mod download
|
||||
RUN mkdir -p /app/backend/pkg/gen && \
|
||||
protoc -I /app/proto \
|
||||
--go_out=/app/backend/pkg/gen --go_opt=paths=source_relative \
|
||||
--go-grpc_out=/app/backend/pkg/gen --go-grpc_opt=paths=source_relative \
|
||||
/app/proto/yoresee_doc/v1/yoresee_doc.proto
|
||||
RUN go mod tidy
|
||||
RUN CGO_ENABLED=0 go build -p 1 -o notification_worker.out ./cmd/notification-worker
|
||||
|
||||
FROM alpine:latest AS runner
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
COPY --from=builder /app/backend/notification_worker.out .
|
||||
|
||||
CMD ["./notification_worker.out"]
|
||||
@@ -0,0 +1,41 @@
|
||||
FROM golang:1.24-alpine AS builder
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
ENV GOPROXY=https://goproxy.cn,direct
|
||||
|
||||
RUN apk add --no-cache protobuf
|
||||
RUN go install google.golang.org/protobuf/cmd/protoc-gen-go@v1.36.5 && \
|
||||
go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@v1.5.1
|
||||
|
||||
COPY backend/go.mod backend/go.sum ./backend/
|
||||
COPY backend ./backend
|
||||
COPY proto ./proto
|
||||
|
||||
WORKDIR /app/backend
|
||||
|
||||
RUN go mod download
|
||||
RUN mkdir -p /app/backend/pkg/gen && \
|
||||
protoc -I /app/proto \
|
||||
--go_out=/app/backend/pkg/gen --go_opt=paths=source_relative \
|
||||
--go-grpc_out=/app/backend/pkg/gen --go-grpc_opt=paths=source_relative \
|
||||
/app/proto/yoresee_doc/v1/yoresee_doc.proto
|
||||
RUN go mod tidy
|
||||
RUN CGO_ENABLED=0 go build -p 1 -o main.out cmd/main.go && \
|
||||
CGO_ENABLED=0 go build -p 1 -o migrate.out ./cmd/migrate && \
|
||||
CGO_ENABLED=0 go build -p 1 -o db_init.out ./cmd/db_init && \
|
||||
CGO_ENABLED=0 go build -p 1 -o es_init.out ./cmd/es_init
|
||||
|
||||
FROM alpine:latest AS runner
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
COPY --from=builder /app/backend/main.out .
|
||||
COPY --from=builder /app/backend/migrate.out .
|
||||
COPY --from=builder /app/backend/db_init.out .
|
||||
COPY --from=builder /app/backend/es_init.out .
|
||||
|
||||
EXPOSE 8080
|
||||
EXPOSE 9090
|
||||
|
||||
CMD ["sh", "-c", "./migrate.out && ./db_init.out && ./es_init.out && ./main.out"]
|
||||
@@ -0,0 +1,32 @@
|
||||
FROM golang:1.24-alpine AS builder
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
ENV GOPROXY=https://goproxy.cn,direct
|
||||
|
||||
RUN apk add --no-cache protobuf
|
||||
RUN go install google.golang.org/protobuf/cmd/protoc-gen-go@v1.36.5 && \
|
||||
go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@v1.5.1
|
||||
|
||||
COPY backend/go.mod backend/go.sum ./backend/
|
||||
COPY backend ./backend
|
||||
COPY proto ./proto
|
||||
|
||||
WORKDIR /app/backend
|
||||
|
||||
RUN go mod download
|
||||
RUN mkdir -p /app/backend/pkg/gen && \
|
||||
protoc -I /app/proto \
|
||||
--go_out=/app/backend/pkg/gen --go_opt=paths=source_relative \
|
||||
--go-grpc_out=/app/backend/pkg/gen --go-grpc_opt=paths=source_relative \
|
||||
/app/proto/yoresee_doc/v1/yoresee_doc.proto
|
||||
RUN go mod tidy
|
||||
RUN CGO_ENABLED=0 go build -p 1 -o search_sync_worker.out ./cmd/search-sync-worker
|
||||
|
||||
FROM alpine:latest AS runner
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
COPY --from=builder /app/backend/search_sync_worker.out .
|
||||
|
||||
CMD ["./search_sync_worker.out"]
|
||||
@@ -0,0 +1,32 @@
|
||||
FROM golang:1.24-alpine AS builder
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
ENV GOPROXY=https://goproxy.cn,direct
|
||||
|
||||
RUN apk add --no-cache protobuf
|
||||
RUN go install google.golang.org/protobuf/cmd/protoc-gen-go@v1.36.5 && \
|
||||
go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@v1.5.1
|
||||
|
||||
COPY backend/go.mod backend/go.sum ./backend/
|
||||
COPY backend ./backend
|
||||
COPY proto ./proto
|
||||
|
||||
WORKDIR /app/backend
|
||||
|
||||
RUN go mod download
|
||||
RUN mkdir -p /app/backend/pkg/gen && \
|
||||
protoc -I /app/proto \
|
||||
--go_out=/app/backend/pkg/gen --go_opt=paths=source_relative \
|
||||
--go-grpc_out=/app/backend/pkg/gen --go-grpc_opt=paths=source_relative \
|
||||
/app/proto/yoresee_doc/v1/yoresee_doc.proto
|
||||
RUN go mod tidy
|
||||
RUN CGO_ENABLED=0 go build -p 1 -o snapshot_worker.out ./cmd/snapshot-worker
|
||||
|
||||
FROM alpine:latest AS runner
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
COPY --from=builder /app/backend/snapshot_worker.out .
|
||||
|
||||
CMD ["./snapshot_worker.out"]
|
||||
@@ -0,0 +1,59 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/XingfenD/yoresee_doc/internal/model"
|
||||
"github.com/XingfenD/yoresee_doc/internal/utils"
|
||||
"github.com/sirupsen/logrus"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
func createUserInTx(tx *gorm.DB, username, email, password string) error {
|
||||
logrus.Printf("Creating user %s in transaction...", username)
|
||||
|
||||
hashedPwd, err := utils.HashPassword(password)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
externalID := utils.GenerateExternalID(utils.ExternalIDContextDocument)
|
||||
|
||||
user := model.User{
|
||||
ExternalID: externalID,
|
||||
Username: username,
|
||||
PasswordHash: hashedPwd,
|
||||
Email: email,
|
||||
Nickname: username,
|
||||
Status: 1,
|
||||
}
|
||||
|
||||
var count int64
|
||||
tx.Model(&model.User{}).Where("email = ?", user.Email).Count(&count)
|
||||
if count == 0 {
|
||||
if err := tx.Create(&user).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
logrus.Printf("User %s created successfully in transaction.", username)
|
||||
} else {
|
||||
logrus.Printf("User %s already exists in transaction.", username)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func createUserWithUsername(tx *gorm.DB, username string) error {
|
||||
email := fmt.Sprintf("%s@yoresee.cc", username)
|
||||
password := username
|
||||
return createUserInTx(tx, username, email, password)
|
||||
}
|
||||
|
||||
func createAdminUserInTx(tx *gorm.DB) error {
|
||||
logrus.Println("Creating admin user in transaction...")
|
||||
|
||||
if err := createUserWithUsername(tx, "admin"); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
logrus.Println("Admin permission granted successfully in transaction.")
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/XingfenD/yoresee_doc/internal/constant"
|
||||
"github.com/XingfenD/yoresee_doc/internal/utils"
|
||||
"github.com/XingfenD/yoresee_doc/pkg/storage"
|
||||
)
|
||||
|
||||
func initializeConfigInConsul(ctx context.Context) error {
|
||||
registerModeKey := utils.GenConfigKey(
|
||||
constant.ConfigKey_First_System,
|
||||
constant.ConfigKey_Second_Security,
|
||||
constant.ConfigKey_Third_RegisterMode,
|
||||
)
|
||||
|
||||
if _, ok, err := storage.Consul.Get(ctx, registerModeKey); err != nil {
|
||||
return err
|
||||
} else if !ok {
|
||||
if err := storage.Consul.Set(ctx, registerModeKey, constant.RegisterMode_Invite); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func markDatabaseInitializedInConsul(ctx context.Context) error {
|
||||
initializedKey := utils.GenConfigKey(
|
||||
constant.ConfigKey_First_System,
|
||||
constant.ConfigKey_Second_Database,
|
||||
constant.ConfigKey_Third_Initialized,
|
||||
)
|
||||
return storage.Consul.Set(ctx, initializedKey, constant.Database_Initialized_True)
|
||||
}
|
||||
@@ -0,0 +1,182 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"github.com/XingfenD/yoresee_doc/internal/model"
|
||||
"github.com/XingfenD/yoresee_doc/internal/utils"
|
||||
"github.com/sirupsen/logrus"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
type DocumentInput struct {
|
||||
Title string
|
||||
Content string
|
||||
Summary string
|
||||
ParentID int64
|
||||
Tags []string
|
||||
KnowledgeID *int64
|
||||
}
|
||||
|
||||
func createDocumentWithAllTables(tx *gorm.DB, adminUserID int64, input DocumentInput) (*model.Document, error) {
|
||||
document := model.Document{
|
||||
ExternalID: utils.GenerateExternalID(utils.ExternalIDContextDocument),
|
||||
Title: input.Title,
|
||||
Type: "markdown",
|
||||
Summary: input.Summary,
|
||||
ParentID: input.ParentID,
|
||||
UserID: adminUserID,
|
||||
Content: input.Content,
|
||||
Tags: input.Tags,
|
||||
ViewCount: 0,
|
||||
EditCount: 0,
|
||||
KnowledgeID: input.KnowledgeID,
|
||||
Path: "0",
|
||||
Depth: 0,
|
||||
}
|
||||
|
||||
if err := tx.Create(&document).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := updateDocumentPathDepth(tx, document.ID, document.ParentID); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
documentVersion := model.DocumentVersion{
|
||||
DocumentID: document.ID,
|
||||
Version: 1,
|
||||
Title: document.Title,
|
||||
Content: document.Content,
|
||||
UserID: adminUserID,
|
||||
ChangeSummary: "Initial version",
|
||||
}
|
||||
|
||||
if err := tx.Create(&documentVersion).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &document, nil
|
||||
}
|
||||
|
||||
func initializeDocumentsInTx(tx *gorm.DB) error {
|
||||
logrus.Println("Initializing default documents in transaction...")
|
||||
|
||||
var adminUser model.User
|
||||
if err := tx.Where("username = ?", "admin").First(&adminUser).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var count int64
|
||||
tx.Model(&model.Document{}).Where("title = ?", "欢迎使用 Yoresee Doc").Count(&count)
|
||||
if count > 0 {
|
||||
logrus.Println("Default document already exists in transaction.")
|
||||
return nil
|
||||
}
|
||||
contentString := "# 欢迎使用 Yoresee Doc\n\n这是您的第一个文档。Yoresee Doc 是一个功能强大的文档管理系统,支持以下特性:\n\n- 📝 富文本编辑\n- 📁 文档分类管理\n- 🔍 全文搜索\n- 👥 协作编辑\n- 📊 版本控制\n- 🔒 权限管理\n\n## 快速开始\n\n1. 点击左侧菜单创建新文档\n2. 使用编辑器撰写内容\n3. 保存文档并分享给团队成员\n\n祝您使用愉快!"
|
||||
|
||||
document := model.Document{
|
||||
ExternalID: utils.GenerateExternalID(utils.ExternalIDContextDocument),
|
||||
Title: "欢迎使用 Yoresee Doc",
|
||||
Type: "markdown",
|
||||
Summary: "Yoresee Doc 系统欢迎文档",
|
||||
ParentID: 0,
|
||||
UserID: adminUser.ID,
|
||||
Content: contentString,
|
||||
Tags: []string{"guide", "welcome"},
|
||||
ViewCount: 0,
|
||||
EditCount: 0,
|
||||
Path: "0",
|
||||
Depth: 0,
|
||||
}
|
||||
|
||||
if err := tx.Create(&document).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
if err := updateDocumentPathDepth(tx, document.ID, document.ParentID); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
documentVersion := model.DocumentVersion{
|
||||
DocumentID: document.ID,
|
||||
Version: 1,
|
||||
Title: document.Title,
|
||||
Content: contentString,
|
||||
UserID: adminUser.ID,
|
||||
ChangeSummary: "Initial version",
|
||||
}
|
||||
|
||||
if err := tx.Create(&documentVersion).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
logrus.Println("Adding child documents to welcome document...")
|
||||
|
||||
childDocumentTitles := []string{
|
||||
"功能介绍",
|
||||
"使用教程",
|
||||
"常见问题",
|
||||
"更新日志",
|
||||
"联系我们",
|
||||
}
|
||||
|
||||
for _, title := range childDocumentTitles {
|
||||
childDoc, err := createDocumentWithAllTables(tx, adminUser.ID, DocumentInput{
|
||||
Title: title,
|
||||
Content: "# " + title + "\n\n这是" + title + "的详细文档内容。",
|
||||
Summary: title + "相关文档",
|
||||
ParentID: document.ID,
|
||||
Tags: []string{"welcome", title},
|
||||
KnowledgeID: nil,
|
||||
})
|
||||
if err != nil {
|
||||
logrus.Warnf("Failed to create child document %s: %v", title, err)
|
||||
continue
|
||||
}
|
||||
|
||||
for k := 1; k <= 2; k++ {
|
||||
grandchildTitle := title + " - 详情" + string(rune('0'+k))
|
||||
_, err := createDocumentWithAllTables(tx, adminUser.ID, DocumentInput{
|
||||
Title: grandchildTitle,
|
||||
Content: "# " + grandchildTitle + "\n\n这是" + grandchildTitle + "的详细文档内容。",
|
||||
Summary: grandchildTitle + "相关文档",
|
||||
ParentID: childDoc.ID,
|
||||
Tags: []string{"welcome", title, "详情"},
|
||||
KnowledgeID: nil,
|
||||
})
|
||||
if err != nil {
|
||||
logrus.Warnf("Failed to create grandchild document %s: %v", grandchildTitle, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
logrus.Println("Child documents added to welcome document successfully")
|
||||
|
||||
logrus.Println("Default document created successfully in transaction")
|
||||
return nil
|
||||
}
|
||||
|
||||
func updateDocumentPathDepth(tx *gorm.DB, docID int64, parentID int64) error {
|
||||
if parentID == 0 {
|
||||
return tx.Exec(`
|
||||
UPDATE document_metas
|
||||
SET path = (id::text)::ltree, depth = 0
|
||||
WHERE id = ?
|
||||
`, docID).Error
|
||||
}
|
||||
|
||||
type pathDepth struct {
|
||||
Path string
|
||||
Depth int
|
||||
}
|
||||
var parent pathDepth
|
||||
if err := tx.Model(&model.Document{}).
|
||||
Select("path, depth").
|
||||
Where("id = ?", parentID).
|
||||
Take(&parent).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return tx.Exec(`
|
||||
UPDATE document_metas
|
||||
SET path = (?::ltree) || (id::text)::ltree, depth = ?
|
||||
WHERE id = ?
|
||||
`, parent.Path, parent.Depth+1, docID).Error
|
||||
}
|
||||
@@ -0,0 +1,117 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"github.com/XingfenD/yoresee_doc/internal/model"
|
||||
"github.com/XingfenD/yoresee_doc/internal/utils"
|
||||
"github.com/sirupsen/logrus"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
func initializeKnowledgeBasesInTx(tx *gorm.DB) error {
|
||||
logrus.Println("Initializing default knowledge bases in transaction...")
|
||||
|
||||
var adminUser model.User
|
||||
if err := tx.Where("username = ?", "admin").First(&adminUser).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var count int64
|
||||
tx.Model(&model.KnowledgeBase{}).Where("name = ?", "默认知识库").Count(&count)
|
||||
if count > 0 {
|
||||
logrus.Println("Default knowledge base already exists in transaction.")
|
||||
return nil
|
||||
}
|
||||
|
||||
defaultKnowledgeBase := model.KnowledgeBase{
|
||||
ExternalID: utils.GenerateExternalID(utils.ExternalIDKnowledgeBase),
|
||||
Name: "默认知识库",
|
||||
Description: "系统默认创建的知识库,用于存储常用文档和资料",
|
||||
Cover: "",
|
||||
CreatorUserID: adminUser.ID,
|
||||
IsPublic: false,
|
||||
}
|
||||
|
||||
if err := tx.Create(&defaultKnowledgeBase).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
exampleKnowledgeBase := model.KnowledgeBase{
|
||||
ExternalID: utils.GenerateExternalID(utils.ExternalIDKnowledgeBase),
|
||||
Name: "示例知识库",
|
||||
Description: "示例知识库,展示知识库功能",
|
||||
Cover: "",
|
||||
CreatorUserID: adminUser.ID,
|
||||
IsPublic: true,
|
||||
}
|
||||
|
||||
if err := tx.Create(&exampleKnowledgeBase).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
logrus.Println("Adding documents to default knowledge base...")
|
||||
|
||||
rootDocumentTitles := []string{
|
||||
"项目概述",
|
||||
"系统架构",
|
||||
"开发规范",
|
||||
"API文档",
|
||||
"数据库设计",
|
||||
"前端开发",
|
||||
"后端开发",
|
||||
"测试计划",
|
||||
"部署指南",
|
||||
"安全规范",
|
||||
"用户手册",
|
||||
}
|
||||
|
||||
for _, title := range rootDocumentTitles {
|
||||
rootDoc, err := createDocumentWithAllTables(tx, adminUser.ID, DocumentInput{
|
||||
Title: title,
|
||||
Content: "# " + title + "\n\n这是" + title + "的详细文档内容。",
|
||||
Summary: title + "相关文档",
|
||||
ParentID: 0,
|
||||
Tags: []string{title},
|
||||
KnowledgeID: &defaultKnowledgeBase.ID,
|
||||
})
|
||||
if err != nil {
|
||||
logrus.Warnf("Failed to create root document %s: %v", title, err)
|
||||
continue
|
||||
}
|
||||
|
||||
for j := 1; j <= 3; j++ {
|
||||
childTitle := title + " - 子文档" + string(rune('0'+j))
|
||||
childDoc, err := createDocumentWithAllTables(tx, adminUser.ID, DocumentInput{
|
||||
Title: childTitle,
|
||||
Content: "# " + childTitle + "\n\n这是" + childTitle + "的详细文档内容。",
|
||||
Summary: childTitle + "相关文档",
|
||||
ParentID: rootDoc.ID,
|
||||
Tags: []string{title, "子文档"},
|
||||
KnowledgeID: &defaultKnowledgeBase.ID,
|
||||
})
|
||||
if err != nil {
|
||||
logrus.Warnf("Failed to create child document %s: %v", childTitle, err)
|
||||
continue
|
||||
}
|
||||
|
||||
for k := 1; k <= 2; k++ {
|
||||
grandchildTitle := childTitle + " - 孙文档" + string(rune('0'+k))
|
||||
_, err := createDocumentWithAllTables(tx, adminUser.ID, DocumentInput{
|
||||
Title: grandchildTitle,
|
||||
Content: "# " + grandchildTitle + "\n\n这是" + grandchildTitle + "的详细文档内容。",
|
||||
Summary: grandchildTitle + "相关文档",
|
||||
ParentID: childDoc.ID,
|
||||
Tags: []string{title, "子文档", "孙文档"},
|
||||
KnowledgeID: &defaultKnowledgeBase.ID,
|
||||
})
|
||||
if err != nil {
|
||||
logrus.Warnf("Failed to create grandchild document %s: %v", grandchildTitle, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
logrus.Println("Documents added to default knowledge base successfully")
|
||||
|
||||
logrus.Println("Default knowledge bases created successfully in transaction")
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,91 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/XingfenD/yoresee_doc/internal/bootstrap"
|
||||
"github.com/XingfenD/yoresee_doc/internal/constant"
|
||||
"github.com/XingfenD/yoresee_doc/internal/utils"
|
||||
"github.com/XingfenD/yoresee_doc/pkg/storage"
|
||||
"github.com/sirupsen/logrus"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
func main() {
|
||||
initializer := bootstrap.NewInitializer().
|
||||
InitConfig().
|
||||
InitPostgres().
|
||||
InitConsul().
|
||||
RequireConsulEnabled()
|
||||
if err := initializer.Err(); err != nil {
|
||||
logrus.Fatalf("Init db_init failed: %v", err)
|
||||
}
|
||||
defer initializer.Shutdown()
|
||||
|
||||
if isDatabaseInitialized() {
|
||||
logrus.Println("Database already initialized, skipping initialization steps")
|
||||
} else {
|
||||
if err := initializeDatabaseInTransaction(); err != nil {
|
||||
logrus.Fatalf("Database initialization failed: %v", err)
|
||||
}
|
||||
if err := initializeConfigInConsul(context.Background()); err != nil {
|
||||
logrus.Fatalf("Consul config initialization failed: %v", err)
|
||||
}
|
||||
if err := markDatabaseInitializedInConsul(context.Background()); err != nil {
|
||||
logrus.Fatalf("Mark database initialized failed: %v", err)
|
||||
}
|
||||
logrus.Println("Database initialized successfully")
|
||||
}
|
||||
|
||||
logrus.Println("All initialization tasks completed successfully!")
|
||||
}
|
||||
|
||||
func isDatabaseInitialized() bool {
|
||||
value, ok, err := storage.Consul.Get(context.Background(), utils.GenConfigKey(
|
||||
constant.ConfigKey_First_System,
|
||||
constant.ConfigKey_Second_Database,
|
||||
constant.ConfigKey_Third_Initialized,
|
||||
))
|
||||
if err != nil || !ok {
|
||||
return false
|
||||
}
|
||||
return value == constant.Database_Initialized_True
|
||||
}
|
||||
|
||||
func initializeDatabaseInTransaction() error {
|
||||
return utils.WithTransaction(storage.DB, func(tx *gorm.DB) error {
|
||||
if err := initializePermissionsInTx(tx); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := createAdminUserInTx(tx); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := createTestUserIntx(tx); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := initializeDocumentsInTx(tx); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := initializeKnowledgeBasesInTx(tx); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := initializeTemplatesInTx(tx); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := createNormalUserInTx(tx); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := initializeUserGroupsInTx(tx); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
|
||||
"github.com/XingfenD/yoresee_doc/internal/model"
|
||||
"github.com/XingfenD/yoresee_doc/internal/utils"
|
||||
"github.com/sirupsen/logrus"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
func createTestUserIntx(tx *gorm.DB) error {
|
||||
logrus.Println("Creating test user in transaction...")
|
||||
return createUserWithUsername(tx, "test")
|
||||
}
|
||||
|
||||
func createNormalUserInTx(tx *gorm.DB) error {
|
||||
logrus.Println("Creating 100 users in transaction...")
|
||||
|
||||
for i := 1; i <= 20; i++ {
|
||||
username := "user" + strconv.Itoa(i)
|
||||
if err := createUserWithUsername(tx, username); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var user model.User
|
||||
if err := tx.Where("username = ?", username).First(&user).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
userNum := strconv.Itoa(i)
|
||||
logrus.Printf("User%s created successfully with ID: %s in transaction.\n", userNum, user.ExternalID)
|
||||
|
||||
logrus.Printf("Creating knowledge base for user%s...\n", userNum)
|
||||
|
||||
kbExternalID := utils.GenerateExternalID(utils.ExternalIDKnowledgeBase)
|
||||
knowledgeBase := model.KnowledgeBase{
|
||||
ExternalID: kbExternalID,
|
||||
Name: "User" + userNum + "'s Knowledge Base",
|
||||
Description: "Knowledge base created by user" + userNum,
|
||||
CreatorUserID: user.ID,
|
||||
IsPublic: false,
|
||||
}
|
||||
|
||||
var kbCount int64
|
||||
tx.Model(&model.KnowledgeBase{}).Where("name = ?", knowledgeBase.Name).Count(&kbCount)
|
||||
if kbCount == 0 {
|
||||
if err := tx.Create(&knowledgeBase).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
logrus.Printf("Knowledge base created successfully for user%s: %s\n", userNum, knowledgeBase.Name)
|
||||
} else {
|
||||
logrus.Printf("Knowledge base for user%s already exists.\n", userNum)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
func initializePermissionsInTx(tx *gorm.DB) error {
|
||||
// logrus.Println("Starting permission initialization in transaction...")
|
||||
|
||||
// adminSubject := model.Subject{
|
||||
// ID: "1",
|
||||
// Type: model.SubjectTypeUser,
|
||||
// }
|
||||
|
||||
// if err := tx.Where("id = ?", adminSubject.ID).FirstOrCreate(&adminSubject).Error; err != nil {
|
||||
// return err
|
||||
// }
|
||||
// logrus.Println("Admin subject created successfully in transaction.")
|
||||
|
||||
// logrus.Println("Permission initialization completed successfully in transaction.")
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,141 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"github.com/XingfenD/yoresee_doc/internal/model"
|
||||
"github.com/sirupsen/logrus"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
type templateSeed struct {
|
||||
Name string
|
||||
Description string
|
||||
Content string
|
||||
Scope string
|
||||
KnowledgeBaseID *int64
|
||||
Tags []string
|
||||
}
|
||||
|
||||
func initializeTemplatesInTx(tx *gorm.DB) error {
|
||||
logrus.Println("Initializing default templates in transaction...")
|
||||
|
||||
var adminUser model.User
|
||||
if err := tx.Where("username = ?", "admin").First(&adminUser).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var defaultKB model.KnowledgeBase
|
||||
if err := tx.Where("name = ?", "默认知识库").First(&defaultKB).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
seeds := []templateSeed{
|
||||
{
|
||||
Name: "个人周报模板",
|
||||
Description: "用于个人每周工作复盘与计划",
|
||||
Content: `# 个人周报
|
||||
|
||||
## 本周完成
|
||||
-
|
||||
|
||||
## 问题与风险
|
||||
-
|
||||
|
||||
## 下周计划
|
||||
- `,
|
||||
Scope: "private",
|
||||
Tags: []string{"个人", "周报"},
|
||||
},
|
||||
{
|
||||
Name: "会议纪要模板",
|
||||
Description: "通用会议记录模板",
|
||||
Content: `# 会议纪要
|
||||
|
||||
## 会议信息
|
||||
- 时间:
|
||||
- 参与人:
|
||||
|
||||
## 讨论内容
|
||||
-
|
||||
|
||||
## 行动项
|
||||
- [ ] `,
|
||||
Scope: "private",
|
||||
Tags: []string{"个人", "会议"},
|
||||
},
|
||||
{
|
||||
Name: "需求评审模板",
|
||||
Description: "知识库内需求评审记录模板",
|
||||
KnowledgeBaseID: &defaultKB.ID,
|
||||
Scope: "knowledge_base",
|
||||
Content: `# 需求评审
|
||||
|
||||
## 背景
|
||||
|
||||
## 目标
|
||||
|
||||
## 方案评审
|
||||
- 优点:
|
||||
- 风险:
|
||||
|
||||
## 结论`,
|
||||
Tags: []string{"知识库", "需求"},
|
||||
},
|
||||
{
|
||||
Name: "技术方案模板",
|
||||
Description: "知识库内技术方案沉淀模板",
|
||||
KnowledgeBaseID: &defaultKB.ID,
|
||||
Scope: "knowledge_base",
|
||||
Content: `# 技术方案
|
||||
|
||||
## 问题定义
|
||||
|
||||
## 方案设计
|
||||
|
||||
## 数据模型
|
||||
|
||||
## 发布计划
|
||||
`,
|
||||
Tags: []string{"知识库", "技术"},
|
||||
},
|
||||
}
|
||||
|
||||
for _, seed := range seeds {
|
||||
if err := createTemplateIfNotExists(tx, adminUser.ID, seed); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
logrus.Println("Default templates initialized successfully in transaction")
|
||||
return nil
|
||||
}
|
||||
|
||||
func createTemplateIfNotExists(tx *gorm.DB, userID int64, seed templateSeed) error {
|
||||
query := tx.Model(&model.Template{}).
|
||||
Where("user_id = ? AND name = ? AND scope = ?", userID, seed.Name, seed.Scope)
|
||||
if seed.KnowledgeBaseID == nil {
|
||||
query = query.Where("knowledge_base_id IS NULL")
|
||||
} else {
|
||||
query = query.Where("knowledge_base_id = ?", *seed.KnowledgeBaseID)
|
||||
}
|
||||
|
||||
var count int64
|
||||
if err := query.Count(&count).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
if count > 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
template := model.Template{
|
||||
Name: seed.Name,
|
||||
Description: seed.Description,
|
||||
DocumentType: model.DocumentType_Markdown,
|
||||
Content: seed.Content,
|
||||
UserID: userID,
|
||||
Scope: seed.Scope,
|
||||
KnowledgeBaseID: seed.KnowledgeBaseID,
|
||||
IsPublic: false,
|
||||
Tags: seed.Tags,
|
||||
}
|
||||
return tx.Create(&template).Error
|
||||
}
|
||||
@@ -0,0 +1,127 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"strings"
|
||||
|
||||
"github.com/XingfenD/yoresee_doc/internal/model"
|
||||
"github.com/XingfenD/yoresee_doc/internal/utils"
|
||||
"github.com/sirupsen/logrus"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
type seedUserGroup struct {
|
||||
Name string
|
||||
Description string
|
||||
MemberUsers []string
|
||||
}
|
||||
|
||||
func initializeUserGroupsInTx(tx *gorm.DB) error {
|
||||
logrus.Println("Creating user groups in transaction...")
|
||||
|
||||
adminUser, err := getUserByUsername(tx, "admin")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
user2, err := getUserByUsername(tx, "user2")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
seedGroups := []seedUserGroup{
|
||||
{
|
||||
Name: "Admin Group",
|
||||
Description: "Group for admin only",
|
||||
MemberUsers: []string{"admin"},
|
||||
},
|
||||
{
|
||||
Name: "User2 Group",
|
||||
Description: "Group for user2 only",
|
||||
MemberUsers: []string{"user2"},
|
||||
},
|
||||
{
|
||||
Name: "Admin & User2 Group",
|
||||
Description: "Group for admin and user2",
|
||||
MemberUsers: []string{"admin", "user2"},
|
||||
},
|
||||
{
|
||||
Name: "Empty Group",
|
||||
Description: "Group with no members",
|
||||
MemberUsers: []string{},
|
||||
},
|
||||
}
|
||||
|
||||
for _, seed := range seedGroups {
|
||||
group, err := findOrCreateGroup(tx, seed.Name, seed.Description, adminUser.ID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, username := range seed.MemberUsers {
|
||||
var userID int64
|
||||
switch username {
|
||||
case "admin":
|
||||
userID = adminUser.ID
|
||||
case "user2":
|
||||
userID = user2.ID
|
||||
default:
|
||||
continue
|
||||
}
|
||||
if err := ensureMembership(tx, group.ID, userID); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
logrus.Println("User groups initialized successfully in transaction.")
|
||||
return nil
|
||||
}
|
||||
|
||||
func getUserByUsername(tx *gorm.DB, username string) (*model.User, error) {
|
||||
var user model.User
|
||||
if err := tx.Where("username = ?", username).First(&user).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &user, nil
|
||||
}
|
||||
|
||||
func findOrCreateGroup(tx *gorm.DB, name, description string, creatorID int64) (*model.UserGroupMeta, error) {
|
||||
name = strings.TrimSpace(name)
|
||||
if name == "" {
|
||||
return nil, gorm.ErrInvalidValue
|
||||
}
|
||||
|
||||
var group model.UserGroupMeta
|
||||
if err := tx.Where("name = ?", name).First(&group).Error; err == nil {
|
||||
return &group, nil
|
||||
} else if !errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
group = model.UserGroupMeta{
|
||||
ExternalID: utils.GenerateExternalID(utils.ExternalIDContextUserGroup),
|
||||
Name: name,
|
||||
Description: strings.TrimSpace(description),
|
||||
CreatorID: creatorID,
|
||||
}
|
||||
if err := tx.Create(&group).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &group, nil
|
||||
}
|
||||
|
||||
func ensureMembership(tx *gorm.DB, groupID, userID int64) error {
|
||||
var count int64
|
||||
if err := tx.Model(&model.MembershipRelation{}).
|
||||
Where("type = ? AND membership_id = ? AND user_id = ?", model.MembershipType_UserGroup, groupID, userID).
|
||||
Count(&count).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
if count > 0 {
|
||||
return nil
|
||||
}
|
||||
return tx.Create(&model.MembershipRelation{
|
||||
Type: model.MembershipType_UserGroup,
|
||||
MembershipID: groupID,
|
||||
UserID: userID,
|
||||
}).Error
|
||||
}
|
||||
@@ -0,0 +1,118 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
|
||||
"github.com/XingfenD/yoresee_doc/internal/bootstrap"
|
||||
"github.com/XingfenD/yoresee_doc/internal/config"
|
||||
"github.com/XingfenD/yoresee_doc/internal/constant"
|
||||
"github.com/XingfenD/yoresee_doc/internal/model"
|
||||
"github.com/XingfenD/yoresee_doc/internal/search"
|
||||
"github.com/XingfenD/yoresee_doc/internal/utils"
|
||||
"github.com/XingfenD/yoresee_doc/pkg/storage"
|
||||
"github.com/sirupsen/logrus"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
const reindexBatchSize = 200
|
||||
|
||||
var errESInitDisabled = errors.New("es_init disabled")
|
||||
|
||||
func main() {
|
||||
initializer := bootstrap.NewInitializer().
|
||||
InitConfig().
|
||||
Check("check elasticsearch enabled", func() error {
|
||||
if config.GlobalConfig == nil || !config.GlobalConfig.Elasticsearch.Enabled {
|
||||
return errESInitDisabled
|
||||
}
|
||||
return nil
|
||||
}).
|
||||
InitPostgres().
|
||||
InitConsul().
|
||||
RequireConsulEnabled().
|
||||
InitElasticsearch()
|
||||
if err := initializer.Err(); err != nil {
|
||||
if errors.Is(err, errESInitDisabled) {
|
||||
logrus.Println("Elasticsearch disabled, skip es_init")
|
||||
return
|
||||
}
|
||||
logrus.Fatalf("Init es_init failed: %v", err)
|
||||
}
|
||||
|
||||
defer initializer.Shutdown()
|
||||
|
||||
ctx := context.Background()
|
||||
indexName := search.DocumentIndexName()
|
||||
initialized := isESInitialized(ctx)
|
||||
|
||||
exists, err := storage.ES.IndexExists(ctx, indexName)
|
||||
if err != nil {
|
||||
logrus.Fatalf("Check Elasticsearch index failed, index=%s, err=%v", indexName, err)
|
||||
}
|
||||
|
||||
if initialized && exists {
|
||||
logrus.Printf("Elasticsearch already initialized, index=%s", indexName)
|
||||
return
|
||||
}
|
||||
|
||||
if !exists {
|
||||
if err := storage.ES.CreateIndex(ctx, indexName, search.BuildDocumentIndexMapping()); err != nil {
|
||||
logrus.Fatalf("Create Elasticsearch index failed, index=%s, err=%v", indexName, err)
|
||||
}
|
||||
logrus.Printf("Elasticsearch index created, index=%s", indexName)
|
||||
}
|
||||
|
||||
if err := reindexDocuments(ctx); err != nil {
|
||||
logrus.Fatalf("Reindex documents failed: %v", err)
|
||||
}
|
||||
|
||||
if err := markESInitializedInConsul(ctx); err != nil {
|
||||
logrus.Fatalf("Mark Elasticsearch initialized failed: %v", err)
|
||||
}
|
||||
|
||||
logrus.Println("Elasticsearch initialization completed successfully")
|
||||
}
|
||||
|
||||
func isESInitialized(ctx context.Context) bool {
|
||||
value, ok, err := storage.Consul.Get(ctx, utils.GenConfigKey(
|
||||
constant.ConfigKey_First_System,
|
||||
constant.ConfigKey_Second_ES,
|
||||
constant.ConfigKey_Third_Initialized,
|
||||
))
|
||||
if err != nil || !ok {
|
||||
return false
|
||||
}
|
||||
return value == constant.ES_Initialized_True
|
||||
}
|
||||
|
||||
func markESInitializedInConsul(ctx context.Context) error {
|
||||
return storage.Consul.Set(
|
||||
ctx,
|
||||
utils.GenConfigKey(
|
||||
constant.ConfigKey_First_System,
|
||||
constant.ConfigKey_Second_ES,
|
||||
constant.ConfigKey_Third_Initialized,
|
||||
),
|
||||
constant.ES_Initialized_True,
|
||||
)
|
||||
}
|
||||
|
||||
func reindexDocuments(ctx context.Context) error {
|
||||
logrus.Println("Start reindexing documents to Elasticsearch...")
|
||||
|
||||
var docs []model.Document
|
||||
return storage.DB.
|
||||
Model(&model.Document{}).
|
||||
Where("deleted_at IS NULL").
|
||||
FindInBatches(&docs, reindexBatchSize, func(tx *gorm.DB, batch int) error {
|
||||
for i := range docs {
|
||||
doc := docs[i]
|
||||
if err := search.UpsertDocument(ctx, &doc); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
logrus.Printf("Reindex batch done, batch=%d, size=%d", batch, len(docs))
|
||||
return nil
|
||||
}).Error
|
||||
}
|
||||
+70
@@ -0,0 +1,70 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/XingfenD/yoresee_doc/internal/bootstrap"
|
||||
"github.com/XingfenD/yoresee_doc/internal/config"
|
||||
"github.com/XingfenD/yoresee_doc/internal/transport/connectserver"
|
||||
"github.com/XingfenD/yoresee_doc/internal/utils"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
func main() {
|
||||
initializer := bootstrap.NewInitializer().
|
||||
InitConfig().
|
||||
InitPostgres().
|
||||
InitRedis().
|
||||
InitConsul().
|
||||
RequireConsulEnabled().
|
||||
InitMinio().
|
||||
InitElasticsearchAllowFail().
|
||||
InitMQ().
|
||||
InitRepository().
|
||||
InitService()
|
||||
if err := initializer.Err(); err != nil {
|
||||
logrus.Fatalf("Init backend failed: %v", err)
|
||||
}
|
||||
|
||||
grpcServer, grpcWebServer, err := connectserver.Start(
|
||||
config.GlobalConfig.Server.GrpcPort,
|
||||
config.GlobalConfig.Server.GrpcWebPort,
|
||||
initializer.Repositories.DB,
|
||||
initializer.Repositories.Redis,
|
||||
initializer.Repositories,
|
||||
)
|
||||
if err != nil {
|
||||
logrus.Fatalf("Start connect servers failed: %v", err)
|
||||
}
|
||||
|
||||
waitForShutdown(initializer, grpcServer, grpcWebServer)
|
||||
}
|
||||
|
||||
func waitForShutdown(initializer *bootstrap.Initializer, grpcServer, grpcWebServer *http.Server) {
|
||||
signalCtx, stop := utils.ShutdownContext()
|
||||
defer stop()
|
||||
|
||||
<-signalCtx.Done()
|
||||
logrus.Info("Shutdown signal received, start draining")
|
||||
connectserver.SetDraining(true)
|
||||
|
||||
shutdownServer("grpc-web", grpcWebServer, 10*time.Second)
|
||||
shutdownServer("grpc", grpcServer, 10*time.Second)
|
||||
|
||||
initializer.Shutdown()
|
||||
}
|
||||
|
||||
func shutdownServer(name string, server *http.Server, timeout time.Duration) {
|
||||
if server == nil {
|
||||
return
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), timeout)
|
||||
defer cancel()
|
||||
|
||||
if err := server.Shutdown(ctx); err != nil {
|
||||
logrus.Errorf("Shutdown %s server failed: %v", name, err)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"github.com/sirupsen/logrus"
|
||||
|
||||
"github.com/XingfenD/yoresee_doc/internal/bootstrap"
|
||||
)
|
||||
|
||||
func main() {
|
||||
initializer := bootstrap.NewInitializer().
|
||||
InitConfig().
|
||||
InitPostgres()
|
||||
if err := initializer.Err(); err != nil {
|
||||
logrus.Fatalf("Init migrate failed: %v", err)
|
||||
}
|
||||
defer initializer.Shutdown()
|
||||
|
||||
logrus.Println("Starting migration...")
|
||||
|
||||
if err := runMigration(); err != nil {
|
||||
logrus.Fatalf("Migration failed: %v", err)
|
||||
}
|
||||
logrus.Println("Database migration completed successfully")
|
||||
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"github.com/XingfenD/yoresee_doc/internal/model"
|
||||
"github.com/XingfenD/yoresee_doc/pkg/storage"
|
||||
"github.com/sirupsen/logrus"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
func runMigration() error {
|
||||
if err := storage.DB.Exec("CREATE EXTENSION IF NOT EXISTS ltree").Error; err != nil {
|
||||
return err
|
||||
}
|
||||
logrus.Println("ltree extension created successfully")
|
||||
|
||||
err := storage.DB.AutoMigrate(
|
||||
&model.Attachment{},
|
||||
&model.Document{},
|
||||
&model.DocumentComment{},
|
||||
&model.DocumentYjsSnapshot{},
|
||||
&model.DocumentVersion{},
|
||||
&model.Invitation{},
|
||||
&model.InvitationRecord{},
|
||||
&model.KnowledgeBase{},
|
||||
&model.RecentDocument{},
|
||||
&model.RecentKnowledgeBase{},
|
||||
&model.RecentTemplate{},
|
||||
&model.MembershipRelation{},
|
||||
&model.Notification{},
|
||||
&model.OrgNodeMeta{},
|
||||
&model.Template{},
|
||||
&model.UserGroupMeta{},
|
||||
&model.User{},
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := migrateRecentKnowledgeBaseIndex(storage.DB); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := migrateRecentDocumentIndex(storage.DB); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := migrateRecentTemplateIndex(storage.DB); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := dropDocumentCommentQuote(storage.DB); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func migrateRecentKnowledgeBaseIndex(db *gorm.DB) error {
|
||||
return db.Exec("CREATE UNIQUE INDEX IF NOT EXISTS idx_recent_kb_user_kb ON recent_knowledge_bases (user_id, knowledge_base_id)").Error
|
||||
}
|
||||
|
||||
func migrateRecentTemplateIndex(db *gorm.DB) error {
|
||||
return db.Exec("CREATE UNIQUE INDEX IF NOT EXISTS idx_recent_tpl_user_tpl ON recent_templates (user_id, template_id)").Error
|
||||
}
|
||||
|
||||
func migrateRecentDocumentIndex(db *gorm.DB) error {
|
||||
return db.Exec("CREATE UNIQUE INDEX IF NOT EXISTS idx_recent_doc_user_doc ON recent_documents (user_id, document_id)").Error
|
||||
}
|
||||
|
||||
func dropDocumentCommentQuote(db *gorm.DB) error {
|
||||
return db.Exec("ALTER TABLE document_comments DROP COLUMN IF EXISTS quote").Error
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/XingfenD/yoresee_doc/internal/bootstrap"
|
||||
"github.com/XingfenD/yoresee_doc/internal/domain_event"
|
||||
"github.com/XingfenD/yoresee_doc/internal/dto"
|
||||
"github.com/XingfenD/yoresee_doc/internal/service/mq_service"
|
||||
"github.com/XingfenD/yoresee_doc/internal/service/notification_service"
|
||||
"github.com/XingfenD/yoresee_doc/internal/utils"
|
||||
"github.com/XingfenD/yoresee_doc/pkg/mq"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
func main() {
|
||||
initializer := bootstrap.NewInitializer().
|
||||
InitConfig().
|
||||
InitPostgres().
|
||||
InitRedis().
|
||||
InitMQ().
|
||||
InitRepository().
|
||||
InitService()
|
||||
if err := initializer.Err(); err != nil {
|
||||
logrus.Fatalf("Init notification-worker failed: %v", err)
|
||||
}
|
||||
|
||||
topic := domain_event.NotificationCreateTopic()
|
||||
group := utils.GetEnvVar("NOTIFICATION_MQ_GROUP", "notification-worker")
|
||||
|
||||
go func() {
|
||||
if err := mq_service.MQSvc.Consume(
|
||||
context.Background(),
|
||||
mq.BackendRabbitMQ,
|
||||
mq.ConsumeOptions{
|
||||
Topic: topic,
|
||||
Mode: mq.ConsumeModeGroup,
|
||||
Group: group,
|
||||
AutoAck: false,
|
||||
OnError: mq.ErrorActionRequeue,
|
||||
},
|
||||
func(ctx context.Context, message mq.Message) error {
|
||||
return handleNotificationEvent(ctx, message.Body)
|
||||
},
|
||||
); err != nil {
|
||||
logrus.Fatalf("Subscribe failed: %v", err)
|
||||
}
|
||||
}()
|
||||
|
||||
initializer.ShutdownOnSignal(500 * time.Millisecond)
|
||||
}
|
||||
|
||||
func handleNotificationEvent(ctx context.Context, data []byte) error {
|
||||
payload := strings.TrimSpace(string(data))
|
||||
if payload == "" {
|
||||
return nil
|
||||
}
|
||||
|
||||
evt, err := domain_event.DecodeNotificationCreateEvent(data)
|
||||
if err != nil {
|
||||
logrus.Errorf("parse notification event failed: %v", err)
|
||||
return nil
|
||||
}
|
||||
|
||||
req := &dto.CreateNotificationRequest{
|
||||
ReceiverExternalIDs: evt.ReceiverExternalIDs,
|
||||
Type: evt.Type,
|
||||
Title: evt.Title,
|
||||
Content: evt.Content,
|
||||
PayloadJSON: evt.PayloadJSON,
|
||||
}
|
||||
if err := notification_service.NotificationSvc.CreateNotifications(req); err != nil {
|
||||
logrus.Errorf("create notifications failed: %v", err)
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,100 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"time"
|
||||
|
||||
"github.com/XingfenD/yoresee_doc/internal/bootstrap"
|
||||
"github.com/XingfenD/yoresee_doc/internal/config"
|
||||
"github.com/XingfenD/yoresee_doc/internal/domain_event"
|
||||
"github.com/XingfenD/yoresee_doc/internal/repository/document_repo"
|
||||
"github.com/XingfenD/yoresee_doc/internal/search"
|
||||
"github.com/XingfenD/yoresee_doc/internal/service/mq_service"
|
||||
"github.com/XingfenD/yoresee_doc/internal/utils"
|
||||
"github.com/XingfenD/yoresee_doc/pkg/mq"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
var errSearchSyncWorkerDisabled = errors.New("search-sync-worker disabled")
|
||||
|
||||
func main() {
|
||||
initializer, err := initSearchSyncWorker()
|
||||
if err != nil {
|
||||
if errors.Is(err, errSearchSyncWorkerDisabled) {
|
||||
logrus.Println("Elasticsearch disabled, skip search-sync-worker")
|
||||
return
|
||||
}
|
||||
logrus.Fatalf("Init search-sync-worker failed: %v", err)
|
||||
}
|
||||
|
||||
backend := mq.BackendRabbitMQ
|
||||
topic := domain_event.DocumentSyncTopic()
|
||||
group := utils.GetEnvVar("SEARCH_SYNC_MQ_GROUP", "search-sync-worker")
|
||||
logrus.Infof("Search sync worker started: backend=%s topic=%s group=%s", backend, topic, group)
|
||||
|
||||
go func() {
|
||||
if err := mq_service.MQSvc.Consume(
|
||||
context.Background(),
|
||||
backend,
|
||||
mq.ConsumeOptions{
|
||||
Topic: topic,
|
||||
Mode: mq.ConsumeModeGroup,
|
||||
Group: group,
|
||||
AutoAck: false,
|
||||
OnError: mq.ErrorActionRequeue,
|
||||
},
|
||||
func(ctx context.Context, message mq.Message) error {
|
||||
return handleDocumentEvent(ctx, message.Body, initializer.Repositories.Document)
|
||||
},
|
||||
); err != nil {
|
||||
logrus.Fatalf("Subscribe search sync topic failed: %v", err)
|
||||
}
|
||||
}()
|
||||
|
||||
initializer.ShutdownOnSignal(500 * time.Millisecond)
|
||||
}
|
||||
|
||||
func initSearchSyncWorker() (*bootstrap.Initializer, error) {
|
||||
initializer := bootstrap.NewInitializer().
|
||||
InitConfig().
|
||||
Check("check elasticsearch enabled", func() error {
|
||||
if config.GlobalConfig == nil || !config.GlobalConfig.Elasticsearch.Enabled {
|
||||
return errSearchSyncWorkerDisabled
|
||||
}
|
||||
return nil
|
||||
}).
|
||||
InitPostgres().
|
||||
InitRedis().
|
||||
InitElasticsearch().
|
||||
InitMQ().
|
||||
InitRepository()
|
||||
return initializer, initializer.Err()
|
||||
}
|
||||
|
||||
func handleDocumentEvent(ctx context.Context, data []byte, docRepo *document_repo.DocumentRepository) error {
|
||||
evt, err := domain_event.DecodeDocumentSyncEvent(data)
|
||||
if err != nil {
|
||||
logrus.Warnf("Parse search sync event failed: %v", err)
|
||||
return nil
|
||||
}
|
||||
|
||||
switch evt.Action {
|
||||
case domain_event.DocumentActionUpsert:
|
||||
doc, err := docRepo.GetByExternalID(evt.ExternalID).Exec(ctx)
|
||||
if err != nil {
|
||||
logrus.Warnf("Search sync load document failed, external_id=%s, err=%v", evt.ExternalID, err)
|
||||
return err
|
||||
}
|
||||
if err := search.UpsertDocument(ctx, doc); err != nil {
|
||||
logrus.Warnf("Search sync upsert failed, external_id=%s, err=%v", evt.ExternalID, err)
|
||||
return err
|
||||
}
|
||||
logrus.Infof("Search sync upsert success, external_id=%s", evt.ExternalID)
|
||||
case domain_event.DocumentActionDelete:
|
||||
// reserved for future hard-delete sync.
|
||||
default:
|
||||
logrus.Warnf("Search sync skip unknown action, action=%s, external_id=%s", evt.Action, evt.ExternalID)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/XingfenD/yoresee_doc/internal/bootstrap"
|
||||
"github.com/XingfenD/yoresee_doc/internal/service/mq_service"
|
||||
"github.com/XingfenD/yoresee_doc/internal/utils"
|
||||
"github.com/XingfenD/yoresee_doc/pkg/constant"
|
||||
"github.com/XingfenD/yoresee_doc/pkg/key"
|
||||
"github.com/XingfenD/yoresee_doc/pkg/mq"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
func main() {
|
||||
initializer := bootstrap.NewInitializer().
|
||||
InitConfig().
|
||||
InitPostgres().
|
||||
InitRedis().
|
||||
InitElasticsearchAllowFail().
|
||||
InitMQ().
|
||||
InitRepository().
|
||||
InitService()
|
||||
if err := initializer.Err(); err != nil {
|
||||
logrus.Fatalf("Init snapshot-worker failed: %v", err)
|
||||
}
|
||||
|
||||
w := &Worker{
|
||||
collabCoreHTTP: utils.GetEnvVar("COLLAB_CORE_HTTP", "http://collab-core:1234"),
|
||||
mqGroup: utils.GetEnvVar("SNAPSHOT_MQ_GROUP", "snapshot-worker"),
|
||||
topic: constant.DirtyDocTopicDefault,
|
||||
mqBackend: mq.BackendRabbitMQ,
|
||||
client: &http.Client{Timeout: 10 * time.Second},
|
||||
dirtySetKey: key.KeyCollabDirtyDocSet(),
|
||||
inFlight: &sync.Map{},
|
||||
}
|
||||
|
||||
logrus.Infof("Snapshot worker started: topic=%s group=%s collabCore=%s", w.topic, w.mqGroup, w.collabCoreHTTP)
|
||||
|
||||
go w.runMQConsumer(mq_service.MQSvc)
|
||||
go w.runScanLoop()
|
||||
|
||||
initializer.ShutdownOnSignal(0)
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/bytedance/sonic"
|
||||
)
|
||||
|
||||
type dirtyDocMessage struct {
|
||||
DocID string `json:"doc_id"`
|
||||
DocId string `json:"docId"`
|
||||
}
|
||||
|
||||
func parseDocID(data []byte) string {
|
||||
payload := strings.TrimSpace(string(data))
|
||||
if payload == "" {
|
||||
return ""
|
||||
}
|
||||
var msg dirtyDocMessage
|
||||
if err := sonic.Unmarshal(data, &msg); err == nil {
|
||||
if msg.DocID != "" {
|
||||
return msg.DocID
|
||||
}
|
||||
if msg.DocId != "" {
|
||||
return msg.DocId
|
||||
}
|
||||
}
|
||||
return payload
|
||||
}
|
||||
@@ -0,0 +1,139 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/XingfenD/yoresee_doc/internal/domain_event"
|
||||
"github.com/XingfenD/yoresee_doc/internal/service/document_service"
|
||||
"github.com/XingfenD/yoresee_doc/internal/status"
|
||||
"github.com/XingfenD/yoresee_doc/internal/utils"
|
||||
"github.com/XingfenD/yoresee_doc/pkg/key"
|
||||
"github.com/XingfenD/yoresee_doc/pkg/storage"
|
||||
"github.com/bytedance/sonic"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
func (w *Worker) snapshotDoc(ctx context.Context, docID string, force bool) error {
|
||||
if _, loaded := w.inFlight.LoadOrStore(docID, struct{}{}); loaded {
|
||||
return nil
|
||||
}
|
||||
defer w.inFlight.Delete(docID)
|
||||
|
||||
logrus.Infof("Snapshot start docId=%s force=%v", docID, force)
|
||||
|
||||
docType, err := document_service.DocumentSvc.GetDocumentTypeByExternalID(ctx, docID)
|
||||
if err != nil {
|
||||
if errors.Is(err, status.StatusDocumentNotFound) {
|
||||
logrus.Infof("Snapshot doc not found docId=%s, cleaning up Redis", docID)
|
||||
cleanupCollabKeys(ctx, docID)
|
||||
return nil
|
||||
}
|
||||
logrus.Errorf("Snapshot get doc type failed docId=%s err=%v", docID, err)
|
||||
return err
|
||||
}
|
||||
|
||||
state, content, err := w.fetchSnapshot(ctx, docID, string(docType))
|
||||
if err != nil {
|
||||
logrus.Errorf("Snapshot fetch failed docId=%s err=%v", docID, err)
|
||||
return err
|
||||
}
|
||||
if len(state) == 0 {
|
||||
logrus.Infof("Snapshot empty docId=%s", docID)
|
||||
return nil
|
||||
}
|
||||
logrus.Infof("Snapshot fetched docId=%s bytes=%d", docID, len(state))
|
||||
|
||||
contentChanged, err := document_service.DocumentSvc.SaveDocumentSnapshotAndContent(ctx, docID, state, content)
|
||||
if err != nil {
|
||||
logrus.Errorf("Snapshot save failed docId=%s err=%v", docID, err)
|
||||
return err
|
||||
}
|
||||
if contentChanged {
|
||||
if pubErr := domain_event.PublishDocumentUpsertEvent(ctx, docID); pubErr != nil {
|
||||
logrus.Warnf("Snapshot publish search sync event failed docId=%s err=%v", docID, pubErr)
|
||||
}
|
||||
}
|
||||
|
||||
if err := flushCollabKeys(ctx, docID, state); err != nil {
|
||||
logrus.Errorf("Snapshot redis flush failed docId=%s err=%v", docID, err)
|
||||
return err
|
||||
}
|
||||
|
||||
if force {
|
||||
logrus.Infof("Snapshot saved (mq) for %s", docID)
|
||||
} else {
|
||||
logrus.Infof("Snapshot saved (scan) for %s", docID)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (w *Worker) fetchSnapshot(ctx context.Context, docID, docType string) (state []byte, content string, err error) {
|
||||
url := fmt.Sprintf("%s/internal/yjs/doc-snapshot/%s?type=%s", strings.TrimRight(w.collabCoreHTTP, "/"), docID, docType)
|
||||
resp, err := w.client.Get(url)
|
||||
if err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode == http.StatusNotFound {
|
||||
return nil, "", nil
|
||||
}
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return nil, "", fmt.Errorf("unexpected status %d", resp.StatusCode)
|
||||
}
|
||||
|
||||
body, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
|
||||
var payload struct {
|
||||
State string `json:"state"`
|
||||
Content string `json:"content"`
|
||||
}
|
||||
if err := sonic.Unmarshal(body, &payload); err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
if payload.State == "" {
|
||||
return nil, payload.Content, nil
|
||||
}
|
||||
|
||||
state, err = utils.DecodeBase64(payload.State)
|
||||
if err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
return state, payload.Content, nil
|
||||
}
|
||||
|
||||
func flushCollabKeys(ctx context.Context, docID string, state []byte) error {
|
||||
rdb := storage.GetRedis()
|
||||
if rdb == nil {
|
||||
return nil
|
||||
}
|
||||
updatesKey := key.KeyCollabDocUpdates(docID)
|
||||
pipe := rdb.TxPipeline()
|
||||
pipe.Del(ctx, updatesKey)
|
||||
pipe.RPush(ctx, updatesKey, state)
|
||||
pipe.SRem(ctx, key.KeyCollabDirtyDocSet(), docID)
|
||||
_, err := pipe.Exec(ctx)
|
||||
return err
|
||||
}
|
||||
|
||||
func cleanupCollabKeys(ctx context.Context, docID string) {
|
||||
rdb := storage.GetRedis()
|
||||
if rdb == nil {
|
||||
return
|
||||
}
|
||||
pipe := rdb.TxPipeline()
|
||||
pipe.SRem(ctx, key.KeyCollabDirtyDocSet(), docID)
|
||||
pipe.Del(ctx, key.KeyCollabDocUpdates(docID))
|
||||
pipe.Del(ctx, key.KeyCollabRoom(docID))
|
||||
if _, err := pipe.Exec(ctx); err != nil {
|
||||
logrus.Warnf("Snapshot cleanup Redis failed docId=%s err=%v", docID, err)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,106 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/XingfenD/yoresee_doc/internal/service/mq_service"
|
||||
"github.com/XingfenD/yoresee_doc/internal/utils"
|
||||
"github.com/XingfenD/yoresee_doc/pkg/key"
|
||||
"github.com/XingfenD/yoresee_doc/pkg/mq"
|
||||
"github.com/XingfenD/yoresee_doc/pkg/storage"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
type Worker struct {
|
||||
collabCoreHTTP string
|
||||
mqGroup string
|
||||
topic string
|
||||
mqBackend mq.Backend
|
||||
client *http.Client
|
||||
dirtySetKey string
|
||||
inFlight *sync.Map
|
||||
}
|
||||
|
||||
func (w *Worker) runMQConsumer(mqSvc *mq_service.MQService) {
|
||||
err := mqSvc.Consume(
|
||||
context.Background(),
|
||||
w.mqBackend,
|
||||
mq.ConsumeOptions{
|
||||
Topic: w.topic,
|
||||
Mode: mq.ConsumeModeGroup,
|
||||
Group: w.mqGroup,
|
||||
AutoAck: false,
|
||||
OnError: mq.ErrorActionRequeue,
|
||||
},
|
||||
func(ctx context.Context, message mq.Message) error {
|
||||
docID := parseDocID(message.Body)
|
||||
if docID == "" {
|
||||
return nil
|
||||
}
|
||||
return w.snapshotDoc(ctx, docID, true)
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
logrus.Fatalf("MQ consumer failed: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func (w *Worker) runScanLoop() {
|
||||
ticker := time.NewTicker(2 * time.Second)
|
||||
defer ticker.Stop()
|
||||
for range ticker.C {
|
||||
w.scanAndSnapshot()
|
||||
}
|
||||
}
|
||||
|
||||
func (w *Worker) scanAndSnapshot() {
|
||||
ctx := context.Background()
|
||||
rdb := storage.GetRedis()
|
||||
if rdb == nil {
|
||||
return
|
||||
}
|
||||
|
||||
docIDs, err := rdb.SMembers(ctx, w.dirtySetKey).Result()
|
||||
if err != nil {
|
||||
logrus.Errorf("Scan dirty docs failed: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
candidates := filterCandidates(ctx, docIDs)
|
||||
logrus.Infof("Scan dirty docs: candidates=%d", len(candidates))
|
||||
if len(candidates) > 0 {
|
||||
logrus.Infof("Dirty doc candidates: %v", candidates)
|
||||
}
|
||||
for _, docID := range candidates {
|
||||
_ = w.snapshotDoc(ctx, docID, false)
|
||||
}
|
||||
}
|
||||
|
||||
func filterCandidates(ctx context.Context, docIDs []string) []string {
|
||||
now := time.Now().UnixMilli()
|
||||
candidates := make([]string, 0, len(docIDs))
|
||||
for _, docID := range docIDs {
|
||||
if docID == "" {
|
||||
continue
|
||||
}
|
||||
lastStr, err := storage.GetRedis().Get(ctx, key.KeyCollabRoom(docID)).Result()
|
||||
if err != nil {
|
||||
logrus.Infof("Dirty doc %s skipped: room key missing", docID)
|
||||
continue
|
||||
}
|
||||
last, err := utils.ParseInt64(lastStr)
|
||||
if err != nil {
|
||||
logrus.Infof("Dirty doc %s skipped: invalid room timestamp %s", docID, lastStr)
|
||||
continue
|
||||
}
|
||||
if now-last < 10_000 {
|
||||
logrus.Infof("Dirty doc %s skipped: lastEditAgo=%dms", docID, now-last)
|
||||
continue
|
||||
}
|
||||
candidates = append(candidates, docID)
|
||||
}
|
||||
return candidates
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
[server]
|
||||
grpc_port = ${BACKEND_GRPC_PORT}
|
||||
grpc_web_port = ${BACKEND_GRPC_WEB_PORT}
|
||||
read_timeout = 10
|
||||
write_timeout = 10
|
||||
|
||||
[database]
|
||||
host = "${POSTGRES_HOST}"
|
||||
port = ${POSTGRES_PORT}
|
||||
user = "${POSTGRES_USER}"
|
||||
password = "${POSTGRES_PASSWORD}"
|
||||
name = "${POSTGRES_DB}"
|
||||
# max_idle_conns = 10
|
||||
# max_open_conns = 100
|
||||
# conn_max_lifetime = 3600
|
||||
# charset = "utf8mb4"
|
||||
|
||||
[redis]
|
||||
host = "${REDIS_HOST}"
|
||||
port = ${REDIS_PORT}
|
||||
password = "${REDIS_PASSWORD}"
|
||||
db = ${REDIS_DB}
|
||||
timeout = 5
|
||||
|
||||
[consul]
|
||||
enabled = true
|
||||
address = "${CONSUL_HOST}:${CONSUL_PORT}"
|
||||
scheme = "http"
|
||||
token = "${CONSUL_ROOT_TOKEN}"
|
||||
datacenter = ""
|
||||
prefix = "${CONSUL_PREFIX}"
|
||||
|
||||
[minio]
|
||||
endpoint = "${MINIO_HOST}:${MINIO_API_PORT}"
|
||||
access_key = "${MINIO_ROOT_USER}"
|
||||
secret_key = "${MINIO_ROOT_PASSWORD}"
|
||||
bucket = "${MINIO_BUCKET}"
|
||||
use_ssl = false
|
||||
timeout = 30
|
||||
|
||||
[elasticsearch]
|
||||
enabled = ${ELASTICSEARCH_ENABLED}
|
||||
addresses = ["http://${ELASTICSEARCH_HOST}:${ELASTICSEARCH_PORT}"]
|
||||
username = "${ELASTICSEARCH_USERNAME}"
|
||||
password = "${ELASTICSEARCH_PASSWORD}"
|
||||
index_prefix = "${ELASTICSEARCH_INDEX_PREFIX}"
|
||||
timeout = 5
|
||||
|
||||
[mq_config]
|
||||
|
||||
# [backend.mq_config.redis]
|
||||
|
||||
[mq_config.rabbitmq]
|
||||
url = "amqp://${RABBITMQ_DEFAULT_USER}:${RABBITMQ_DEFAULT_PASS}@${RABBITMQ_HOST}:${RABBITMQ_AMQP_PORT}/"
|
||||
|
||||
[backend]
|
||||
system_name = "${SYSTEM_NAME}"
|
||||
internal_rpc_key = "${BACKEND_INTERNAL_RPC_KEY}"
|
||||
|
||||
[backend.jwt]
|
||||
secret = "${JWT_SECRET}"
|
||||
expire = 86400 # 24 hours
|
||||
refresh_expire = 604800 # 7 days
|
||||
|
||||
[backend.log]
|
||||
level = "debug"
|
||||
# Optional: silent/error/warn/info. When empty, follows backend.log.level.
|
||||
# gorm_log_level = "warn"
|
||||
file = "logs/app.log"
|
||||
console = true
|
||||
file_log = true
|
||||
|
||||
[backend.document]
|
||||
max_size = 10
|
||||
allowed_extensions = [".md", ".txt", ".pdf", ".doc", ".docx"]
|
||||
storage_path = "uploads"
|
||||
|
||||
[backend.security]
|
||||
password_hash_cost = 12
|
||||
max_login_attempts = 5
|
||||
login_lock_duration = 30
|
||||
[backend.security.cors]
|
||||
allowed_origins = ["*"]
|
||||
allowed_methods = ["GET", "POST", "PUT", "DELETE", "OPTIONS"]
|
||||
allowed_headers = ["Origin", "Content-Type", "Authorization"]
|
||||
allow_credentials = true
|
||||
max_age = 86400
|
||||
@@ -0,0 +1,74 @@
|
||||
module github.com/XingfenD/yoresee_doc
|
||||
|
||||
go 1.24.1
|
||||
|
||||
require (
|
||||
connectrpc.com/connect v1.19.1
|
||||
github.com/bwmarrin/snowflake v0.3.0
|
||||
github.com/bytedance/gg v1.1.0
|
||||
github.com/bytedance/sonic v1.14.0
|
||||
github.com/golang-jwt/jwt/v5 v5.3.1
|
||||
github.com/improbable-eng/grpc-web v0.15.0
|
||||
github.com/minio/minio-go/v7 v7.0.98
|
||||
github.com/rabbitmq/amqp091-go v1.10.0
|
||||
github.com/redis/go-redis/v9 v9.18.0
|
||||
github.com/sirupsen/logrus v1.9.4
|
||||
github.com/spf13/viper v1.21.0
|
||||
golang.org/x/crypto v0.46.0
|
||||
golang.org/x/net v0.48.0
|
||||
golang.org/x/sync v0.19.0
|
||||
google.golang.org/grpc v1.79.2
|
||||
google.golang.org/protobuf v1.36.10
|
||||
gorm.io/driver/postgres v1.6.0
|
||||
gorm.io/gorm v1.31.1
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/bytedance/sonic/loader v0.3.0 // indirect
|
||||
github.com/cenkalti/backoff/v4 v4.1.1 // indirect
|
||||
github.com/cespare/xxhash/v2 v2.3.0 // indirect
|
||||
github.com/cloudwego/base64x v0.1.6 // indirect
|
||||
github.com/desertbit/timer v0.0.0-20180107155436-c41aec40b27f // indirect
|
||||
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
|
||||
github.com/dustin/go-humanize v1.0.1 // indirect
|
||||
github.com/fsnotify/fsnotify v1.9.0 // indirect
|
||||
github.com/gin-contrib/sse v1.1.0 // indirect
|
||||
github.com/go-ini/ini v1.67.0 // indirect
|
||||
github.com/go-playground/validator/v10 v10.27.0 // indirect
|
||||
github.com/go-viper/mapstructure/v2 v2.4.0 // indirect
|
||||
github.com/google/uuid v1.6.0 // indirect
|
||||
github.com/jackc/pgpassfile v1.0.0 // indirect
|
||||
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect
|
||||
github.com/jackc/pgx/v5 v5.6.0 // indirect
|
||||
github.com/jackc/puddle/v2 v2.2.2 // indirect
|
||||
github.com/jinzhu/inflection v1.0.0 // indirect
|
||||
github.com/jinzhu/now v1.1.5 // indirect
|
||||
github.com/json-iterator/go v1.1.12 // indirect
|
||||
github.com/klauspost/compress v1.18.2 // indirect
|
||||
github.com/klauspost/cpuid/v2 v2.3.0 // indirect
|
||||
github.com/klauspost/crc32 v1.3.0 // indirect
|
||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||
github.com/minio/crc64nvme v1.1.1 // indirect
|
||||
github.com/minio/md5-simd v1.1.2 // indirect
|
||||
github.com/pelletier/go-toml/v2 v2.2.4 // indirect
|
||||
github.com/philhofer/fwd v1.2.0 // indirect
|
||||
github.com/rogpeppe/go-internal v1.10.0 // indirect
|
||||
github.com/rs/cors v1.7.0 // indirect
|
||||
github.com/rs/xid v1.6.0 // indirect
|
||||
github.com/sagikazarmark/locafero v0.11.0 // indirect
|
||||
github.com/sourcegraph/conc v0.3.1-0.20240121214520-5f936abd7ae8 // indirect
|
||||
github.com/spf13/afero v1.15.0 // indirect
|
||||
github.com/spf13/cast v1.10.0 // indirect
|
||||
github.com/spf13/pflag v1.0.10 // indirect
|
||||
github.com/subosito/gotenv v1.6.0 // indirect
|
||||
github.com/tinylib/msgp v1.6.1 // indirect
|
||||
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
|
||||
github.com/ugorji/go/codec v1.3.0 // indirect
|
||||
go.uber.org/atomic v1.11.0 // indirect
|
||||
go.yaml.in/yaml/v3 v3.0.4 // indirect
|
||||
golang.org/x/arch v0.20.0 // indirect
|
||||
golang.org/x/sys v0.39.0 // indirect
|
||||
golang.org/x/text v0.32.0 // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20251202230838-ff82c1b0f217 // indirect
|
||||
nhooyr.io/websocket v1.8.6 // indirect
|
||||
)
|
||||
@@ -0,0 +1,652 @@
|
||||
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
||||
cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
||||
connectrpc.com/connect v1.19.1 h1:R5M57z05+90EfEvCY1b7hBxDVOUl45PrtXtAV2fOC14=
|
||||
connectrpc.com/connect v1.19.1/go.mod h1:tN20fjdGlewnSFeZxLKb0xwIZ6ozc3OQs2hTXy4du9w=
|
||||
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
|
||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
|
||||
github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible/go.mod h1:r7JcOSlj0wfOMncg0iLm8Leh48TZaKVeNIfJntJ2wa0=
|
||||
github.com/Shopify/sarama v1.19.0/go.mod h1:FVkBWblsNy7DGZRfXLU0O9RCGt5g3g3yEuWXgklEdEo=
|
||||
github.com/Shopify/toxiproxy v2.1.4+incompatible/go.mod h1:OXgGpZ6Cli1/URJOF1DMxUHB2q5Ap20/P/eIdh4G0pI=
|
||||
github.com/VividCortex/gohistogram v1.0.0/go.mod h1:Pf5mBqqDxYaXu3hDrrU+w6nw50o/4+TcAqDqk/vUH7g=
|
||||
github.com/afex/hystrix-go v0.0.0-20180502004556-fa1af6a1f4f5/go.mod h1:SkGFH1ia65gfNATL8TAiHDNxPzPdmEL5uirI2Uyuz6c=
|
||||
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
|
||||
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
|
||||
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
|
||||
github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
|
||||
github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho=
|
||||
github.com/apache/thrift v0.12.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ=
|
||||
github.com/apache/thrift v0.13.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ=
|
||||
github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o=
|
||||
github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY=
|
||||
github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
|
||||
github.com/aryann/difflib v0.0.0-20170710044230-e206f873d14a/go.mod h1:DAHtR1m6lCRdSC2Tm3DSWRPvIPr6xNKyeHdqDQSQT+A=
|
||||
github.com/aws/aws-lambda-go v1.13.3/go.mod h1:4UKl9IzQMoD+QF79YdCuzCwp8VbmG4VAQwij/eHl5CU=
|
||||
github.com/aws/aws-sdk-go v1.27.0/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo=
|
||||
github.com/aws/aws-sdk-go-v2 v0.18.0/go.mod h1:JWVYvqSMppoMJC0x5wdwiImzgXTI9FuZwxzkQq9wy+g=
|
||||
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
|
||||
github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
|
||||
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
|
||||
github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs=
|
||||
github.com/bsm/ginkgo/v2 v2.12.0 h1:Ny8MWAHyOepLGlLKYmXG4IEkioBysk6GpaRTLC8zwWs=
|
||||
github.com/bsm/ginkgo/v2 v2.12.0/go.mod h1:SwYbGRRDovPVboqFv0tPTcG1sN61LM1Z4ARdbAV9g4c=
|
||||
github.com/bsm/gomega v1.27.10 h1:yeMWxP2pV2fG3FgAODIY8EiRE3dy0aeFYt4l7wh6yKA=
|
||||
github.com/bsm/gomega v1.27.10/go.mod h1:JyEr/xRbxbtgWNi8tIEVPUYZ5Dzef52k01W3YH0H+O0=
|
||||
github.com/bwmarrin/snowflake v0.3.0 h1:xm67bEhkKh6ij1790JB83OujPR5CzNe8QuQqAgISZN0=
|
||||
github.com/bwmarrin/snowflake v0.3.0/go.mod h1:NdZxfVWX+oR6y2K0o6qAYv6gIOP9rjG0/E9WsDpxqwE=
|
||||
github.com/bytedance/gg v1.1.0 h1:FSKRxOZeN30w7h6snEbHxzgVMUV7+Xu4gc/Lz1cmBFw=
|
||||
github.com/bytedance/gg v1.1.0/go.mod h1:MeGhXyy5K20hNAU9GkMM51sXdm/lsqdU0CxwIiGvZpo=
|
||||
github.com/bytedance/sonic v1.14.0 h1:/OfKt8HFw0kh2rj8N0F6C/qPGRESq0BbaNZgcNXXzQQ=
|
||||
github.com/bytedance/sonic v1.14.0/go.mod h1:WoEbx8WTcFJfzCe0hbmyTGrfjt8PzNEBdxlNUO24NhA=
|
||||
github.com/bytedance/sonic/loader v0.3.0 h1:dskwH8edlzNMctoruo8FPTJDF3vLtDT0sXZwvZJyqeA=
|
||||
github.com/bytedance/sonic/loader v0.3.0/go.mod h1:N8A3vUdtUebEY2/VQC0MyhYeKUFosQU6FxH2JmUe6VI=
|
||||
github.com/casbin/casbin/v2 v2.1.2/go.mod h1:YcPU1XXisHhLzuxH9coDNf2FbKpjGlbCg3n9yuLkIJQ=
|
||||
github.com/cenkalti/backoff v2.2.1+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QHaoyV4aDUVVkXQJJJ3NXXM=
|
||||
github.com/cenkalti/backoff/v4 v4.1.1 h1:G2HAfAmvm/GcKan2oOQpBXOd2tT2G57ZnZGWa1PxPBQ=
|
||||
github.com/cenkalti/backoff/v4 v4.1.1/go.mod h1:scbssz8iZGpm3xbr14ovlUdkxfGXNInqkPWOWmG2CLw=
|
||||
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
|
||||
github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
|
||||
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||
github.com/clbanning/x2j v0.0.0-20191024224557-825249438eec/go.mod h1:jMjuTZXRI4dUb/I5gc9Hdhagfvm9+RyrPryS/auMzxE=
|
||||
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
|
||||
github.com/cloudwego/base64x v0.1.6 h1:t11wG9AECkCDk5fMSoxmufanudBtJ+/HemLstXDLI2M=
|
||||
github.com/cloudwego/base64x v0.1.6/go.mod h1:OFcloc187FXDaYHvrNIjxSe8ncn0OOM8gEHfghB2IPU=
|
||||
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
|
||||
github.com/cockroachdb/datadriven v0.0.0-20190809214429-80d97fb3cbaa/go.mod h1:zn76sxSg3SzpJ0PPJaLDCu+Bu0Lg3sKTORVIj19EIF8=
|
||||
github.com/codahale/hdrhistogram v0.0.0-20161010025455-3a0bb77429bd/go.mod h1:sE/e/2PUdi/liOCUjSTXgM1o87ZssimdTWN964YiIeI=
|
||||
github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
|
||||
github.com/coreos/go-systemd v0.0.0-20180511133405-39ca1b05acc7/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
|
||||
github.com/coreos/pkg v0.0.0-20160727233714-3ac0863d7acf/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
|
||||
github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/desertbit/timer v0.0.0-20180107155436-c41aec40b27f h1:U5y3Y5UE0w7amNe7Z5G/twsBW0KEalRQXZzf8ufSh9I=
|
||||
github.com/desertbit/timer v0.0.0-20180107155436-c41aec40b27f/go.mod h1:xH/i4TFMt8koVQZ6WFms69WAsDWr2XsYL3Hkl7jkoLE=
|
||||
github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
|
||||
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78=
|
||||
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc=
|
||||
github.com/dustin/go-humanize v0.0.0-20171111073723-bb3d318650d4/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
|
||||
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
|
||||
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
|
||||
github.com/eapache/go-resiliency v1.1.0/go.mod h1:kFI+JgMyC7bLPUVY133qvEBtVayf5mFgVsvEsIPBvNs=
|
||||
github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21/go.mod h1:+020luEh2TKB4/GOp8oxxtq0Daoen/Cii55CzbTV6DU=
|
||||
github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I=
|
||||
github.com/edsrzf/mmap-go v1.0.0/go.mod h1:YO35OhQPt3KJa3ryjFM5Bs14WD66h8eGKpfaBNrHW5M=
|
||||
github.com/envoyproxy/go-control-plane v0.6.9/go.mod h1:SBwIajubJHhxtWwsL9s8ss4safvEdbitLhGGK48rN6g=
|
||||
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
||||
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
||||
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
|
||||
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
|
||||
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
|
||||
github.com/franela/goblin v0.0.0-20200105215937-c9ffbefa60db/go.mod h1:7dvUGVsVBjqR7JHJk0brhHOZYGmfBYOrK0ZhYMEtBr4=
|
||||
github.com/franela/goreq v0.0.0-20171204163338-bcd34c9993f8/go.mod h1:ZhphrRTfi2rbfLwlschooIH4+wKKDR4Pdxhh+TRoA20=
|
||||
github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8=
|
||||
github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=
|
||||
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
|
||||
github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k=
|
||||
github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0=
|
||||
github.com/gabriel-vasile/mimetype v1.4.8 h1:FfZ3gj38NjllZIeJAmMhr+qKL8Wu+nOoI3GqacKw1NM=
|
||||
github.com/gabriel-vasile/mimetype v1.4.8/go.mod h1:ByKUIKGjh1ODkGM1asKUbQZOLGrPjydw3hYPU2YU9t8=
|
||||
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
|
||||
github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
|
||||
github.com/gin-contrib/sse v1.1.0 h1:n0w2GMuUpWDVp7qSpvze6fAu9iRxJY4Hmj6AmBOU05w=
|
||||
github.com/gin-contrib/sse v1.1.0/go.mod h1:hxRZ5gVpWMT7Z0B0gSNYqqsSCNIJMjzvm6fqCz9vjwM=
|
||||
github.com/gin-gonic/gin v1.6.3 h1:ahKqKTFpO5KTPHxWZjEdPScmYaGtLo8Y4DMHoEsnp14=
|
||||
github.com/gin-gonic/gin v1.6.3/go.mod h1:75u5sXoLsGZoRN5Sgbi1eraJ4GU3++wFwWzhwvtwp4M=
|
||||
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
|
||||
github.com/go-ini/ini v1.67.0 h1:z6ZrTEZqSWOTyH2FlglNbNgARyHG8oLW9gMELqKr06A=
|
||||
github.com/go-ini/ini v1.67.0/go.mod h1:ByCAeIL28uOIIG0E3PJtZPDL8WnHpFKFOtgjp+3Ies8=
|
||||
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
|
||||
github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
|
||||
github.com/go-kit/kit v0.10.0/go.mod h1:xUsJbQ/Fp4kEt7AFgCuvyX4a71u8h9jB8tj/ORgOZ7o=
|
||||
github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
|
||||
github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
|
||||
github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A=
|
||||
github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=
|
||||
github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
|
||||
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
|
||||
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
|
||||
github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
|
||||
github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8=
|
||||
github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA=
|
||||
github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=
|
||||
github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA=
|
||||
github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
|
||||
github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
|
||||
github.com/go-playground/validator/v10 v10.2.0/go.mod h1:uOYAAleCW8F/7oMFd6aG0GOhaH6EGOAJShg8Id5JGkI=
|
||||
github.com/go-playground/validator/v10 v10.27.0 h1:w8+XrWVMhGkxOaaowyKH35gFydVHOvC0/uWoy2Fzwn4=
|
||||
github.com/go-playground/validator/v10 v10.27.0/go.mod h1:I5QpIEbmr8On7W0TktmJAumgzX4CA1XNl4ZmDuVHKKo=
|
||||
github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w=
|
||||
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
|
||||
github.com/go-viper/mapstructure/v2 v2.4.0 h1:EBsztssimR/CONLSZZ04E8qAkxNYq4Qp9LvH92wZUgs=
|
||||
github.com/go-viper/mapstructure/v2 v2.4.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM=
|
||||
github.com/gobwas/httphead v0.0.0-20180130184737-2c6c146eadee h1:s+21KNqlpePfkah2I+gwHF8xmJWRjooY+5248k6m4A0=
|
||||
github.com/gobwas/httphead v0.0.0-20180130184737-2c6c146eadee/go.mod h1:L0fX3K22YWvt/FAX9NnzrNzcI4wNYi9Yku4O0LKYflo=
|
||||
github.com/gobwas/pool v0.2.0 h1:QEmUOlnSjWtnpRGHF3SauEiOsy82Cup83Vf2LcMlnc8=
|
||||
github.com/gobwas/pool v0.2.0/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw=
|
||||
github.com/gobwas/ws v1.0.2 h1:CoAavW/wd/kulfZmSIBt6p24n4j7tHgNVCjsfHVNUbo=
|
||||
github.com/gobwas/ws v1.0.2/go.mod h1:szmBTxLgaFppYjEmNtny/v3w89xOydFnnZMcgRRu/EM=
|
||||
github.com/gogo/googleapis v1.1.0/go.mod h1:gf4bu3Q80BeJ6H1S1vYPm8/ELATdvryBaNFGgqEef3s=
|
||||
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
|
||||
github.com/gogo/protobuf v1.2.0/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
|
||||
github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4=
|
||||
github.com/golang-jwt/jwt/v5 v5.3.1 h1:kYf81DTWFe7t+1VvL7eS+jKFVWaUnK9cB1qbwn63YCY=
|
||||
github.com/golang-jwt/jwt/v5 v5.3.1/go.mod h1:fxCRLWMO43lRc8nhHWY6LGqRcf+1gQWArsqaEUEa5bE=
|
||||
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
|
||||
github.com/golang/groupcache v0.0.0-20160516000752-02826c3e7903/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||
github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
||||
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
|
||||
github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk=
|
||||
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
|
||||
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
|
||||
github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
|
||||
github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
|
||||
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
|
||||
github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=
|
||||
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
|
||||
github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
|
||||
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
|
||||
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
|
||||
github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
|
||||
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
|
||||
github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
|
||||
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
|
||||
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
|
||||
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
|
||||
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
|
||||
github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
|
||||
github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg=
|
||||
github.com/gorilla/mux v1.6.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
|
||||
github.com/gorilla/mux v1.7.3/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
|
||||
github.com/gorilla/websocket v0.0.0-20170926233335-4201258b820c/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ=
|
||||
github.com/gorilla/websocket v1.4.1 h1:q7AeDBpnBk8AogcD4DSag/Ukw/KV+YhzLj2bP5HvKCM=
|
||||
github.com/gorilla/websocket v1.4.1/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
||||
github.com/grpc-ecosystem/go-grpc-middleware v1.0.1-0.20190118093823-f849b5445de4/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs=
|
||||
github.com/grpc-ecosystem/go-grpc-middleware v1.2.2/go.mod h1:EaizFBKfUKtMIF5iaDEhniwNedqGo9FuLFzppDr3uwI=
|
||||
github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk=
|
||||
github.com/grpc-ecosystem/grpc-gateway v1.9.5/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY=
|
||||
github.com/hashicorp/consul/api v1.3.0/go.mod h1:MmDNSzIMUjNpY/mQ398R4bk2FnqQLoPndWW5VkKPlCE=
|
||||
github.com/hashicorp/consul/sdk v0.3.0/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8=
|
||||
github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
|
||||
github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80=
|
||||
github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60=
|
||||
github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM=
|
||||
github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk=
|
||||
github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU=
|
||||
github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU=
|
||||
github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4=
|
||||
github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
|
||||
github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
|
||||
github.com/hashicorp/go-version v1.2.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
|
||||
github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90=
|
||||
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
|
||||
github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
|
||||
github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64=
|
||||
github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ=
|
||||
github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I=
|
||||
github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc=
|
||||
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
|
||||
github.com/hudl/fargo v1.3.0/go.mod h1:y3CKSmjA+wD2gak7sUSXTAoopbhU08POFhmITJgmKTg=
|
||||
github.com/improbable-eng/grpc-web v0.15.0 h1:BN+7z6uNXZ1tQGcNAuaU1YjsLTApzkjt2tzCixLaUPQ=
|
||||
github.com/improbable-eng/grpc-web v0.15.0/go.mod h1:1sy9HKV4Jt9aEs9JSnkWlRJPuPtwNr0l57L4f878wP8=
|
||||
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
|
||||
github.com/influxdata/influxdb1-client v0.0.0-20191209144304-8bf82d3c094d/go.mod h1:qj24IKcXYK6Iy9ceXlo3Tc+vtHo9lIhSX5JddghvEPo=
|
||||
github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM=
|
||||
github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg=
|
||||
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 h1:iCEnooe7UlwOQYpKFhBabPMi4aNAfoODPEFNiAnClxo=
|
||||
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM=
|
||||
github.com/jackc/pgx/v5 v5.6.0 h1:SWJzexBzPL5jb0GEsrPMLIsi/3jOo7RHlzTjcAeDrPY=
|
||||
github.com/jackc/pgx/v5 v5.6.0/go.mod h1:DNZ/vlrUnhWCoFGxHAG8U2ljioxukquj7utPDgtQdTw=
|
||||
github.com/jackc/puddle/v2 v2.2.2 h1:PR8nw+E/1w0GLuRFSmiioY6UooMp6KJv0/61nB7icHo=
|
||||
github.com/jackc/puddle/v2 v2.2.2/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4=
|
||||
github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E=
|
||||
github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
|
||||
github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ=
|
||||
github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
|
||||
github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k=
|
||||
github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo=
|
||||
github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4=
|
||||
github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
|
||||
github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
|
||||
github.com/json-iterator/go v1.1.8/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
|
||||
github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
|
||||
github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
|
||||
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
|
||||
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
|
||||
github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
|
||||
github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
|
||||
github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM=
|
||||
github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q=
|
||||
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
||||
github.com/klauspost/compress v1.10.3/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs=
|
||||
github.com/klauspost/compress v1.11.7/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs=
|
||||
github.com/klauspost/compress v1.18.2 h1:iiPHWW0YrcFgpBYhsA6D1+fqHssJscY/Tm/y2Uqnapk=
|
||||
github.com/klauspost/compress v1.18.2/go.mod h1:R0h/fSBs8DE4ENlcrlib3PsXS61voFxhIs2DeRhCvJ4=
|
||||
github.com/klauspost/cpuid/v2 v2.0.1/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
|
||||
github.com/klauspost/cpuid/v2 v2.3.0 h1:S4CRMLnYUhGeDFDqkGriYKdfoFlDnMtqTiI/sFzhA9Y=
|
||||
github.com/klauspost/cpuid/v2 v2.3.0/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0=
|
||||
github.com/klauspost/crc32 v1.3.0 h1:sSmTt3gUt81RP655XGZPElI0PelVTZ6YwCRnPSupoFM=
|
||||
github.com/klauspost/crc32 v1.3.0/go.mod h1:D7kQaZhnkX/Y0tstFGf8VUzv2UofNGqCjnC3zdHB0Hw=
|
||||
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
||||
github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
||||
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
|
||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
||||
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
|
||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||
github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII=
|
||||
github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ=
|
||||
github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI=
|
||||
github.com/lightstep/lightstep-tracer-common/golang/gogo v0.0.0-20190605223551-bc2310a04743/go.mod h1:qklhhLq1aX+mtWk9cPHPzaBjWImj5ULL6C7HFJtXQMM=
|
||||
github.com/lightstep/lightstep-tracer-go v0.18.1/go.mod h1:jlF1pusYV4pidLvZ+XD0UBX0ZE6WURAspgAczcDHrL4=
|
||||
github.com/lyft/protoc-gen-validate v0.0.13/go.mod h1:XbGvPuh87YZc5TdIa2/I4pLk0QoUACkjt2znoq26NVQ=
|
||||
github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
|
||||
github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
|
||||
github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
|
||||
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
|
||||
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
||||
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||
github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
|
||||
github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg=
|
||||
github.com/minio/crc64nvme v1.1.1 h1:8dwx/Pz49suywbO+auHCBpCtlW1OfpcLN7wYgVR6wAI=
|
||||
github.com/minio/crc64nvme v1.1.1/go.mod h1:eVfm2fAzLlxMdUGc0EEBGSMmPwmXD5XiNRpnu9J3bvg=
|
||||
github.com/minio/md5-simd v1.1.2 h1:Gdi1DZK69+ZVMoNHRXJyNcxrMA4dSxoYHZSQbirFg34=
|
||||
github.com/minio/md5-simd v1.1.2/go.mod h1:MzdKDxYpY2BT9XQFocsiZf/NKVtR7nkE4RoEpN+20RM=
|
||||
github.com/minio/minio-go/v7 v7.0.98 h1:MeAVKjLVz+XJ28zFcuYyImNSAh8Mq725uNW4beRisi0=
|
||||
github.com/minio/minio-go/v7 v7.0.98/go.mod h1:cY0Y+W7yozf0mdIclrttzo1Iiu7mEf9y7nk2uXqMOvM=
|
||||
github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc=
|
||||
github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
|
||||
github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI=
|
||||
github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg=
|
||||
github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY=
|
||||
github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
|
||||
github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
|
||||
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
|
||||
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
|
||||
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
|
||||
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
|
||||
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
|
||||
github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f h1:KUppIJq7/+SVif2QVs3tOP0zanoHgBEVAwHxUSIzRqU=
|
||||
github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
|
||||
github.com/mwitkow/grpc-proxy v0.0.0-20181017164139-0f1106ef9c76/go.mod h1:x5OoJHDHqxHS801UIuhqGl6QdSAEJvtausosHSdazIo=
|
||||
github.com/nats-io/jwt v0.3.0/go.mod h1:fRYCDE99xlTsqUzISS1Bi75UBJ6ljOJQOAAu5VglpSg=
|
||||
github.com/nats-io/jwt v0.3.2/go.mod h1:/euKqTS1ZD+zzjYrY7pseZrTtWQSjujC7xjPc8wL6eU=
|
||||
github.com/nats-io/nats-server/v2 v2.1.2/go.mod h1:Afk+wRZqkMQs/p45uXdrVLuab3gwv3Z8C4HTBu8GD/k=
|
||||
github.com/nats-io/nats.go v1.9.1/go.mod h1:ZjDU1L/7fJ09jvUSRVBR2e7+RnLiiIQyqyzEE/Zbp4w=
|
||||
github.com/nats-io/nkeys v0.1.0/go.mod h1:xpnFELMwJABBLVhffcfd1MZx6VsNRFpEugbxziKVo7w=
|
||||
github.com/nats-io/nkeys v0.1.3/go.mod h1:xpnFELMwJABBLVhffcfd1MZx6VsNRFpEugbxziKVo7w=
|
||||
github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c=
|
||||
github.com/oklog/oklog v0.3.2/go.mod h1:FCV+B7mhrz4o+ueLpx+KqkyXRGMWOYEvfiXtdGtbWGs=
|
||||
github.com/oklog/run v1.0.0/go.mod h1:dlhp/R75TPv97u0XWUtDeV/lRKWPKSdTuV0TZvrmrQA=
|
||||
github.com/olekukonko/tablewriter v0.0.0-20170122224234-a0225b3f23b5/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo=
|
||||
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
||||
github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
||||
github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
|
||||
github.com/op/go-logging v0.0.0-20160315200505-970db520ece7/go.mod h1:HzydrMdWErDVzsI23lYNej1Htcns9BCg93Dk0bBINWk=
|
||||
github.com/opentracing-contrib/go-observer v0.0.0-20170622124052-a52f23424492/go.mod h1:Ngi6UdF0k5OKD5t5wlmGhe/EDKPoUM3BXZSSfIuJbis=
|
||||
github.com/opentracing/basictracer-go v1.0.0/go.mod h1:QfBfYuafItcjQuMwinw9GhYKwFXS9KnPs5lxoYwgW74=
|
||||
github.com/opentracing/opentracing-go v1.0.2/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o=
|
||||
github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o=
|
||||
github.com/openzipkin-contrib/zipkin-go-opentracing v0.4.5/go.mod h1:/wsWhb9smxSfWAKL3wpBW7V8scJMt8N8gnaMCS9E/cA=
|
||||
github.com/openzipkin/zipkin-go v0.1.6/go.mod h1:QgAqvLzwWbR/WpD4A3cGpPtJrZXNIiJc5AZX7/PBEpw=
|
||||
github.com/openzipkin/zipkin-go v0.2.1/go.mod h1:NaW6tEwdmWMaCDZzg8sh+IBNOxHMPnhQw8ySjnjRyN4=
|
||||
github.com/openzipkin/zipkin-go v0.2.2/go.mod h1:NaW6tEwdmWMaCDZzg8sh+IBNOxHMPnhQw8ySjnjRyN4=
|
||||
github.com/pact-foundation/pact-go v1.0.4/go.mod h1:uExwJY4kCzNPcHRj+hCR/HBbOOIwwtUjcrb0b5/5kLM=
|
||||
github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
|
||||
github.com/pborman/uuid v1.2.0/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtPdI/k=
|
||||
github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4=
|
||||
github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY=
|
||||
github.com/performancecopilot/speed v3.0.0+incompatible/go.mod h1:/CLtqpZ5gBg1M9iaPbIdPPGyKcA8hKdoy6hAWba7Yac=
|
||||
github.com/philhofer/fwd v1.2.0 h1:e6DnBTl7vGY+Gz322/ASL4Gyp1FspeMvx1RNDoToZuM=
|
||||
github.com/philhofer/fwd v1.2.0/go.mod h1:RqIHx9QI14HlwKwm98g9Re5prTQ6LdeRQn+gXJFxsJM=
|
||||
github.com/pierrec/lz4 v1.0.2-0.20190131084431-473cd7ce01a1/go.mod h1:3/3N9NVKO0jef7pBehbT1qWhCMrIgbYNnFAZCqQ5LRc=
|
||||
github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY=
|
||||
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pkg/profile v1.2.1/go.mod h1:hJw3o1OdXxsrSjjVksARp5W95eeEaEfptyVZyv6JUPA=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI=
|
||||
github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
|
||||
github.com/prometheus/client_golang v0.9.3-0.20190127221311-3c4408c8b829/go.mod h1:p2iRAGwDERtqlqzRXnrOVns+ignqQo//hLXqYxZYVNs=
|
||||
github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo=
|
||||
github.com/prometheus/client_golang v1.3.0/go.mod h1:hJaj2vgQTGQmVCsAACORcieXFeDPbaTKGT+JTgUa3og=
|
||||
github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M=
|
||||
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
|
||||
github.com/prometheus/client_model v0.0.0-20190115171406-56726106282f/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
|
||||
github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||
github.com/prometheus/client_model v0.1.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||
github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||
github.com/prometheus/common v0.2.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
|
||||
github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
|
||||
github.com/prometheus/common v0.7.0/go.mod h1:DjGbpBbp5NYNiECxcL/VnbXCCaQpKd3tt26CguLLsqA=
|
||||
github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo=
|
||||
github.com/prometheus/common v0.15.0/go.mod h1:U+gB1OBLb1lF3O42bTCL+FK18tX9Oar16Clt/msog/s=
|
||||
github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
|
||||
github.com/prometheus/procfs v0.0.0-20190117184657-bf6a532e95b1/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
|
||||
github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
|
||||
github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A=
|
||||
github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU=
|
||||
github.com/prometheus/procfs v0.3.0/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU=
|
||||
github.com/rabbitmq/amqp091-go v1.10.0 h1:STpn5XsHlHGcecLmMFCtg7mqq0RnD+zFr4uzukfVhBw=
|
||||
github.com/rabbitmq/amqp091-go v1.10.0/go.mod h1:Hy4jKW5kQART1u+JkDTF9YYOQUHXqMuhrgxOEeS7G4o=
|
||||
github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4=
|
||||
github.com/redis/go-redis/v9 v9.18.0 h1:pMkxYPkEbMPwRdenAzUNyFNrDgHx9U+DrBabWNfSRQs=
|
||||
github.com/redis/go-redis/v9 v9.18.0/go.mod h1:k3ufPphLU5YXwNTUcCRXGxUoF1fqxnhFQmscfkCoDA0=
|
||||
github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg=
|
||||
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
|
||||
github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ=
|
||||
github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog=
|
||||
github.com/rs/cors v1.7.0 h1:+88SsELBHx5r+hZ8TCkggzSstaWNbDvThkVK8H6f9ik=
|
||||
github.com/rs/cors v1.7.0/go.mod h1:gFx+x8UowdsKA9AchylcLynDq+nNFfI8FkUZdN/jGCU=
|
||||
github.com/rs/xid v1.6.0 h1:fV591PaemRlL6JfRxGDEPl69wICngIQ3shQtzfy2gxU=
|
||||
github.com/rs/xid v1.6.0/go.mod h1:7XoLgs4eV+QndskICGsho+ADou8ySMSjJKDIan90Nz0=
|
||||
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||
github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
|
||||
github.com/sagikazarmark/locafero v0.11.0 h1:1iurJgmM9G3PA/I+wWYIOw/5SyBtxapeHDcg+AAIFXc=
|
||||
github.com/sagikazarmark/locafero v0.11.0/go.mod h1:nVIGvgyzw595SUSUE6tvCp3YYTeHs15MvlmU87WwIik=
|
||||
github.com/samuel/go-zookeeper v0.0.0-20190923202752-2cc03de413da/go.mod h1:gi+0XIa01GRL2eRQVjQkKGqKF3SF9vZR/HnPullcV2E=
|
||||
github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc=
|
||||
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
|
||||
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
|
||||
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
|
||||
github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88=
|
||||
github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
|
||||
github.com/sirupsen/logrus v1.9.4 h1:TsZE7l11zFCLZnZ+teH4Umoq5BhEIfIzfRDZ1Uzql2w=
|
||||
github.com/sirupsen/logrus v1.9.4/go.mod h1:ftWc9WdOfJ0a92nsE2jF5u5ZwH8Bv2zdeOC42RjbV2g=
|
||||
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
|
||||
github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
|
||||
github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM=
|
||||
github.com/sony/gobreaker v0.4.1/go.mod h1:ZKptC7FHNvhBz7dN2LGjPVBz2sZJmc0/PkyDJOjmxWY=
|
||||
github.com/sourcegraph/conc v0.3.1-0.20240121214520-5f936abd7ae8 h1:+jumHNA0Wrelhe64i8F6HNlS8pkoyMv5sreGx2Ry5Rw=
|
||||
github.com/sourcegraph/conc v0.3.1-0.20240121214520-5f936abd7ae8/go.mod h1:3n1Cwaq1E1/1lhQhtRK2ts/ZwZEhjcQeJQ1RuC6Q/8U=
|
||||
github.com/spf13/afero v1.15.0 h1:b/YBCLWAJdFWJTN9cLhiXXcD7mzKn9Dm86dNnfyQw1I=
|
||||
github.com/spf13/afero v1.15.0/go.mod h1:NC2ByUVxtQs4b3sIUphxK0NioZnmxgyCrfzeuq8lxMg=
|
||||
github.com/spf13/cast v1.10.0 h1:h2x0u2shc1QuLHfxi+cTJvs30+ZAHOGRic8uyGTDWxY=
|
||||
github.com/spf13/cast v1.10.0/go.mod h1:jNfB8QC9IA6ZuY2ZjDp0KtFO2LZZlg4S/7bzP6qqeHo=
|
||||
github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ=
|
||||
github.com/spf13/pflag v1.0.1/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
|
||||
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||
github.com/spf13/pflag v1.0.10 h1:4EBh2KAYBwaONj6b2Ye1GiHfwjqyROoF4RwYO+vPwFk=
|
||||
github.com/spf13/pflag v1.0.10/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||
github.com/spf13/viper v1.21.0 h1:x5S+0EU27Lbphp4UKm1C+1oQO+rKx36vfCoaVebLFSU=
|
||||
github.com/spf13/viper v1.21.0/go.mod h1:P0lhsswPGWD/1lZJ9ny3fYnVqxiegrlNrEmgLjbTCAY=
|
||||
github.com/streadway/amqp v0.0.0-20190404075320-75d898a42a94/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1SddzLXVw=
|
||||
github.com/streadway/amqp v0.0.0-20190827072141-edfb9018d271/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1SddzLXVw=
|
||||
github.com/streadway/handy v0.0.0-20190108123426-d5acb3125c2a/go.mod h1:qNTQ5P5JnDBl6z3cMAg/SywNDC5ABu5ApDIw6lUbRmI=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
||||
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
||||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
|
||||
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
|
||||
github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8=
|
||||
github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU=
|
||||
github.com/tinylib/msgp v1.6.1 h1:ESRv8eL3u+DNHUoSAAQRE50Hm162zqAnBoGv9PzScPY=
|
||||
github.com/tinylib/msgp v1.6.1/go.mod h1:RSp0LW9oSxFut3KzESt5Voq4GVWyS+PSulT77roAqEA=
|
||||
github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
|
||||
github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI=
|
||||
github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08=
|
||||
github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw=
|
||||
github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY=
|
||||
github.com/ugorji/go/codec v1.3.0 h1:Qd2W2sQawAfG8XSvzwhBeoGq71zXOC/Q1E9y/wUcsUA=
|
||||
github.com/ugorji/go/codec v1.3.0/go.mod h1:pRBVtBSKl77K30Bv8R2P+cLSGaTtex6fsA2Wjqmfxj4=
|
||||
github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA=
|
||||
github.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0=
|
||||
github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU=
|
||||
github.com/zeebo/xxh3 v1.0.2 h1:xZmwmqxHZA8AI603jOQ0tMqmBr9lPeFwGg6d+xy9DC0=
|
||||
github.com/zeebo/xxh3 v1.0.2/go.mod h1:5NWz9Sef7zIDm2JHfFlcQvNekmcEl9ekUZQQKCYaDcA=
|
||||
go.etcd.io/bbolt v1.3.3/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
|
||||
go.etcd.io/etcd v0.0.0-20191023171146-3cf2f69b5738/go.mod h1:dnLIgRNXwCJa5e+c6mIZCrds/GIG4ncV9HhK5PX7jPg=
|
||||
go.opencensus.io v0.20.1/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk=
|
||||
go.opencensus.io v0.20.2/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk=
|
||||
go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
|
||||
go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64=
|
||||
go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y=
|
||||
go.opentelemetry.io/otel v1.39.0 h1:8yPrr/S0ND9QEfTfdP9V+SiwT4E0G7Y5MO7p85nis48=
|
||||
go.opentelemetry.io/otel v1.39.0/go.mod h1:kLlFTywNWrFyEdH0oj2xK0bFYZtHRYUdv1NklR/tgc8=
|
||||
go.opentelemetry.io/otel/metric v1.39.0 h1:d1UzonvEZriVfpNKEVmHXbdf909uGTOQjA0HF0Ls5Q0=
|
||||
go.opentelemetry.io/otel/metric v1.39.0/go.mod h1:jrZSWL33sD7bBxg1xjrqyDjnuzTUB0x1nBERXd7Ftcs=
|
||||
go.opentelemetry.io/otel/sdk v1.39.0 h1:nMLYcjVsvdui1B/4FRkwjzoRVsMK8uL/cj0OyhKzt18=
|
||||
go.opentelemetry.io/otel/sdk v1.39.0/go.mod h1:vDojkC4/jsTJsE+kh+LXYQlbL8CgrEcwmt1ENZszdJE=
|
||||
go.opentelemetry.io/otel/sdk/metric v1.39.0 h1:cXMVVFVgsIf2YL6QkRF4Urbr/aMInf+2WKg+sEJTtB8=
|
||||
go.opentelemetry.io/otel/sdk/metric v1.39.0/go.mod h1:xq9HEVH7qeX69/JnwEfp6fVq5wosJsY1mt4lLfYdVew=
|
||||
go.opentelemetry.io/otel/trace v1.39.0 h1:2d2vfpEDmCJ5zVYz7ijaJdOF59xLomrvj7bjt6/qCJI=
|
||||
go.opentelemetry.io/otel/trace v1.39.0/go.mod h1:88w4/PnZSazkGzz/w84VHpQafiU4EtqqlVdxWy+rNOA=
|
||||
go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
|
||||
go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
|
||||
go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ=
|
||||
go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE=
|
||||
go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0=
|
||||
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
|
||||
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
|
||||
go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=
|
||||
go.uber.org/multierr v1.3.0/go.mod h1:VgVr7evmIr6uPjLBxg28wmKNXyqE9akIJ5XnfpiKl+4=
|
||||
go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA=
|
||||
go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
|
||||
go.uber.org/zap v1.13.0/go.mod h1:zwrFLgMcdUuIBviXEYEH1YKNaOBnKXsx2IPda5bBwHM=
|
||||
go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc=
|
||||
go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=
|
||||
golang.org/x/arch v0.20.0 h1:dx1zTU0MAE98U+TQ8BLl7XsJbgze2WnNKF/8tGp/Q6c=
|
||||
golang.org/x/arch v0.20.0/go.mod h1:bdwinDaKcfZUGpH09BB7ZmOfhalA8lQdzl62l8gGWsk=
|
||||
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||
golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.46.0 h1:cKRW/pmt1pKAfetfu+RCEvjvZkA9RimPbh7bhFjGVBU=
|
||||
golang.org/x/crypto v0.46.0/go.mod h1:Evb/oLKmMraqjZ2iQTwDwvCtJkczlDuTmdJXoZVzqU0=
|
||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20200331195152-e8c3332aa8e5/go.mod h1:4M0jN8W1tt0AVLNr8HDosyJCDCDuyL9N9+3m7wDWgKw=
|
||||
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
|
||||
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
|
||||
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
|
||||
golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||
golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||
golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o=
|
||||
golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
|
||||
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
|
||||
golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
|
||||
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190125091013-d26f9f9a57f3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
|
||||
golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200421231249-e086a090c8fd/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
||||
golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
|
||||
golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.48.0 h1:zyQRTTrjc33Lhh0fBgT/H3oZq9WuvRR5gPC70xpDiQU=
|
||||
golang.org/x/net v0.48.0/go.mod h1:+ndRgGjkh8FGtu1w1FGbEC31if4VrNVMuKTgcAAnQRY=
|
||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4=
|
||||
golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
|
||||
golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20181122145206-62eef0e2fa9b/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191220142924-d4481acd189f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200420163511-1957bb5e6d1f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.39.0 h1:CvCKL8MeisomCi6qNZ+wbb0DN9E5AATixKsvNtMoMFk=
|
||||
golang.org/x/sys v0.39.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.32.0 h1:ZD01bjUt1FQ9WJ0ClOL5vxgxOI/sVCNgX1YtKwcY0mU=
|
||||
golang.org/x/text v0.32.0/go.mod h1:o/rUWzghvpD5TXrTIBuJU77MTaN0ljMWE47kxGJQ7jY=
|
||||
golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
|
||||
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||
golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||
golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
||||
golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
|
||||
golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20200103221440-774c71fcf114/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk=
|
||||
gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E=
|
||||
google.golang.org/api v0.3.1/go.mod h1:6wY9I6uQWHQ8EM57III9mq/AjF+i8G65rmVagqKMtkk=
|
||||
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
|
||||
google.golang.org/appengine v1.2.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
|
||||
google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
||||
google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
||||
google.golang.org/genproto v0.0.0-20190530194941-fb225487d101/go.mod h1:z3L6/3dTEVtUr6QSP8miRzeRqwQOioJ9I66odjN4I7s=
|
||||
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
|
||||
google.golang.org/genproto v0.0.0-20200423170343-7949de9c1215/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
||||
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
|
||||
google.golang.org/genproto v0.0.0-20210126160654-44e461bb6506/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20251202230838-ff82c1b0f217 h1:gRkg/vSppuSQoDjxyiGfN4Upv/h/DQmIR10ZU8dh4Ww=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20251202230838-ff82c1b0f217/go.mod h1:7i2o+ce6H/6BluujYR+kqX3GKH+dChPTQU19wjRPiGk=
|
||||
google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs=
|
||||
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
|
||||
google.golang.org/grpc v1.20.0/go.mod h1:chYK+tFQF0nDUGJgXMSgLCQk3phJEuONr2DCgLDdAQM=
|
||||
google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
|
||||
google.golang.org/grpc v1.21.0/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
|
||||
google.golang.org/grpc v1.22.1/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
|
||||
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
|
||||
google.golang.org/grpc v1.23.1/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
|
||||
google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
|
||||
google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
|
||||
google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
|
||||
google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk=
|
||||
google.golang.org/grpc v1.32.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
|
||||
google.golang.org/grpc v1.79.2 h1:fRMD94s2tITpyJGtBBn7MkMseNpOZU8ZxgC3MMBaXRU=
|
||||
google.golang.org/grpc v1.79.2/go.mod h1:KmT0Kjez+0dde/v2j9vzwoAScgEPx/Bw1CYChhHLrHQ=
|
||||
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
|
||||
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
|
||||
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
|
||||
google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
|
||||
google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
|
||||
google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
||||
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
||||
google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
||||
google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4=
|
||||
google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
|
||||
google.golang.org/protobuf v1.36.10 h1:AYd7cD/uASjIL6Q9LiTjz8JLcrh/88q5UObnmY3aOOE=
|
||||
google.golang.org/protobuf v1.36.10/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
|
||||
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
||||
gopkg.in/cheggaaa/pb.v1 v1.0.25/go.mod h1:V/YB90LKu/1FcN3WVnfiiE5oMCibMjukxqG/qStrOgw=
|
||||
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
|
||||
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
|
||||
gopkg.in/gcfg.v1 v1.2.3/go.mod h1:yesOnuUOFQAhST5vPY4nbZsb/huCgGGXlipJsBn0b3o=
|
||||
gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo=
|
||||
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
|
||||
gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI=
|
||||
gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74=
|
||||
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU=
|
||||
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gorm.io/driver/postgres v1.6.0 h1:2dxzU8xJ+ivvqTRph34QX+WrRaJlmfyPqXmoGVjMBa4=
|
||||
gorm.io/driver/postgres v1.6.0/go.mod h1:vUw0mrGgrTK+uPHEhAdV4sfFELrByKVGnaVRkXDhtWo=
|
||||
gorm.io/gorm v1.31.1 h1:7CA8FTFz/gRfgqgpeKIBcervUn3xSyPUmr6B2WXJ7kg=
|
||||
gorm.io/gorm v1.31.1/go.mod h1:XyQVbO2k6YkOis7C2437jSit3SsDK72s7n7rsSHd+Gs=
|
||||
honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
|
||||
nhooyr.io/websocket v1.8.6 h1:s+C3xAMLwGmlI31Nyn/eAehUlZPwfYZu2JXM621Q5/k=
|
||||
nhooyr.io/websocket v1.8.6/go.mod h1:B70DZP8IakI65RVQ51MsWP/8jndNma26DVA/nFSCgW0=
|
||||
sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o=
|
||||
sourcegraph.com/sourcegraph/appdash v0.0.0-20190731080439-ebfcffb1b5c0/go.mod h1:hI742Nqp5OhwiqlzhgfbWU4mW4yO10fP+LoT9WOswdU=
|
||||
@@ -0,0 +1,67 @@
|
||||
package auth
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/XingfenD/yoresee_doc/internal/config"
|
||||
"github.com/golang-jwt/jwt/v5"
|
||||
)
|
||||
|
||||
type JWTValidator struct{}
|
||||
|
||||
func (v *JWTValidator) Validate(tokenString string) (*Claims, error) {
|
||||
return ParseToken(tokenString)
|
||||
}
|
||||
|
||||
func (v *JWTValidator) IsExpired(claims *Claims) bool {
|
||||
return time.Now().After(claims.ExpiresAt.Time)
|
||||
}
|
||||
|
||||
type Claims struct {
|
||||
ExternalID string `json:"external_id"`
|
||||
Username string `json:"username"`
|
||||
jwt.RegisteredClaims
|
||||
}
|
||||
|
||||
func GenerateToken(externalID string, username string) (string, error) {
|
||||
cfg := config.GlobalConfig.Backend.Jwt
|
||||
now := time.Now()
|
||||
expireTime := now.Add(time.Duration(cfg.Expire) * time.Second)
|
||||
|
||||
claims := Claims{
|
||||
ExternalID: externalID,
|
||||
Username: username,
|
||||
RegisteredClaims: jwt.RegisteredClaims{
|
||||
ExpiresAt: jwt.NewNumericDate(expireTime),
|
||||
Issuer: "doc_manager",
|
||||
},
|
||||
}
|
||||
|
||||
tokenClaims := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
|
||||
token, err := tokenClaims.SignedString([]byte(cfg.Secret))
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
if err := StoreJWTToken(externalID, token, time.Until(expireTime)); err != nil {
|
||||
return "", fmt.Errorf("store jwt token failed: %w", err)
|
||||
}
|
||||
|
||||
return token, nil
|
||||
}
|
||||
|
||||
func ParseToken(token string) (*Claims, error) {
|
||||
cfg := config.GlobalConfig.Backend.Jwt
|
||||
tokenClaims, err := jwt.ParseWithClaims(token, &Claims{}, func(token *jwt.Token) (interface{}, error) {
|
||||
return []byte(cfg.Secret), nil
|
||||
})
|
||||
|
||||
if tokenClaims != nil {
|
||||
if claims, ok := tokenClaims.Claims.(*Claims); ok && tokenClaims.Valid {
|
||||
return claims, nil
|
||||
}
|
||||
}
|
||||
|
||||
return nil, err
|
||||
}
|
||||
@@ -0,0 +1,102 @@
|
||||
package auth
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/sha256"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/XingfenD/yoresee_doc/internal/status"
|
||||
"github.com/XingfenD/yoresee_doc/pkg/key"
|
||||
"github.com/XingfenD/yoresee_doc/pkg/storage"
|
||||
"github.com/redis/go-redis/v9"
|
||||
)
|
||||
|
||||
func hashToken(token string) string {
|
||||
sum := sha256.Sum256([]byte(token))
|
||||
return hex.EncodeToString(sum[:])
|
||||
}
|
||||
|
||||
func StoreJWTToken(userExternalID, token string, ttl time.Duration) error {
|
||||
if storage.KVS == nil {
|
||||
return status.StatusRedisNotInitialized
|
||||
}
|
||||
if ttl <= 0 {
|
||||
return status.GenErrWithCustomMsg(status.StatusInternalParamsError, "invalid jwt ttl")
|
||||
}
|
||||
|
||||
ctx := context.Background()
|
||||
tokenHash := hashToken(token)
|
||||
pipe := storage.KVS.TxPipeline()
|
||||
pipe.Set(ctx, key.KeyJWTActiveToken(tokenHash), userExternalID, ttl)
|
||||
pipe.SAdd(ctx, key.KeyJWTUserTokenSet(userExternalID), tokenHash)
|
||||
pipe.Expire(ctx, key.KeyJWTUserTokenSet(userExternalID), ttl)
|
||||
if _, err := pipe.Exec(ctx); err != nil {
|
||||
return fmt.Errorf("persist jwt token failed: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func ValidateJWTTokenInRedis(userExternalID, token string) error {
|
||||
if storage.KVS == nil {
|
||||
return status.StatusRedisNotInitialized
|
||||
}
|
||||
|
||||
ctx := context.Background()
|
||||
tokenHash := hashToken(token)
|
||||
blacklisted, err := storage.KVS.Exists(ctx, key.KeyJWTBlacklistToken(tokenHash)).Result()
|
||||
if err != nil {
|
||||
return fmt.Errorf("query jwt blacklist failed: %w", err)
|
||||
}
|
||||
if blacklisted > 0 {
|
||||
return status.GenErrWithCustomMsg(status.StatusTokenInvalid, "token is blacklisted")
|
||||
}
|
||||
|
||||
storedUserExternalID, err := storage.KVS.Get(ctx, key.KeyJWTActiveToken(tokenHash)).Result()
|
||||
if err != nil {
|
||||
if err == redis.Nil {
|
||||
return status.GenErrWithCustomMsg(status.StatusTokenInvalid, "token is invalid")
|
||||
}
|
||||
return fmt.Errorf("query active jwt token failed: %w", err)
|
||||
}
|
||||
if storedUserExternalID != userExternalID {
|
||||
return status.GenErrWithCustomMsg(status.StatusTokenInvalid, "token user mismatch")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func BlacklistUserJWTs(userExternalID string) error {
|
||||
if storage.KVS == nil {
|
||||
return status.StatusRedisNotInitialized
|
||||
}
|
||||
|
||||
ctx := context.Background()
|
||||
tokenHashes, err := storage.KVS.SMembers(ctx, key.KeyJWTUserTokenSet(userExternalID)).Result()
|
||||
if err != nil {
|
||||
return fmt.Errorf("list user jwt tokens failed: %w", err)
|
||||
}
|
||||
if len(tokenHashes) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
pipe := storage.KVS.TxPipeline()
|
||||
for _, tokenHash := range tokenHashes {
|
||||
activeKey := key.KeyJWTActiveToken(tokenHash)
|
||||
ttlCmd := storage.KVS.TTL(ctx, activeKey)
|
||||
ttl, ttlErr := ttlCmd.Result()
|
||||
if ttlErr != nil && ttlErr != redis.Nil {
|
||||
return fmt.Errorf("query token ttl failed: %w", ttlErr)
|
||||
}
|
||||
if ttl <= 0 {
|
||||
ttl = 24 * time.Hour
|
||||
}
|
||||
pipe.Set(ctx, key.KeyJWTBlacklistToken(tokenHash), "1", ttl)
|
||||
pipe.Del(ctx, activeKey)
|
||||
}
|
||||
pipe.Del(ctx, key.KeyJWTUserTokenSet(userExternalID))
|
||||
if _, err := pipe.Exec(ctx); err != nil {
|
||||
return fmt.Errorf("blacklist user jwt tokens failed: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,274 @@
|
||||
package bootstrap
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/XingfenD/yoresee_doc/internal/config"
|
||||
"github.com/XingfenD/yoresee_doc/internal/repository"
|
||||
"github.com/XingfenD/yoresee_doc/internal/service"
|
||||
"github.com/XingfenD/yoresee_doc/internal/service/mq_service"
|
||||
"github.com/XingfenD/yoresee_doc/internal/utils"
|
||||
"github.com/XingfenD/yoresee_doc/pkg/storage"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
type Initializer struct {
|
||||
err error
|
||||
shutdownHooks []utils.ShutdownHook
|
||||
hookNames map[string]struct{}
|
||||
shutdownOnce sync.Once
|
||||
Repositories *repository.Repositories
|
||||
}
|
||||
|
||||
func NewInitializer() *Initializer {
|
||||
return &Initializer{
|
||||
hookNames: make(map[string]struct{}),
|
||||
}
|
||||
}
|
||||
|
||||
func (i *Initializer) Check(step string, fn func() error) *Initializer {
|
||||
if i.err != nil {
|
||||
return i
|
||||
}
|
||||
if err := fn(); err != nil {
|
||||
i.err = fmt.Errorf("%s failed: %w", step, err)
|
||||
}
|
||||
return i
|
||||
}
|
||||
|
||||
func (i *Initializer) CheckAllowFail(step string, fn func() error) *Initializer {
|
||||
if i.err != nil {
|
||||
return i
|
||||
}
|
||||
if err := fn(); err != nil {
|
||||
logrus.Warnf("%s failed, continue with degraded mode: %v", step, err)
|
||||
}
|
||||
return i
|
||||
}
|
||||
|
||||
func (i *Initializer) InitConfig() *Initializer {
|
||||
return i.Check("config.InitConfig", config.InitConfig)
|
||||
}
|
||||
|
||||
func (i *Initializer) InitPostgres() *Initializer {
|
||||
i.Check("storage.OpenPostgres", func() error {
|
||||
dbCfg := config.GlobalConfig.Database
|
||||
logCfg := config.GlobalConfig.Backend.Log
|
||||
gormLogLevel := logCfg.GormLogLevel
|
||||
if gormLogLevel == "" {
|
||||
gormLogLevel = logCfg.Level
|
||||
}
|
||||
|
||||
db, err := storage.OpenPostgres(&storage.PostgresOptions{
|
||||
Host: dbCfg.Host,
|
||||
Port: dbCfg.Port,
|
||||
User: dbCfg.User,
|
||||
Password: dbCfg.Password,
|
||||
Name: dbCfg.Name,
|
||||
MaxIdleConns: dbCfg.MaxIdleConns,
|
||||
MaxOpenConns: dbCfg.MaxOpenConns,
|
||||
GormLogLevel: gormLogLevel,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
storage.DB = db
|
||||
return nil
|
||||
})
|
||||
|
||||
return i.addShutdownHook("Postgres", func() error {
|
||||
if storage.DB == nil {
|
||||
return nil
|
||||
}
|
||||
sqlDB, err := storage.DB.DB()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
storage.DB = nil
|
||||
return sqlDB.Close()
|
||||
})
|
||||
}
|
||||
|
||||
func (i *Initializer) InitRedis() *Initializer {
|
||||
i.Check("storage.NewRedisClient", func() error {
|
||||
cfg := config.GlobalConfig.Redis
|
||||
client, err := storage.NewRedisClient(&storage.RedisOptions{
|
||||
Host: cfg.Host,
|
||||
Port: cfg.Port,
|
||||
Password: cfg.Password,
|
||||
DB: cfg.DB,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
storage.KVS = client
|
||||
return nil
|
||||
})
|
||||
|
||||
return i.addShutdownHook("Redis", func() error {
|
||||
if storage.KVS == nil {
|
||||
return nil
|
||||
}
|
||||
client := storage.KVS
|
||||
storage.KVS = nil
|
||||
return client.Close()
|
||||
})
|
||||
}
|
||||
|
||||
func (i *Initializer) InitConsul() *Initializer {
|
||||
return i.Check("storage.NewConsulClient", func() error {
|
||||
cfg := config.GlobalConfig.Consul
|
||||
storage.Consul = storage.NewConsulClient(&storage.ConsulOptions{
|
||||
Enabled: cfg.Enabled,
|
||||
Address: cfg.Address,
|
||||
Scheme: cfg.Scheme,
|
||||
Token: cfg.Token,
|
||||
Datacenter: cfg.Datacenter,
|
||||
Prefix: cfg.Prefix,
|
||||
})
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
func (i *Initializer) RequireConsulEnabled() *Initializer {
|
||||
return i.Check("storage.ConsulEnabled", func() error {
|
||||
if !storage.ConsulEnabled() {
|
||||
return fmt.Errorf("consul is required but not enabled")
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
func (i *Initializer) InitMinio() *Initializer {
|
||||
return i.Check("storage.NewMinioClient", func() error {
|
||||
cfg := config.GlobalConfig.Minio
|
||||
client, err := storage.NewMinioClient(&storage.MinioOptions{
|
||||
Endpoint: cfg.Endpoint,
|
||||
AccessKey: cfg.AccessKey,
|
||||
SecretKey: cfg.SecretKey,
|
||||
UseSSL: cfg.UseSSL,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err = storage.EnsureBucket(context.Background(), client, cfg.Bucket); err != nil {
|
||||
return err
|
||||
}
|
||||
if err = storage.EnsureBucketPublicRead(context.Background(), client, cfg.Bucket); err != nil {
|
||||
return err
|
||||
}
|
||||
storage.MinioClient = client
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
func (i *Initializer) InitElasticsearch() *Initializer {
|
||||
i.Check("storage.NewElasticsearchClient", func() error {
|
||||
cfg := config.GlobalConfig.Elasticsearch
|
||||
client, err := storage.NewElasticsearchClient(&storage.ElasticsearchOptions{
|
||||
Enabled: cfg.Enabled,
|
||||
Addresses: cfg.Addresses,
|
||||
Username: cfg.Username,
|
||||
Password: cfg.Password,
|
||||
Timeout: cfg.Timeout,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
storage.ES = client
|
||||
return nil
|
||||
})
|
||||
|
||||
return i.addShutdownHook("Elasticsearch", func() error {
|
||||
storage.ES = nil
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
func (i *Initializer) InitElasticsearchAllowFail() *Initializer {
|
||||
i.CheckAllowFail("storage.NewElasticsearchClient", func() error {
|
||||
cfg := config.GlobalConfig.Elasticsearch
|
||||
client, err := storage.NewElasticsearchClient(&storage.ElasticsearchOptions{
|
||||
Enabled: cfg.Enabled,
|
||||
Addresses: cfg.Addresses,
|
||||
Username: cfg.Username,
|
||||
Password: cfg.Password,
|
||||
Timeout: cfg.Timeout,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
storage.ES = client
|
||||
return nil
|
||||
})
|
||||
if storage.ES != nil {
|
||||
i.addShutdownHook("Elasticsearch", func() error {
|
||||
storage.ES = nil
|
||||
return nil
|
||||
})
|
||||
}
|
||||
return i
|
||||
}
|
||||
|
||||
func (i *Initializer) InitMQ() *Initializer {
|
||||
i.Check("mq_service.Init", func() error {
|
||||
return mq_service.MQSvc.Init(&config.GlobalConfig.MQConfig)
|
||||
})
|
||||
return i.addShutdownHook("MQ", mq_service.MQSvc.Close)
|
||||
}
|
||||
|
||||
func (i *Initializer) InitRepository() *Initializer {
|
||||
return i.Check("repository.NewRepositories", func() error {
|
||||
i.Repositories = repository.NewRepositories(storage.DB, storage.KVS)
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
func (i *Initializer) InitService() *Initializer {
|
||||
return i.Check("service.Init", func() error {
|
||||
return service.Init(config.GlobalConfig, i.Repositories)
|
||||
})
|
||||
}
|
||||
|
||||
func (i *Initializer) Err() error {
|
||||
return i.err
|
||||
}
|
||||
|
||||
func (i *Initializer) Shutdown() {
|
||||
i.shutdownOnce.Do(func() {
|
||||
if len(i.shutdownHooks) == 0 {
|
||||
return
|
||||
}
|
||||
reversed := make([]utils.ShutdownHook, 0, len(i.shutdownHooks))
|
||||
for idx := len(i.shutdownHooks) - 1; idx >= 0; idx-- {
|
||||
reversed = append(reversed, i.shutdownHooks[idx])
|
||||
}
|
||||
utils.RunShutdownHooks(reversed...)
|
||||
})
|
||||
}
|
||||
|
||||
func (i *Initializer) ShutdownOnSignal(delay time.Duration) {
|
||||
utils.WaitForShutdownSignal()
|
||||
if delay > 0 {
|
||||
time.Sleep(delay)
|
||||
}
|
||||
i.Shutdown()
|
||||
}
|
||||
|
||||
func (i *Initializer) addShutdownHook(name string, fn func() error) *Initializer {
|
||||
if i.err != nil || fn == nil || name == "" {
|
||||
return i
|
||||
}
|
||||
if _, exists := i.hookNames[name]; exists {
|
||||
return i
|
||||
}
|
||||
i.hookNames[name] = struct{}{}
|
||||
i.shutdownHooks = append(i.shutdownHooks, utils.ShutdownHook{
|
||||
Name: name,
|
||||
Fn: fn,
|
||||
})
|
||||
return i
|
||||
}
|
||||
Vendored
+144
@@ -0,0 +1,144 @@
|
||||
package cache
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"math/rand"
|
||||
"time"
|
||||
|
||||
"github.com/XingfenD/yoresee_doc/internal/status"
|
||||
"github.com/bytedance/sonic"
|
||||
"github.com/redis/go-redis/v9"
|
||||
"golang.org/x/sync/singleflight"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
type Loader struct {
|
||||
redis *redis.Client
|
||||
sf singleflight.Group
|
||||
}
|
||||
|
||||
func (l *Loader) Redis() *redis.Client {
|
||||
return l.redis
|
||||
}
|
||||
|
||||
func NewLoader(redis *redis.Client) *Loader {
|
||||
return &Loader{
|
||||
redis: redis,
|
||||
}
|
||||
}
|
||||
|
||||
type CacheLoadOperation[T any] struct {
|
||||
loader *Loader
|
||||
keyParserMap map[string]Parser[T]
|
||||
defaultKey *string
|
||||
baseTTL time.Duration
|
||||
dbLoader func() (*T, error)
|
||||
valueType bool
|
||||
}
|
||||
|
||||
func NewCacheLoadOperation[T any](loader *Loader) *CacheLoadOperation[T] {
|
||||
return &CacheLoadOperation[T]{
|
||||
loader: loader,
|
||||
baseTTL: time.Hour,
|
||||
valueType: false,
|
||||
keyParserMap: make(map[string]Parser[T]),
|
||||
}
|
||||
}
|
||||
|
||||
func (op *CacheLoadOperation[T]) WithDefaultKeyAndParser(key string, parser Parser[T]) *CacheLoadOperation[T] {
|
||||
var defaultParser Parser[T]
|
||||
op.defaultKey = &key
|
||||
if parser == nil {
|
||||
defaultParser = DefaultParser[T]
|
||||
} else {
|
||||
defaultParser = parser
|
||||
}
|
||||
|
||||
op.keyParserMap[key] = defaultParser
|
||||
return op
|
||||
}
|
||||
|
||||
func (op *CacheLoadOperation[T]) WithKeyAndParser(key string, parser Parser[T]) *CacheLoadOperation[T] {
|
||||
op.keyParserMap[key] = parser
|
||||
return op
|
||||
}
|
||||
|
||||
func (op *CacheLoadOperation[T]) WithTTL(ttl time.Duration) *CacheLoadOperation[T] {
|
||||
op.baseTTL = ttl
|
||||
return op
|
||||
}
|
||||
|
||||
func (op *CacheLoadOperation[T]) WithDBLoader(dbLoader func() (*T, error)) *CacheLoadOperation[T] {
|
||||
op.dbLoader = dbLoader
|
||||
return op
|
||||
}
|
||||
|
||||
func (op *CacheLoadOperation[T]) Exec(ctx context.Context) (*T, error) {
|
||||
if op.defaultKey == nil {
|
||||
return nil, status.GenErrWithCustomMsg(status.StatusInternalParamsError, "default key is needed")
|
||||
}
|
||||
if op.dbLoader == nil {
|
||||
return nil, status.GenErrWithCustomMsg(status.StatusInternalParamsError, "database loader function is required")
|
||||
}
|
||||
|
||||
// try to load from any of the cache keys
|
||||
for key, parser := range op.keyParserMap {
|
||||
val, err := op.loader.redis.Get(ctx, key).Result()
|
||||
if err == nil {
|
||||
if val == "null" {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
if data, err := parser([]byte(val)); err == nil {
|
||||
return data, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// all caches missed, use singleflight to avoid concurrent db queries
|
||||
sfKey := *op.defaultKey
|
||||
v, err, _ := op.loader.sf.Do(sfKey, func() (interface{}, error) {
|
||||
// double check all caches
|
||||
for key, parser := range op.keyParserMap {
|
||||
|
||||
val, err := op.loader.redis.Get(ctx, key).Result()
|
||||
if err == nil {
|
||||
if val == "null" {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
if data, err := parser([]byte(val)); err == nil {
|
||||
return data, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// query database
|
||||
data, err := op.dbLoader()
|
||||
if err != nil {
|
||||
// cache empty value for all keys if record not found
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
op.loader.redis.Set(ctx, *op.defaultKey, "null", 5*time.Minute)
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
|
||||
bytes, _ := sonic.Marshal(data)
|
||||
ttl := op.baseTTL + time.Duration(rand.Intn(600))*time.Second
|
||||
|
||||
op.loader.redis.Set(ctx, *op.defaultKey, bytes, ttl)
|
||||
|
||||
return data, nil
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if v == nil {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
return v.(*T), nil
|
||||
}
|
||||
Vendored
+27
@@ -0,0 +1,27 @@
|
||||
package cache
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
|
||||
"github.com/XingfenD/yoresee_doc/internal/model"
|
||||
)
|
||||
|
||||
type Parser[T any] func(data []byte) (*T, error)
|
||||
|
||||
func DefaultParser[T any](data []byte) (*T, error) {
|
||||
var value T
|
||||
if err := json.Unmarshal(data, &value); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &value, nil
|
||||
}
|
||||
|
||||
var ParseInt64 = DefaultParser[int64]
|
||||
|
||||
func ParseIDFromDocument(data []byte) (*int64, error) {
|
||||
var doc model.Document
|
||||
if err := json.Unmarshal(data, &doc); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &doc.ID, nil
|
||||
}
|
||||
@@ -0,0 +1,151 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/XingfenD/yoresee_doc/internal/status"
|
||||
"github.com/sirupsen/logrus"
|
||||
"github.com/spf13/viper"
|
||||
)
|
||||
|
||||
type Config struct {
|
||||
Server ServerConfig `mapstructure:"server"`
|
||||
Database DatabaseConfig `mapstructure:"database"`
|
||||
Redis RedisConfig `mapstructure:"redis"`
|
||||
Minio MinioConfig `mapstructure:"minio"`
|
||||
Elasticsearch ElasticsearchConfig `mapstructure:"elasticsearch"`
|
||||
MQConfig MQConfig `mapstructure:"mq_config"`
|
||||
Consul ConsulConfig `mapstructure:"consul"`
|
||||
Backend BackendConfig `mapstructure:"backend"`
|
||||
}
|
||||
|
||||
type ServerConfig struct {
|
||||
GrpcPort int `mapstructure:"grpc_port"`
|
||||
GrpcWebPort int `mapstructure:"grpc_web_port"`
|
||||
}
|
||||
|
||||
type DatabaseConfig struct {
|
||||
Host string `mapstructure:"host"`
|
||||
Port int `mapstructure:"port"`
|
||||
User string `mapstructure:"user"`
|
||||
Password string `mapstructure:"password"`
|
||||
Name string `mapstructure:"name"`
|
||||
MaxIdleConns int `mapstructure:"max_idle_conns"`
|
||||
MaxOpenConns int `mapstructure:"max_open_conns"`
|
||||
}
|
||||
|
||||
type RedisConfig struct {
|
||||
Host string `mapstructure:"host"`
|
||||
Port int `mapstructure:"port"`
|
||||
Password string `mapstructure:"password"`
|
||||
DB int `mapstructure:"db"`
|
||||
}
|
||||
|
||||
type ConsulConfig struct {
|
||||
Enabled bool `mapstructure:"enabled"`
|
||||
Address string `mapstructure:"address"`
|
||||
Scheme string `mapstructure:"scheme"`
|
||||
Token string `mapstructure:"token"`
|
||||
Datacenter string `mapstructure:"datacenter"`
|
||||
Prefix string `mapstructure:"prefix"`
|
||||
}
|
||||
|
||||
type MinioConfig struct {
|
||||
Endpoint string `mapstructure:"endpoint"`
|
||||
AccessKey string `mapstructure:"access_key"`
|
||||
SecretKey string `mapstructure:"secret_key"`
|
||||
Bucket string `mapstructure:"bucket"`
|
||||
UseSSL bool `mapstructure:"use_ssl"`
|
||||
}
|
||||
|
||||
type ElasticsearchConfig struct {
|
||||
Enabled bool `mapstructure:"enabled"`
|
||||
Addresses []string `mapstructure:"addresses"`
|
||||
Username string `mapstructure:"username"`
|
||||
Password string `mapstructure:"password"`
|
||||
IndexPrefix string `mapstructure:"index_prefix"`
|
||||
Timeout int `mapstructure:"timeout"`
|
||||
}
|
||||
|
||||
type ClusterRole string
|
||||
|
||||
const (
|
||||
ClusterRoleProducer ClusterRole = "producer"
|
||||
ClusterRoleConsumer ClusterRole = "consumer"
|
||||
ClusterRoleHybrid ClusterRole = "hybrid" // producer and consumer
|
||||
)
|
||||
|
||||
type BackendConfig struct {
|
||||
Jwt JWTConfig `mapstructure:"jwt"`
|
||||
Log LogConfig `mapstructure:"log"`
|
||||
Security SecurityConfig `mapstructure:"security"`
|
||||
SystemName string `mapstructure:"system_name"`
|
||||
InternalRPCKey string `mapstructure:"internal_rpc_key"`
|
||||
}
|
||||
|
||||
type LogConfig struct {
|
||||
Level string `mapstructure:"level"`
|
||||
GormLogLevel string `mapstructure:"gorm_log_level"`
|
||||
}
|
||||
|
||||
type MQConfig struct {
|
||||
Redis RedisQueueConfig `mapstructure:"redis"`
|
||||
RabbitMQ RabbitMQQueueConfig `mapstructure:"rabbitmq"`
|
||||
}
|
||||
|
||||
type RedisQueueConfig struct {
|
||||
}
|
||||
|
||||
type RabbitMQQueueConfig struct {
|
||||
URL string `mapstructure:"url"`
|
||||
}
|
||||
|
||||
type JWTConfig struct {
|
||||
Secret string `mapstructure:"secret"`
|
||||
Expire int64 `mapstructure:"expire"`
|
||||
}
|
||||
|
||||
type SecurityConfig struct {
|
||||
PasswordHashCost int `mapstructure:"password_hash_cost"`
|
||||
CORS CORSConfig `mapstructure:"cors"`
|
||||
}
|
||||
|
||||
type CORSConfig struct {
|
||||
AllowedOrigins []string `mapstructure:"allowed_origins"`
|
||||
AllowedMethods []string `mapstructure:"allowed_methods"`
|
||||
AllowedHeaders []string `mapstructure:"allowed_headers"`
|
||||
AllowCredentials bool `mapstructure:"allow_credentials"`
|
||||
MaxAge int `mapstructure:"max_age"`
|
||||
}
|
||||
|
||||
var GlobalConfig *Config
|
||||
|
||||
func InitConfig() error {
|
||||
viper.SetConfigName("config")
|
||||
viper.SetConfigType("toml")
|
||||
viper.AddConfigPath(".")
|
||||
viper.AddConfigPath("./config")
|
||||
|
||||
if err := viper.ReadInConfig(); err != nil {
|
||||
return status.GenErrWithCustomMsg(status.StatusServiceInternalError, "read config failed")
|
||||
}
|
||||
|
||||
viper.SetEnvKeyReplacer(strings.NewReplacer(".", "_"))
|
||||
viper.AutomaticEnv()
|
||||
|
||||
if err := viper.Unmarshal(&GlobalConfig); err != nil {
|
||||
return status.GenErrWithCustomMsg(status.StatusServiceInternalError, "unmarshal config failed")
|
||||
}
|
||||
|
||||
levelText := strings.TrimSpace(GlobalConfig.Backend.Log.Level)
|
||||
if levelText == "" {
|
||||
levelText = "info"
|
||||
}
|
||||
level, err := logrus.ParseLevel(strings.ToLower(levelText))
|
||||
if err != nil {
|
||||
return status.GenErrWithCustomMsg(status.StatusServiceInternalError, "invalid backend.log.level")
|
||||
}
|
||||
logrus.SetLevel(level)
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
package constant
|
||||
|
||||
const (
|
||||
ConfigKey_First_System string = "system"
|
||||
)
|
||||
|
||||
const (
|
||||
ConfigKey_Second_Security string = "security"
|
||||
ConfigKey_Second_Database string = "database"
|
||||
ConfigKey_Second_ES string = "elasticsearch"
|
||||
)
|
||||
|
||||
const (
|
||||
ConfigKey_Third_RegisterMode string = "register_mode"
|
||||
ConfigKey_Third_Initialized string = "initialized"
|
||||
ConfigKey_Third_RegisterLimit string = "register_limit" // token_bucket_limit
|
||||
)
|
||||
@@ -0,0 +1,23 @@
|
||||
package constant
|
||||
|
||||
// system.security.register_mode
|
||||
const (
|
||||
RegisterMode_Open string = "open"
|
||||
RegisterMode_Invite string = "invite"
|
||||
)
|
||||
|
||||
// system.database.initialized
|
||||
const (
|
||||
Database_Initialized_True string = "true"
|
||||
// Database_Initialized_False string = "false"
|
||||
)
|
||||
|
||||
// system.elasticsearch.initialized
|
||||
const (
|
||||
ES_Initialized_True string = "true"
|
||||
)
|
||||
|
||||
const (
|
||||
RegisterLimit_On string = "on"
|
||||
RegisterLimit_Off string = "off"
|
||||
)
|
||||
@@ -0,0 +1,27 @@
|
||||
package constant
|
||||
|
||||
// Notification type constants.
|
||||
const (
|
||||
NotificationType_Comment = "comment"
|
||||
NotificationType_Reply = "reply"
|
||||
NotificationType_Mention = "mention"
|
||||
NotificationType_System = "system"
|
||||
)
|
||||
|
||||
// notificationTitles holds default (zh-CN) titles keyed by notification type.
|
||||
// Replace with a proper i18n lookup once multi-language support is added.
|
||||
var notificationTitles = map[string]string{
|
||||
NotificationType_Comment: "收到新评论",
|
||||
NotificationType_Reply: "回复了你的评论",
|
||||
NotificationType_Mention: "在评论中提及了你",
|
||||
NotificationType_System: "系统通知",
|
||||
}
|
||||
|
||||
// GetNotificationTitle returns the default title for the given notification type.
|
||||
// Falls back to the type string itself if the type is unrecognised.
|
||||
func GetNotificationTitle(notifType string) string {
|
||||
if title, ok := notificationTitles[notifType]; ok {
|
||||
return title
|
||||
}
|
||||
return notifType
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
package domain_event
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/XingfenD/yoresee_doc/pkg/constant"
|
||||
"github.com/bytedance/sonic"
|
||||
)
|
||||
|
||||
const (
|
||||
DocumentActionUpsert = "upsert"
|
||||
DocumentActionDelete = "delete"
|
||||
)
|
||||
|
||||
type DocumentSyncEvent struct {
|
||||
Action string `json:"action"`
|
||||
ExternalID string `json:"external_id"`
|
||||
}
|
||||
|
||||
func DocumentSyncTopic() string {
|
||||
return constant.SearchSyncTopicDefault
|
||||
}
|
||||
|
||||
func PublishDocumentUpsertEvent(ctx context.Context, externalID string) error {
|
||||
externalID = strings.TrimSpace(externalID)
|
||||
if externalID == "" {
|
||||
return fmt.Errorf("external_id is empty")
|
||||
}
|
||||
return publishJSONToRabbitMQ(ctx, DocumentSyncTopic(), DocumentSyncEvent{
|
||||
Action: DocumentActionUpsert,
|
||||
ExternalID: externalID,
|
||||
})
|
||||
}
|
||||
|
||||
func DecodeDocumentSyncEvent(data []byte) (*DocumentSyncEvent, error) {
|
||||
var evt DocumentSyncEvent
|
||||
if err := sonic.Unmarshal(data, &evt); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
evt.Action = strings.TrimSpace(evt.Action)
|
||||
evt.ExternalID = strings.TrimSpace(evt.ExternalID)
|
||||
if evt.Action == "" {
|
||||
evt.Action = DocumentActionUpsert
|
||||
}
|
||||
if evt.ExternalID == "" {
|
||||
return nil, fmt.Errorf("external_id is empty")
|
||||
}
|
||||
return &evt, nil
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
package domain_event
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strings"
|
||||
|
||||
"github.com/XingfenD/yoresee_doc/pkg/constant"
|
||||
"github.com/bytedance/sonic"
|
||||
)
|
||||
|
||||
type NotificationCreateEvent struct {
|
||||
ReceiverExternalIDs []string `json:"receiver_external_ids"`
|
||||
Type string `json:"type"`
|
||||
Title string `json:"title"`
|
||||
Content string `json:"content"`
|
||||
PayloadJSON string `json:"payload_json"`
|
||||
}
|
||||
|
||||
func NotificationCreateTopic() string {
|
||||
return constant.NotificationTopicDefault
|
||||
}
|
||||
|
||||
func PublishNotificationCreateEvent(ctx context.Context, evt NotificationCreateEvent) error {
|
||||
return publishJSONToRabbitMQ(ctx, NotificationCreateTopic(), evt)
|
||||
}
|
||||
|
||||
func DecodeNotificationCreateEvent(data []byte) (*NotificationCreateEvent, error) {
|
||||
var evt NotificationCreateEvent
|
||||
if err := sonic.Unmarshal(data, &evt); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
cleanReceiverIDs := make([]string, 0, len(evt.ReceiverExternalIDs))
|
||||
for _, receiverID := range evt.ReceiverExternalIDs {
|
||||
receiverID = strings.TrimSpace(receiverID)
|
||||
if receiverID == "" {
|
||||
continue
|
||||
}
|
||||
cleanReceiverIDs = append(cleanReceiverIDs, receiverID)
|
||||
}
|
||||
evt.ReceiverExternalIDs = cleanReceiverIDs
|
||||
return &evt, nil
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
package domain_event
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/XingfenD/yoresee_doc/internal/service/mq_service"
|
||||
"github.com/XingfenD/yoresee_doc/pkg/mq"
|
||||
"github.com/bytedance/sonic"
|
||||
)
|
||||
|
||||
func publishJSONToRabbitMQ(ctx context.Context, topic string, payload any) error {
|
||||
data, err := sonic.Marshal(payload)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return mq_service.MQSvc.Publish(ctx, mq.BackendRabbitMQ, mq.PublishMessage{
|
||||
Topic: topic,
|
||||
Body: data,
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
package dto
|
||||
|
||||
import "time"
|
||||
|
||||
type AttachmentResponse struct {
|
||||
ExternalID string `json:"external_id"`
|
||||
DocumentExternalID string `json:"document_external_id"`
|
||||
Name string `json:"name"`
|
||||
Size int64 `json:"size"`
|
||||
MimeType string `json:"mime_type"`
|
||||
Path string `json:"path"`
|
||||
URL string `json:"url"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
package dto
|
||||
|
||||
type CreateCommentRequest struct {
|
||||
DocumentExternalID string `json:"document_external_id"`
|
||||
Content string `json:"content"`
|
||||
ParentExternalID *string `json:"parent_external_id,omitempty"`
|
||||
AnchorID *string `json:"anchor_id,omitempty"`
|
||||
Quote *string `json:"quote,omitempty"`
|
||||
CreatorExternalID string `json:"creator_external_id"`
|
||||
MentionedUserExternalIDs []string `json:"mentioned_user_external_ids,omitempty"`
|
||||
}
|
||||
|
||||
type ListCommentsRequest struct {
|
||||
DocumentExternalID string `json:"document_external_id"`
|
||||
Page int `json:"page"`
|
||||
PageSize int `json:"page_size"`
|
||||
}
|
||||
|
||||
type DeleteCommentRequest struct {
|
||||
ExternalID string `json:"external_id"`
|
||||
OperatorExternalID string `json:"operator_external_id"`
|
||||
}
|
||||
|
||||
type UpdateCommentRequest struct {
|
||||
ExternalID string `json:"external_id"`
|
||||
Content string `json:"content"`
|
||||
OperatorExternalID string `json:"operator_external_id"`
|
||||
}
|
||||
@@ -0,0 +1,169 @@
|
||||
package dto
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/XingfenD/yoresee_doc/internal/model"
|
||||
)
|
||||
|
||||
type DocumentType string
|
||||
|
||||
const DocumentType_Markdown DocumentType = "markdown"
|
||||
const DocumentType_Table DocumentType = "table"
|
||||
const DocumentType_MarkdownSlide DocumentType = "markdown_slide"
|
||||
const DocumentType_YoreseeRichText DocumentType = "yoresee_rich_text"
|
||||
|
||||
type ContainerType string
|
||||
|
||||
const ContainerType_Own ContainerType = "own"
|
||||
const ContainerType_KnowledgeBase ContainerType = "knowledge_base"
|
||||
|
||||
type DocumentBase struct {
|
||||
ExternalID string `json:"external_id"`
|
||||
Title string `json:"title"`
|
||||
Type DocumentType `json:"type"`
|
||||
Summary string `json:"summary"`
|
||||
IsPublic bool `json:"is_public"`
|
||||
Tags []string `json:"tags"`
|
||||
ViewCount int `json:"view_count"`
|
||||
EditCount int `json:"edit_count"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
}
|
||||
|
||||
type DocumentMetaResponse struct {
|
||||
DocumentBase
|
||||
Children []*DocumentMetaResponse `json:"children,omitempty"`
|
||||
HasChildren bool `json:"hasChildren,omitempty"`
|
||||
}
|
||||
|
||||
type DocumentResponse struct {
|
||||
DocumentMetaResponse
|
||||
Content string `json:"content"`
|
||||
}
|
||||
|
||||
func NewDocumentMetaResponseFromModel(doc *model.Document) *DocumentMetaResponse {
|
||||
response := &DocumentMetaResponse{
|
||||
DocumentBase: DocumentBase{
|
||||
ExternalID: doc.ExternalID,
|
||||
Title: doc.Title,
|
||||
Type: DocumentType(doc.Type),
|
||||
Summary: doc.Summary,
|
||||
IsPublic: doc.IsPublic,
|
||||
Tags: doc.Tags,
|
||||
ViewCount: doc.ViewCount,
|
||||
EditCount: doc.EditCount,
|
||||
CreatedAt: doc.CreatedAt,
|
||||
UpdatedAt: doc.UpdatedAt,
|
||||
},
|
||||
}
|
||||
|
||||
return response
|
||||
}
|
||||
|
||||
func NewDocumentResponseFromModel(doc *model.Document) *DocumentResponse {
|
||||
if doc == nil {
|
||||
return &DocumentResponse{}
|
||||
}
|
||||
response := &DocumentResponse{
|
||||
DocumentMetaResponse: DocumentMetaResponse{
|
||||
DocumentBase: DocumentBase{
|
||||
ExternalID: doc.ExternalID,
|
||||
Title: doc.Title,
|
||||
Type: DocumentType(doc.Type),
|
||||
Summary: doc.Summary,
|
||||
IsPublic: doc.IsPublic,
|
||||
Tags: doc.Tags,
|
||||
ViewCount: doc.ViewCount,
|
||||
EditCount: doc.EditCount,
|
||||
CreatedAt: doc.CreatedAt,
|
||||
UpdatedAt: doc.UpdatedAt,
|
||||
},
|
||||
},
|
||||
Content: doc.Content,
|
||||
}
|
||||
|
||||
return response
|
||||
}
|
||||
|
||||
// type DirectoryResponse struct {
|
||||
// ExternalID string `json:"external_id"`
|
||||
// Title string `json:"title"`
|
||||
// HasChildren bool `json:"has_children"`
|
||||
// ParentID string `json:"parent_id"`
|
||||
// }
|
||||
|
||||
type DocumentsListExternalArgs struct {
|
||||
UserExternalID *string `json:"user_external_id"`
|
||||
RootDocumentExternalID *string `json:"root_document_external_id"`
|
||||
KnowledgeExternalID *string `json:"knowledge_external_id"`
|
||||
}
|
||||
|
||||
type DocumentsListFilterArgs struct {
|
||||
TitleKeyword *string `json:"title_keyword"`
|
||||
DocType *string `json:"doc_type"`
|
||||
Tags []string `json:"tags"`
|
||||
CreateTimeRangeStart *string `json:"create_time_range_start"`
|
||||
CreateTimeRangeEnd *string `json:"create_time_range_end"`
|
||||
UpdateTimeRangeStart *string `json:"update_time_range_start"`
|
||||
UpdateTimeRangeEnd *string `json:"update_time_range_end"`
|
||||
}
|
||||
|
||||
type ListDocumentsBaseArgs struct {
|
||||
ListOwnDoc bool `json:"list_own_doc"`
|
||||
DirectoryOnly bool `json:"directory_only"`
|
||||
}
|
||||
|
||||
type ListRecentDocumentsRequest struct {
|
||||
UserExternalID string `json:"user_external_id"`
|
||||
StartTime *time.Time `json:"start_time"`
|
||||
EndTime *time.Time `json:"end_time"`
|
||||
Pagination Pagination `json:"pagination"`
|
||||
}
|
||||
|
||||
type RecordRecentDocumentRequest struct {
|
||||
UserExternalID string `json:"user_external_id"`
|
||||
DocumentExternalID string `json:"document_external_id"`
|
||||
}
|
||||
|
||||
type ListDocumentsByExternalRequest struct {
|
||||
ExternalArgs *DocumentsListExternalArgs `json:"external_args"`
|
||||
ListDocumentsBaseArgs
|
||||
FilterArgs *DocumentsListFilterArgs `json:"filter_args"`
|
||||
SortArgs SortArgs `json:"sort_args"`
|
||||
Pagination Pagination `json:"pagination"`
|
||||
Options *RecursiveOptions `json:"options"`
|
||||
}
|
||||
|
||||
type CreateDocumentRequest struct {
|
||||
Title string `json:"title"`
|
||||
Type DocumentType `json:"type"`
|
||||
ContainerType ContainerType `json:"container_type"`
|
||||
IsPublic bool `json:"is_public"`
|
||||
CreatorExternalID *string `json:"creator_external_id"`
|
||||
TemplateID *int64 `json:"template_id"`
|
||||
|
||||
// optional
|
||||
ParentExternalID *string `json:"parent_external_id"`
|
||||
KnowledgeExternalID *string `json:"knowledge_external_id"`
|
||||
}
|
||||
|
||||
type CreateDocumentResponse struct {
|
||||
ExternalID string `json:"external_id"`
|
||||
}
|
||||
|
||||
type UpdateDocumentRequest struct {
|
||||
ExternalID string `json:"external_id"`
|
||||
Title *string `json:"title,omitempty"`
|
||||
KnowledgeBaseExternalID *string `json:"knowledge_base_external_id,omitempty"`
|
||||
ParentExternalID *string `json:"parent_external_id,omitempty"`
|
||||
MoveToContainer *ContainerType `json:"move_to_container,omitempty"`
|
||||
Content *string `json:"content,omitempty"`
|
||||
}
|
||||
|
||||
type UpdateDocumentMetaRequest struct {
|
||||
ExternalID string `json:"external_id"`
|
||||
Title *string `json:"title,omitempty"`
|
||||
Summary *string `json:"summary,omitempty"`
|
||||
Tags *[]string `json:"tags,omitempty"`
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
package dto
|
||||
|
||||
type GetDocumentSettingsRequest struct {
|
||||
ExternalID string `json:"external_id"`
|
||||
}
|
||||
|
||||
type DocumentSettingsResponse struct {
|
||||
IsPublic bool `json:"is_public"`
|
||||
}
|
||||
|
||||
type UpdateDocumentSettingsRequest struct {
|
||||
ExternalID string `json:"external_id"`
|
||||
IsPublic bool `json:"is_public"`
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
package dto
|
||||
|
||||
import "time"
|
||||
|
||||
type DocumentVersionResponse struct {
|
||||
Version int `json:"version"`
|
||||
Title string `json:"title"`
|
||||
Content string `json:"content"`
|
||||
ChangeSummary string `json:"change_summary"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
package dto
|
||||
|
||||
type Pagination struct {
|
||||
Page int `json:"page"`
|
||||
PageSize int `json:"page_size"`
|
||||
}
|
||||
|
||||
func (p *Pagination) Validate() bool {
|
||||
if p == nil {
|
||||
return false
|
||||
}
|
||||
if p.Page <= 0 || p.PageSize <= 0 {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
type SortArgs struct {
|
||||
Field string `json:"field"`
|
||||
Desc bool `json:"desc"`
|
||||
}
|
||||
|
||||
type RecursiveOptions struct {
|
||||
IncludeChildren bool `json:"include_children"`
|
||||
Recursive bool `json:"recursive"`
|
||||
Depth *int `json:"depth"`
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
package dto
|
||||
|
||||
import "time"
|
||||
|
||||
type ListInvitationsReq struct {
|
||||
CreatorID *int64 `json:"creator_id"`
|
||||
MaxUsedCnt *int64 `json:"max_used_cnt"`
|
||||
ExpiresAtStart *string `json:"expires_at_start"`
|
||||
ExpiresAtEnd *string `json:"expires_at_end"`
|
||||
CreatedAtStart *string `json:"created_at_start"`
|
||||
CreatedAtEnd *string `json:"created_at_end"`
|
||||
Disabled *bool `json:"disabled"`
|
||||
OnlyMine *bool `json:"only_mine"`
|
||||
Keyword *string `json:"keyword"`
|
||||
SortArgs SortArgs `json:"sort_args"`
|
||||
Pagination Pagination `json:"pagination"`
|
||||
}
|
||||
|
||||
type CreateInvitationRequest struct {
|
||||
CreatorExternalID string `json:"creator_external_id"`
|
||||
MaxUsedCnt *int64 `json:"max_used_cnt"`
|
||||
ExpiresAt *time.Time `json:"expires_at"`
|
||||
Note *string `json:"note"`
|
||||
}
|
||||
|
||||
type UpdateInvitationRequest struct {
|
||||
Code string `json:"code"`
|
||||
MaxUsedCnt *int64 `json:"max_used_cnt"`
|
||||
ExpiresAt *time.Time `json:"expires_at"`
|
||||
Disabled *bool `json:"disabled"`
|
||||
Note *string `json:"note"`
|
||||
}
|
||||
|
||||
type DeleteInvitationRequest struct {
|
||||
Code string `json:"code"`
|
||||
}
|
||||
|
||||
type InvitationResponse struct {
|
||||
Code string `json:"code"`
|
||||
CreatedByExternalID string `json:"created_by_external_id"`
|
||||
CreatedByName string `json:"created_by_name"`
|
||||
UsedCnt int64 `json:"used_cnt"`
|
||||
MaxUsedCnt *int64 `json:"max_used_cnt"`
|
||||
ExpiresAt *time.Time `json:"expires_at"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
Disabled bool `json:"disabled"`
|
||||
Note *string `json:"note"`
|
||||
}
|
||||
|
||||
type ListInvitationRecordsRequest struct {
|
||||
Code *string `json:"code"`
|
||||
Status *string `json:"status"`
|
||||
UsedAtStart *string `json:"used_at_start"`
|
||||
UsedAtEnd *string `json:"used_at_end"`
|
||||
CreatorID *int64 `json:"creator_id"`
|
||||
OnlyMine *bool `json:"only_mine"`
|
||||
Keyword *string `json:"keyword"`
|
||||
Pagination Pagination `json:"pagination"`
|
||||
}
|
||||
|
||||
type InvitationRecordResponse struct {
|
||||
Code string `json:"code"`
|
||||
UsedBy string `json:"used_by"`
|
||||
UsedByExternalID string `json:"used_by_external_id"`
|
||||
UsedAt time.Time `json:"used_at"`
|
||||
Status string `json:"status"`
|
||||
}
|
||||
@@ -0,0 +1,98 @@
|
||||
package dto
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/XingfenD/yoresee_doc/internal/model"
|
||||
)
|
||||
|
||||
type KnowledgeBaseBase struct {
|
||||
ExternalID string `json:"external_id"`
|
||||
Name string `json:"name"`
|
||||
Description string `json:"description"`
|
||||
Cover string `json:"cover"`
|
||||
IsPublic bool `json:"is_public"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
DeletedAt time.Time `json:"deleted_at"`
|
||||
}
|
||||
|
||||
type KnowledgeBaseExtend struct {
|
||||
CreatorUserExternalID string `json:"creator_user_external_id"`
|
||||
CreatorName string `json:"creator_name"`
|
||||
DocumentsCount int64 `json:"documents_count"`
|
||||
}
|
||||
|
||||
type KnowledgeBaseResponse struct {
|
||||
KnowledgeBaseBase
|
||||
KnowledgeBaseExtend
|
||||
}
|
||||
|
||||
func NewKnowledgeBaseResponseFromModel(kb *model.KnowledgeBase, kbExtend *KnowledgeBaseExtend) *KnowledgeBaseResponse {
|
||||
response := &KnowledgeBaseResponse{
|
||||
KnowledgeBaseBase: KnowledgeBaseBase{
|
||||
ExternalID: kb.ExternalID,
|
||||
Name: kb.Name,
|
||||
Description: kb.Description,
|
||||
Cover: kb.Cover,
|
||||
IsPublic: kb.IsPublic,
|
||||
CreatedAt: kb.CreatedAt,
|
||||
UpdatedAt: kb.UpdatedAt,
|
||||
DeletedAt: kb.DeletedAt,
|
||||
},
|
||||
}
|
||||
if kbExtend != nil {
|
||||
response.KnowledgeBaseExtend = KnowledgeBaseExtend{
|
||||
CreatorUserExternalID: kbExtend.CreatorUserExternalID,
|
||||
CreatorName: kbExtend.CreatorName,
|
||||
DocumentsCount: kbExtend.DocumentsCount,
|
||||
}
|
||||
}
|
||||
|
||||
return response
|
||||
}
|
||||
|
||||
type CreateRecentKnowledgeBaseRequest struct {
|
||||
UserExternalID string `json:"user_external_id"`
|
||||
KnowledgeBaseExternalID string `json:"knowledge_base_external_id"`
|
||||
AssessTime time.Time `json:"assess_time"`
|
||||
}
|
||||
|
||||
type CreateKnowledgeBaseRequest struct {
|
||||
CreatorExternalID string `json:"creator_external_id"`
|
||||
Name string `json:"name"`
|
||||
Description string `json:"description"`
|
||||
Cover string `json:"cover"`
|
||||
IsPublic bool `json:"is_public"`
|
||||
}
|
||||
|
||||
type CreateKnowledgeBaseResponse struct {
|
||||
ExternalID string `json:"external_id"`
|
||||
}
|
||||
|
||||
type ListRecentKnowledgeBasesRequest struct {
|
||||
UserExternalID string `json:"user_external_id"`
|
||||
StartTime *time.Time `json:"start_time,omitempty"`
|
||||
EndTime *time.Time `json:"end_time,omitempty"`
|
||||
Pagination Pagination `json:"pagination"`
|
||||
}
|
||||
|
||||
type KnowledgeBaseListByExternalRequest struct {
|
||||
CreatorExternalID string `json:"creator_external_id"`
|
||||
FilterArgs *KnowledgeBaseListFilterArgs `json:"filter_args"`
|
||||
SortArgs SortArgs `json:"sort_args"`
|
||||
Pagination Pagination `json:"pagination"`
|
||||
}
|
||||
|
||||
type KnowledgeBaseGetByExternalIDRequest struct {
|
||||
KnowledgeBaseExternalID string `json:"knowledge_base_external_id"`
|
||||
}
|
||||
|
||||
type KnowledgeBaseListFilterArgs struct {
|
||||
IsPublic *bool `json:"is_public"`
|
||||
NameKeyword *string `json:"name_keyword"`
|
||||
CreateTimeRangeStart *string `json:"create_time_range_start"`
|
||||
CreateTimeRangeEnd *string `json:"create_time_range_end"`
|
||||
UpdateTimeRangeStart *string `json:"update_time_range_start"`
|
||||
UpdateTimeRangeEnd *string `json:"update_time_range_end"`
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
package dto
|
||||
|
||||
// MembershipType mirrors model.MembershipType to keep DTO layer independent.
|
||||
type MembershipType int64
|
||||
|
||||
const (
|
||||
MembershipType_UserGroup MembershipType = 1
|
||||
MembershipType_OrgNode MembershipType = 2
|
||||
)
|
||||
|
||||
type MembershipRelationBase struct {
|
||||
Type MembershipType `json:"type"`
|
||||
UserID int64 `json:"user_id"`
|
||||
MembershipID int64 `json:"membership_id"`
|
||||
}
|
||||
|
||||
type MembershipBase struct {
|
||||
Type MembershipType `json:"type"`
|
||||
MembershipExternalID string `json:"membership_external_id"`
|
||||
MembershipName string `json:"membership_name"`
|
||||
}
|
||||
|
||||
type MembershipBaseRequest struct {
|
||||
Type MembershipType `json:"type"`
|
||||
MembershipExternalID string `json:"membership_external_id"`
|
||||
}
|
||||
|
||||
type MembershipMetaResponse struct {
|
||||
MembershipBase
|
||||
Description string `json:"description"`
|
||||
}
|
||||
|
||||
type CreateMembershipRelationRequest struct {
|
||||
MembershipBaseRequest
|
||||
UserExternalID string `json:"user_external_id"`
|
||||
}
|
||||
|
||||
type MembershipRelationResponse struct {
|
||||
MembershipBase
|
||||
UserList []UserBase `json:"user_list"`
|
||||
}
|
||||
|
||||
// type MembershipAuthorityResponse struct {
|
||||
// MembershipBase
|
||||
// CreatorExternalID string
|
||||
// CurrentUserAuthority []model.Permission
|
||||
// }
|
||||
@@ -0,0 +1,20 @@
|
||||
package dto
|
||||
|
||||
type CreateNotificationRequest struct {
|
||||
ReceiverExternalIDs []string `json:"receiver_external_ids"`
|
||||
Type string `json:"type"`
|
||||
Title string `json:"title"`
|
||||
Content string `json:"content"`
|
||||
PayloadJSON string `json:"payload_json"`
|
||||
}
|
||||
|
||||
type ListNotificationsRequest struct {
|
||||
UserExternalID string `json:"user_external_id"`
|
||||
Status *string `json:"status,omitempty"`
|
||||
Pagination Pagination `json:"pagination"`
|
||||
}
|
||||
|
||||
type MarkNotificationsReadRequest struct {
|
||||
UserExternalID string `json:"user_external_id"`
|
||||
ExternalIDs []string `json:"external_ids"`
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
package dto
|
||||
|
||||
type OrgNodeResponse struct {
|
||||
ExternalID string `json:"external_id"`
|
||||
ParentExternalID string `json:"parent_external_id"`
|
||||
Name string `json:"name"`
|
||||
Path string `json:"path"`
|
||||
Description string `json:"description"`
|
||||
CreatorUserExternalID string `json:"creator_user_external_id"`
|
||||
MemberCount int `json:"member_count"`
|
||||
Children []*OrgNodeResponse `json:"children,omitempty"`
|
||||
}
|
||||
|
||||
type ListOrgNodesRequest struct {
|
||||
ParentExternalID string `json:"parent_external_id"`
|
||||
Keyword *string `json:"keyword"`
|
||||
Pagination Pagination `json:"pagination"`
|
||||
IncludeChildren bool `json:"include_children"`
|
||||
}
|
||||
|
||||
type GetOrgNodeRequest struct {
|
||||
ExternalID string `json:"external_id"`
|
||||
IncludeChildren bool `json:"include_children"`
|
||||
}
|
||||
|
||||
type CreateOrgNodeRequest struct {
|
||||
CreatorUserExternalID string `json:"creator_user_external_id"`
|
||||
ParentExternalID string `json:"parent_external_id"`
|
||||
Name string `json:"name"`
|
||||
Description string `json:"description"`
|
||||
MemberUserExternalIDs []string `json:"member_user_external_ids"`
|
||||
}
|
||||
|
||||
type UpdateOrgNodeRequest struct {
|
||||
ExternalID string `json:"external_id"`
|
||||
Name *string `json:"name"`
|
||||
Description *string `json:"description"`
|
||||
SyncMembers bool `json:"sync_members"`
|
||||
MemberUserExternalIDs []string `json:"member_user_external_ids"`
|
||||
}
|
||||
|
||||
type DeleteOrgNodeRequest struct {
|
||||
ExternalID string `json:"external_id"`
|
||||
}
|
||||
|
||||
type MoveOrgNodeRequest struct {
|
||||
ExternalID string `json:"external_id"`
|
||||
NewParentExternalID string `json:"new_parent_external_id"`
|
||||
}
|
||||
|
||||
type ListOrgNodeMembersRequest struct {
|
||||
ExternalID string `json:"external_id"`
|
||||
Keyword *string `json:"keyword"`
|
||||
Pagination Pagination `json:"pagination"`
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
package dto
|
||||
|
||||
import (
|
||||
"time"
|
||||
)
|
||||
|
||||
// PermissionGrant 权限授予请求
|
||||
type PermissionGrant struct {
|
||||
Resource Resource `json:"resource" binding:"required"`
|
||||
Subject Subject `json:"subject" binding:"required"`
|
||||
Permissions []string `json:"permissions" binding:"required"`
|
||||
Scope Scope `json:"scope" binding:"required"`
|
||||
Priority int `json:"priority"`
|
||||
IsDeny bool `json:"is_deny"`
|
||||
ValidFrom *time.Time `json:"valid_from"`
|
||||
ValidUntil *time.Time `json:"valid_until"`
|
||||
}
|
||||
|
||||
// Resource 资源DTO
|
||||
type Resource struct {
|
||||
Type string `json:"type" binding:"required"`
|
||||
ID string `json:"id" binding:"required"`
|
||||
Path string `json:"path"`
|
||||
}
|
||||
type Subject struct {
|
||||
Type string `json:"type" binding:"required"`
|
||||
ID string `json:"id" binding:"required"`
|
||||
}
|
||||
|
||||
type Scope struct {
|
||||
Type string `json:"type" binding:"required"`
|
||||
}
|
||||
|
||||
type PermissionCheck struct {
|
||||
Resource Resource `json:"resource" binding:"required"`
|
||||
Permission string `json:"permission" binding:"required"`
|
||||
}
|
||||
|
||||
type PermissionBatchCheck struct {
|
||||
Resource Resource `json:"resource" binding:"required"`
|
||||
Permissions []string `json:"permissions" binding:"required"`
|
||||
}
|
||||
|
||||
type PermissionBatchCheckResponse map[string]bool
|
||||
|
||||
type PermissionEffectiveResponse []string
|
||||
@@ -0,0 +1,67 @@
|
||||
package dto
|
||||
|
||||
import "time"
|
||||
|
||||
type TemplateContainer string
|
||||
|
||||
const (
|
||||
TemplateContainerOwn TemplateContainer = "own"
|
||||
TemplateContainerKnowledgeBase TemplateContainer = "knowledge_base"
|
||||
TemplateContainerPublic TemplateContainer = "public"
|
||||
)
|
||||
|
||||
type CreateTemplateRequest struct {
|
||||
UserExternalID string `json:"user_external_id"`
|
||||
TargetContainer TemplateContainer `json:"target_container"`
|
||||
KnowledgeBaseExternalID *string `json:"knowledge_base_external_id,omitempty"`
|
||||
TemplateContent string `json:"template_content"`
|
||||
Type DocumentType `json:"type"`
|
||||
}
|
||||
|
||||
type UpdateTemplateSettingsRequest struct {
|
||||
UserExternalID string `json:"user_external_id"`
|
||||
TemplateID int64 `json:"template_id"`
|
||||
Name *string `json:"name,omitempty"`
|
||||
Description *string `json:"description,omitempty"`
|
||||
IsPublic *bool `json:"is_public,omitempty"`
|
||||
}
|
||||
|
||||
type TemplateResponse struct {
|
||||
ID int64 `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Description string `json:"description"`
|
||||
Content string `json:"content"`
|
||||
Type DocumentType `json:"type"`
|
||||
Scope string `json:"scope"`
|
||||
KnowledgeBaseExternalID string `json:"knowledge_base_external_id"`
|
||||
Tags []string `json:"tags"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
}
|
||||
|
||||
type TemplateListFilterArgs struct {
|
||||
NameKeyword *string `json:"name_keyword"`
|
||||
TargetContainer *TemplateContainer `json:"target_container"`
|
||||
KnowledgeBaseID *string `json:"knowledge_base_id"`
|
||||
Type *DocumentType `json:"type"`
|
||||
}
|
||||
|
||||
type TemplateListByExternalRequest struct {
|
||||
CreatorExternalID string `json:"creator_external_id"`
|
||||
FilterArgs *TemplateListFilterArgs `json:"filter_args"`
|
||||
SortArgs SortArgs `json:"sort_args"`
|
||||
Pagination Pagination `json:"pagination"`
|
||||
}
|
||||
|
||||
type CreateRecentTemplateRequest struct {
|
||||
UserExternalID string `json:"user_external_id"`
|
||||
TemplateID int64 `json:"template_id"`
|
||||
AccessTime time.Time `json:"access_time"`
|
||||
}
|
||||
|
||||
type ListRecentTemplatesRequest struct {
|
||||
UserExternalID string `json:"user_external_id"`
|
||||
StartTime *time.Time `json:"start_time,omitempty"`
|
||||
EndTime *time.Time `json:"end_time,omitempty"`
|
||||
Pagination Pagination `json:"pagination"`
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
package dto
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/XingfenD/yoresee_doc/internal/model"
|
||||
)
|
||||
|
||||
type UserBase struct {
|
||||
ExternalID string `json:"external_id"`
|
||||
Username string `json:"username"`
|
||||
Email string `json:"email"`
|
||||
Nickname string `json:"nickname"`
|
||||
Avatar string `json:"avatar"`
|
||||
AvatarObjectKey string `json:"-"`
|
||||
AvatarVersion int64 `json:"-"`
|
||||
Status int `json:"status"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
}
|
||||
|
||||
type UserCreate struct {
|
||||
Username string `json:"username"`
|
||||
Email string `json:"email"`
|
||||
Password string `json:"password"`
|
||||
InvitationCode *string `json:"invitation_code"`
|
||||
}
|
||||
|
||||
type UserUpdate struct {
|
||||
Username string `json:"username"`
|
||||
Email string `json:"email"`
|
||||
Password string `json:"password"`
|
||||
Nickname string `json:"nickname"`
|
||||
Avatar string `json:"avatar"`
|
||||
Status int `json:"status"`
|
||||
InvitationCode *string `json:"invitation_code"`
|
||||
}
|
||||
|
||||
type UpdateProfileRequest struct {
|
||||
Username *string `json:"username"`
|
||||
Email *string `json:"email"`
|
||||
Nickname *string `json:"nickname"`
|
||||
Password *string `json:"password"`
|
||||
AvatarFile []byte `json:"avatar_file"`
|
||||
AvatarFilename *string `json:"avatar_filename"`
|
||||
AvatarContentType *string `json:"avatar_content_type"`
|
||||
}
|
||||
|
||||
type UserResponse struct {
|
||||
UserBase
|
||||
InvitationCode *string `json:"invitation_code"`
|
||||
}
|
||||
|
||||
func NewUserResponseFromModel(user *model.User) *UserResponse {
|
||||
return &UserResponse{
|
||||
UserBase: UserBase{
|
||||
ExternalID: user.ExternalID,
|
||||
Username: user.Username,
|
||||
Email: user.Email,
|
||||
Nickname: user.Nickname,
|
||||
Avatar: "",
|
||||
AvatarObjectKey: user.AvatarObjectKey,
|
||||
AvatarVersion: user.AvatarVersion,
|
||||
Status: user.Status,
|
||||
CreatedAt: user.CreatedAt,
|
||||
UpdatedAt: user.UpdatedAt,
|
||||
},
|
||||
InvitationCode: user.InvitationCode,
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
package dto
|
||||
|
||||
type UserGroupResponse struct {
|
||||
ExternalID string `json:"external_id"`
|
||||
Name string `json:"name"`
|
||||
Description string `json:"description"`
|
||||
CreatorUserExternalID string `json:"creator_user_external_id"`
|
||||
MemberCount int `json:"member_count"`
|
||||
}
|
||||
|
||||
type ListUserGroupsRequest struct {
|
||||
Keyword *string `json:"keyword"`
|
||||
Pagination Pagination `json:"pagination"`
|
||||
}
|
||||
|
||||
type GetUserGroupRequest struct {
|
||||
ExternalID string `json:"external_id"`
|
||||
}
|
||||
|
||||
type CreateUserGroupRequest struct {
|
||||
CreatorUserExternalID string `json:"creator_user_external_id"`
|
||||
Name string `json:"name"`
|
||||
Description string `json:"description"`
|
||||
MemberUserExternalIDs []string `json:"member_user_external_ids"`
|
||||
}
|
||||
|
||||
type UpdateUserGroupRequest struct {
|
||||
ExternalID string `json:"external_id"`
|
||||
Name *string `json:"name"`
|
||||
Description *string `json:"description"`
|
||||
SyncMembers bool `json:"sync_members"`
|
||||
MemberUserExternalIDs []string `json:"member_user_external_ids"`
|
||||
}
|
||||
|
||||
type DeleteUserGroupRequest struct {
|
||||
ExternalID string `json:"external_id"`
|
||||
}
|
||||
|
||||
type ListUsersRequest struct {
|
||||
Keyword *string `json:"keyword"`
|
||||
Pagination Pagination `json:"pagination"`
|
||||
}
|
||||
|
||||
type UpdateUserRequest struct {
|
||||
ExternalID string `json:"external_id"`
|
||||
Username *string `json:"username"`
|
||||
Email *string `json:"email"`
|
||||
Nickname *string `json:"nickname"`
|
||||
Status *int32 `json:"status"`
|
||||
}
|
||||
|
||||
type ListUserGroupMembersRequest struct {
|
||||
ExternalID string `json:"external_id"`
|
||||
Keyword *string `json:"keyword"`
|
||||
Pagination Pagination `json:"pagination"`
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
package doc_container_mapper
|
||||
|
||||
import (
|
||||
"github.com/XingfenD/yoresee_doc/internal/dto"
|
||||
"github.com/XingfenD/yoresee_doc/internal/model"
|
||||
pb "github.com/XingfenD/yoresee_doc/pkg/gen/yoresee_doc/v1"
|
||||
)
|
||||
|
||||
type knowledgeBaseAdapter struct{}
|
||||
|
||||
func (knowledgeBaseAdapter) Name() string {
|
||||
return string(dto.ContainerType_KnowledgeBase)
|
||||
}
|
||||
|
||||
func (knowledgeBaseAdapter) ProtoType() pb.CreateDocumentContainerType {
|
||||
return pb.CreateDocumentContainerType_CREATE_DOCUMENT_CONTAINER_TYPE_KNOWLEDGE_BASE
|
||||
}
|
||||
|
||||
func (knowledgeBaseAdapter) DTOType() dto.ContainerType {
|
||||
return dto.ContainerType_KnowledgeBase
|
||||
}
|
||||
|
||||
func (knowledgeBaseAdapter) ModelType() model.ContainerType {
|
||||
return model.ContainerType_KnowledgeBase
|
||||
}
|
||||
|
||||
func (knowledgeBaseAdapter) RequiresKnowledgeBaseID() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func init() { RegisterMapper(knowledgeBaseAdapter{}) }
|
||||
@@ -0,0 +1,31 @@
|
||||
package doc_container_mapper
|
||||
|
||||
import (
|
||||
"github.com/XingfenD/yoresee_doc/internal/dto"
|
||||
"github.com/XingfenD/yoresee_doc/internal/model"
|
||||
pb "github.com/XingfenD/yoresee_doc/pkg/gen/yoresee_doc/v1"
|
||||
)
|
||||
|
||||
type ownAdapter struct{}
|
||||
|
||||
func (ownAdapter) Name() string {
|
||||
return string(dto.ContainerType_Own)
|
||||
}
|
||||
|
||||
func (ownAdapter) ProtoType() pb.CreateDocumentContainerType {
|
||||
return pb.CreateDocumentContainerType_CREATE_DOCUMENT_CONTAINER_TYPE_OWN
|
||||
}
|
||||
|
||||
func (ownAdapter) DTOType() dto.ContainerType {
|
||||
return dto.ContainerType_Own
|
||||
}
|
||||
|
||||
func (ownAdapter) ModelType() model.ContainerType {
|
||||
return model.ContainerType_Own
|
||||
}
|
||||
|
||||
func (ownAdapter) RequiresKnowledgeBaseID() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func init() { RegisterMapper(ownAdapter{}) }
|
||||
@@ -0,0 +1,168 @@
|
||||
package doc_container_mapper
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/XingfenD/yoresee_doc/internal/dto"
|
||||
"github.com/XingfenD/yoresee_doc/internal/model"
|
||||
pb "github.com/XingfenD/yoresee_doc/pkg/gen/yoresee_doc/v1"
|
||||
)
|
||||
|
||||
const defaultContainerName = "own"
|
||||
|
||||
// Mapper defines a document container type mapping across transport(dto/proto) and model layers.
|
||||
type Mapper interface {
|
||||
Name() string
|
||||
ProtoType() pb.CreateDocumentContainerType
|
||||
DTOType() dto.ContainerType
|
||||
ModelType() model.ContainerType
|
||||
RequiresKnowledgeBaseID() bool
|
||||
}
|
||||
|
||||
type registry struct {
|
||||
byName map[string]Mapper
|
||||
byProto map[pb.CreateDocumentContainerType]Mapper
|
||||
byDTO map[dto.ContainerType]Mapper
|
||||
byModel map[model.ContainerType]Mapper
|
||||
}
|
||||
|
||||
var globalRegistry = ®istry{
|
||||
byName: make(map[string]Mapper),
|
||||
byProto: make(map[pb.CreateDocumentContainerType]Mapper),
|
||||
byDTO: make(map[dto.ContainerType]Mapper),
|
||||
byModel: make(map[model.ContainerType]Mapper),
|
||||
}
|
||||
|
||||
func RegisterMapper(mapper Mapper) {
|
||||
if err := globalRegistry.register(mapper); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
func (r *registry) register(mapper Mapper) error {
|
||||
if mapper == nil {
|
||||
return fmt.Errorf("document container mapper is nil")
|
||||
}
|
||||
name := normalizeName(mapper.Name())
|
||||
if name == "" {
|
||||
return fmt.Errorf("document container mapper name is empty")
|
||||
}
|
||||
|
||||
protoType := mapper.ProtoType()
|
||||
dtoType := mapper.DTOType()
|
||||
modelType := mapper.ModelType()
|
||||
if protoType == pb.CreateDocumentContainerType_CREATE_DOCUMENT_CONTAINER_TYPE_UNSPECIFIED || dtoType == "" || modelType == "" {
|
||||
return fmt.Errorf("document container mapper %q has invalid mapping", name)
|
||||
}
|
||||
|
||||
if _, exists := r.byName[name]; exists {
|
||||
return fmt.Errorf("document container mapper %q already registered", name)
|
||||
}
|
||||
if _, exists := r.byProto[protoType]; exists {
|
||||
return fmt.Errorf("document container proto mapping %v already registered", protoType)
|
||||
}
|
||||
if _, exists := r.byDTO[dtoType]; exists {
|
||||
return fmt.Errorf("document container dto mapping %q already registered", dtoType)
|
||||
}
|
||||
if _, exists := r.byModel[modelType]; exists {
|
||||
return fmt.Errorf("document container model mapping %q already registered", modelType)
|
||||
}
|
||||
|
||||
r.byName[name] = mapper
|
||||
r.byProto[protoType] = mapper
|
||||
r.byDTO[dtoType] = mapper
|
||||
r.byModel[modelType] = mapper
|
||||
return nil
|
||||
}
|
||||
|
||||
func normalizeName(name string) string {
|
||||
return strings.ToLower(strings.TrimSpace(name))
|
||||
}
|
||||
|
||||
func (r *registry) findByName(name string) Mapper {
|
||||
return r.byName[normalizeName(name)]
|
||||
}
|
||||
|
||||
func (r *registry) findByProto(t pb.CreateDocumentContainerType) Mapper {
|
||||
return r.byProto[t]
|
||||
}
|
||||
|
||||
func (r *registry) findByDTO(t dto.ContainerType) Mapper {
|
||||
if mapper, ok := r.byDTO[t]; ok {
|
||||
return mapper
|
||||
}
|
||||
return r.byName[normalizeName(string(t))]
|
||||
}
|
||||
|
||||
func (r *registry) findByModel(t model.ContainerType) Mapper {
|
||||
if mapper, ok := r.byModel[t]; ok {
|
||||
return mapper
|
||||
}
|
||||
return r.byName[normalizeName(string(t))]
|
||||
}
|
||||
|
||||
func (r *registry) defaultMapper() Mapper {
|
||||
if mapper := r.findByName(defaultContainerName); mapper != nil {
|
||||
return mapper
|
||||
}
|
||||
for _, mapper := range r.byName {
|
||||
return mapper
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func DefaultDTOType() dto.ContainerType {
|
||||
mapper := globalRegistry.defaultMapper()
|
||||
if mapper == nil {
|
||||
return dto.ContainerType(defaultContainerName)
|
||||
}
|
||||
return mapper.DTOType()
|
||||
}
|
||||
|
||||
func IsSupportedDTOType(t dto.ContainerType) bool {
|
||||
return globalRegistry.findByDTO(t) != nil
|
||||
}
|
||||
|
||||
func IsSupportedProtoType(t pb.CreateDocumentContainerType) bool {
|
||||
return globalRegistry.findByProto(t) != nil
|
||||
}
|
||||
|
||||
func FromProtoType(t pb.CreateDocumentContainerType) (dto.ContainerType, bool) {
|
||||
if mapper := globalRegistry.findByProto(t); mapper != nil {
|
||||
return mapper.DTOType(), true
|
||||
}
|
||||
return "", false
|
||||
}
|
||||
|
||||
func ToProtoType(t dto.ContainerType) pb.CreateDocumentContainerType {
|
||||
if mapper := globalRegistry.findByDTO(t); mapper != nil {
|
||||
return mapper.ProtoType()
|
||||
}
|
||||
return pb.CreateDocumentContainerType_CREATE_DOCUMENT_CONTAINER_TYPE_UNSPECIFIED
|
||||
}
|
||||
|
||||
func ToModelType(t dto.ContainerType) model.ContainerType {
|
||||
if mapper := globalRegistry.findByDTO(t); mapper != nil {
|
||||
return mapper.ModelType()
|
||||
}
|
||||
return model.ContainerType(DefaultDTOType())
|
||||
}
|
||||
|
||||
func FromModelType(t model.ContainerType) dto.ContainerType {
|
||||
if mapper := globalRegistry.findByModel(t); mapper != nil {
|
||||
return mapper.DTOType()
|
||||
}
|
||||
normalized := normalizeName(string(t))
|
||||
if normalized != "" {
|
||||
return dto.ContainerType(normalized)
|
||||
}
|
||||
return DefaultDTOType()
|
||||
}
|
||||
|
||||
func RequiresKnowledgeBaseID(t dto.ContainerType) bool {
|
||||
if mapper := globalRegistry.findByDTO(t); mapper != nil {
|
||||
return mapper.RequiresKnowledgeBaseID()
|
||||
}
|
||||
return false
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
package doc_type_mapper
|
||||
|
||||
import (
|
||||
"github.com/XingfenD/yoresee_doc/internal/dto"
|
||||
"github.com/XingfenD/yoresee_doc/internal/model"
|
||||
pb "github.com/XingfenD/yoresee_doc/pkg/gen/yoresee_doc/v1"
|
||||
)
|
||||
|
||||
type markdownAdapter struct{}
|
||||
|
||||
func (markdownAdapter) Name() string {
|
||||
return string(dto.DocumentType_Markdown)
|
||||
}
|
||||
|
||||
func (markdownAdapter) ProtoType() pb.DocumentType {
|
||||
return pb.DocumentType_DOCUMENT_TYPE_MARKDOWN
|
||||
}
|
||||
|
||||
func (markdownAdapter) DTOType() dto.DocumentType {
|
||||
return dto.DocumentType_Markdown
|
||||
}
|
||||
|
||||
func (markdownAdapter) ModelType() model.DocumentType {
|
||||
return model.DocumentType_Markdown
|
||||
}
|
||||
|
||||
func init() { RegisterMapper(markdownAdapter{}) }
|
||||
@@ -0,0 +1,27 @@
|
||||
package doc_type_mapper
|
||||
|
||||
import (
|
||||
"github.com/XingfenD/yoresee_doc/internal/dto"
|
||||
"github.com/XingfenD/yoresee_doc/internal/model"
|
||||
pb "github.com/XingfenD/yoresee_doc/pkg/gen/yoresee_doc/v1"
|
||||
)
|
||||
|
||||
type markdownSlideAdapter struct{}
|
||||
|
||||
func (markdownSlideAdapter) Name() string {
|
||||
return string(dto.DocumentType_MarkdownSlide)
|
||||
}
|
||||
|
||||
func (markdownSlideAdapter) ProtoType() pb.DocumentType {
|
||||
return pb.DocumentType_DOCUMENT_TYPE_MARKDOWN_SLIDE
|
||||
}
|
||||
|
||||
func (markdownSlideAdapter) DTOType() dto.DocumentType {
|
||||
return dto.DocumentType_MarkdownSlide
|
||||
}
|
||||
|
||||
func (markdownSlideAdapter) ModelType() model.DocumentType {
|
||||
return model.DocumentType_MarkdownSlide
|
||||
}
|
||||
|
||||
func init() { RegisterMapper(markdownSlideAdapter{}) }
|
||||
@@ -0,0 +1,167 @@
|
||||
package doc_type_mapper
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/XingfenD/yoresee_doc/internal/dto"
|
||||
"github.com/XingfenD/yoresee_doc/internal/model"
|
||||
pb "github.com/XingfenD/yoresee_doc/pkg/gen/yoresee_doc/v1"
|
||||
)
|
||||
|
||||
const defaultTypeName = "markdown"
|
||||
|
||||
// Mapper defines a document type mapping across transport(dto/proto) and model layers.
|
||||
type Mapper interface {
|
||||
Name() string
|
||||
ProtoType() pb.DocumentType
|
||||
DTOType() dto.DocumentType
|
||||
ModelType() model.DocumentType
|
||||
}
|
||||
|
||||
type registry struct {
|
||||
byName map[string]Mapper
|
||||
byProto map[pb.DocumentType]Mapper
|
||||
byDTO map[dto.DocumentType]Mapper
|
||||
byModel map[model.DocumentType]Mapper
|
||||
}
|
||||
|
||||
var globalRegistry = ®istry{
|
||||
byName: make(map[string]Mapper),
|
||||
byProto: make(map[pb.DocumentType]Mapper),
|
||||
byDTO: make(map[dto.DocumentType]Mapper),
|
||||
byModel: make(map[model.DocumentType]Mapper),
|
||||
}
|
||||
|
||||
// RegisterMapper registers a document type mapper.
|
||||
// Implementing a new type only requires adding a new mapper and calling this in init().
|
||||
func RegisterMapper(mapper Mapper) {
|
||||
if err := globalRegistry.register(mapper); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
func (r *registry) register(mapper Mapper) error {
|
||||
if mapper == nil {
|
||||
return fmt.Errorf("document type mapper is nil")
|
||||
}
|
||||
|
||||
name := normalizeName(mapper.Name())
|
||||
if name == "" {
|
||||
return fmt.Errorf("document type mapper name is empty")
|
||||
}
|
||||
|
||||
dtoType := mapper.DTOType()
|
||||
modelType := mapper.ModelType()
|
||||
protoType := mapper.ProtoType()
|
||||
if dtoType == "" || modelType == "" || protoType == pb.DocumentType_DOCUMENT_TYPE_UNSPECIFIED {
|
||||
return fmt.Errorf("document type mapper %q has invalid mapping", name)
|
||||
}
|
||||
|
||||
if _, exists := r.byName[name]; exists {
|
||||
return fmt.Errorf("document type mapper %q already registered", name)
|
||||
}
|
||||
if _, exists := r.byProto[protoType]; exists {
|
||||
return fmt.Errorf("document type proto mapping %v already registered", protoType)
|
||||
}
|
||||
if _, exists := r.byDTO[dtoType]; exists {
|
||||
return fmt.Errorf("document type dto mapping %q already registered", dtoType)
|
||||
}
|
||||
if _, exists := r.byModel[modelType]; exists {
|
||||
return fmt.Errorf("document type model mapping %q already registered", modelType)
|
||||
}
|
||||
|
||||
r.byName[name] = mapper
|
||||
r.byProto[protoType] = mapper
|
||||
r.byDTO[dtoType] = mapper
|
||||
r.byModel[modelType] = mapper
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *registry) findByName(name string) Mapper {
|
||||
return r.byName[normalizeName(name)]
|
||||
}
|
||||
|
||||
func (r *registry) findByProto(t pb.DocumentType) Mapper {
|
||||
return r.byProto[t]
|
||||
}
|
||||
|
||||
func (r *registry) findByDTO(t dto.DocumentType) Mapper {
|
||||
if mapper, ok := r.byDTO[t]; ok {
|
||||
return mapper
|
||||
}
|
||||
return r.byName[normalizeName(string(t))]
|
||||
}
|
||||
|
||||
func (r *registry) findByModel(t model.DocumentType) Mapper {
|
||||
if mapper, ok := r.byModel[t]; ok {
|
||||
return mapper
|
||||
}
|
||||
return r.byName[normalizeName(string(t))]
|
||||
}
|
||||
|
||||
func (r *registry) defaultMapper() Mapper {
|
||||
if mapper := r.findByName(defaultTypeName); mapper != nil {
|
||||
return mapper
|
||||
}
|
||||
for _, mapper := range r.byName {
|
||||
return mapper
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func normalizeName(name string) string {
|
||||
normalized := strings.ToLower(strings.TrimSpace(name))
|
||||
switch normalized {
|
||||
case "slide":
|
||||
return "markdown_slide"
|
||||
case "yoreseerichtext", "yoresee-rich-text":
|
||||
return "yoresee_rich_text"
|
||||
default:
|
||||
return normalized
|
||||
}
|
||||
}
|
||||
|
||||
func DefaultDTOType() dto.DocumentType {
|
||||
mapper := globalRegistry.defaultMapper()
|
||||
if mapper == nil {
|
||||
return dto.DocumentType(defaultTypeName)
|
||||
}
|
||||
return mapper.DTOType()
|
||||
}
|
||||
|
||||
func IsSupportedDTOType(t dto.DocumentType) bool {
|
||||
return globalRegistry.findByDTO(t) != nil
|
||||
}
|
||||
|
||||
func FromProtoType(t pb.DocumentType) dto.DocumentType {
|
||||
if mapper := globalRegistry.findByProto(t); mapper != nil {
|
||||
return mapper.DTOType()
|
||||
}
|
||||
return DefaultDTOType()
|
||||
}
|
||||
|
||||
func ToProtoType(t dto.DocumentType) pb.DocumentType {
|
||||
if mapper := globalRegistry.findByDTO(t); mapper != nil {
|
||||
return mapper.ProtoType()
|
||||
}
|
||||
return pb.DocumentType_DOCUMENT_TYPE_UNSPECIFIED
|
||||
}
|
||||
|
||||
func ToModelType(t dto.DocumentType) model.DocumentType {
|
||||
if mapper := globalRegistry.findByDTO(t); mapper != nil {
|
||||
return mapper.ModelType()
|
||||
}
|
||||
return model.DocumentType(DefaultDTOType())
|
||||
}
|
||||
|
||||
func FromModelType(t model.DocumentType) dto.DocumentType {
|
||||
if mapper := globalRegistry.findByModel(t); mapper != nil {
|
||||
return mapper.DTOType()
|
||||
}
|
||||
normalized := normalizeName(string(t))
|
||||
if normalized != "" {
|
||||
return dto.DocumentType(normalized)
|
||||
}
|
||||
return DefaultDTOType()
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
package doc_type_mapper
|
||||
|
||||
import (
|
||||
"github.com/XingfenD/yoresee_doc/internal/dto"
|
||||
"github.com/XingfenD/yoresee_doc/internal/model"
|
||||
pb "github.com/XingfenD/yoresee_doc/pkg/gen/yoresee_doc/v1"
|
||||
)
|
||||
|
||||
type tableAdapter struct{}
|
||||
|
||||
func (tableAdapter) Name() string {
|
||||
return string(dto.DocumentType_Table)
|
||||
}
|
||||
|
||||
func (tableAdapter) ProtoType() pb.DocumentType {
|
||||
return pb.DocumentType_DOCUMENT_TYPE_TABLE
|
||||
}
|
||||
|
||||
func (tableAdapter) DTOType() dto.DocumentType {
|
||||
return dto.DocumentType_Table
|
||||
}
|
||||
|
||||
func (tableAdapter) ModelType() model.DocumentType {
|
||||
return model.DocumentType_Table
|
||||
}
|
||||
|
||||
func init() { RegisterMapper(tableAdapter{}) }
|
||||
@@ -0,0 +1,27 @@
|
||||
package doc_type_mapper
|
||||
|
||||
import (
|
||||
"github.com/XingfenD/yoresee_doc/internal/dto"
|
||||
"github.com/XingfenD/yoresee_doc/internal/model"
|
||||
pb "github.com/XingfenD/yoresee_doc/pkg/gen/yoresee_doc/v1"
|
||||
)
|
||||
|
||||
type yoreseeRichTextAdapter struct{}
|
||||
|
||||
func (yoreseeRichTextAdapter) Name() string {
|
||||
return string(dto.DocumentType_YoreseeRichText)
|
||||
}
|
||||
|
||||
func (yoreseeRichTextAdapter) ProtoType() pb.DocumentType {
|
||||
return pb.DocumentType_DOCUMENT_TYPE_YORESEE_RICH_TEXT
|
||||
}
|
||||
|
||||
func (yoreseeRichTextAdapter) DTOType() dto.DocumentType {
|
||||
return dto.DocumentType_YoreseeRichText
|
||||
}
|
||||
|
||||
func (yoreseeRichTextAdapter) ModelType() model.DocumentType {
|
||||
return model.DocumentType_YoreseeRichText
|
||||
}
|
||||
|
||||
func init() { RegisterMapper(yoreseeRichTextAdapter{}) }
|
||||
@@ -0,0 +1,34 @@
|
||||
package template_container_mapper
|
||||
|
||||
import (
|
||||
"github.com/XingfenD/yoresee_doc/internal/dto"
|
||||
pb "github.com/XingfenD/yoresee_doc/pkg/gen/yoresee_doc/v1"
|
||||
)
|
||||
|
||||
type knowledgeBaseAdapter struct{}
|
||||
|
||||
func (knowledgeBaseAdapter) Name() string {
|
||||
return "knowledge_base"
|
||||
}
|
||||
|
||||
func (knowledgeBaseAdapter) ProtoType() pb.CreateTemplateContainer {
|
||||
return pb.CreateTemplateContainer_KNOWLEDGEBASE_TEMPLATE
|
||||
}
|
||||
|
||||
func (knowledgeBaseAdapter) DTOType() dto.TemplateContainer {
|
||||
return dto.TemplateContainerKnowledgeBase
|
||||
}
|
||||
|
||||
func (knowledgeBaseAdapter) Scope() string {
|
||||
return "knowledge_base"
|
||||
}
|
||||
|
||||
func (knowledgeBaseAdapter) IsPublic() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (knowledgeBaseAdapter) RequiresKnowledgeBaseID() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func init() { RegisterMapper(knowledgeBaseAdapter{}) }
|
||||
@@ -0,0 +1,34 @@
|
||||
package template_container_mapper
|
||||
|
||||
import (
|
||||
"github.com/XingfenD/yoresee_doc/internal/dto"
|
||||
pb "github.com/XingfenD/yoresee_doc/pkg/gen/yoresee_doc/v1"
|
||||
)
|
||||
|
||||
type ownAdapter struct{}
|
||||
|
||||
func (ownAdapter) Name() string {
|
||||
return "own"
|
||||
}
|
||||
|
||||
func (ownAdapter) ProtoType() pb.CreateTemplateContainer {
|
||||
return pb.CreateTemplateContainer_OWN_TEMPLATE
|
||||
}
|
||||
|
||||
func (ownAdapter) DTOType() dto.TemplateContainer {
|
||||
return dto.TemplateContainerOwn
|
||||
}
|
||||
|
||||
func (ownAdapter) Scope() string {
|
||||
return "private"
|
||||
}
|
||||
|
||||
func (ownAdapter) IsPublic() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (ownAdapter) RequiresKnowledgeBaseID() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func init() { RegisterMapper(ownAdapter{}) }
|
||||
@@ -0,0 +1,34 @@
|
||||
package template_container_mapper
|
||||
|
||||
import (
|
||||
"github.com/XingfenD/yoresee_doc/internal/dto"
|
||||
pb "github.com/XingfenD/yoresee_doc/pkg/gen/yoresee_doc/v1"
|
||||
)
|
||||
|
||||
type publicAdapter struct{}
|
||||
|
||||
func (publicAdapter) Name() string {
|
||||
return "public"
|
||||
}
|
||||
|
||||
func (publicAdapter) ProtoType() pb.CreateTemplateContainer {
|
||||
return pb.CreateTemplateContainer_PUBLIC_TEMPLATE
|
||||
}
|
||||
|
||||
func (publicAdapter) DTOType() dto.TemplateContainer {
|
||||
return dto.TemplateContainerPublic
|
||||
}
|
||||
|
||||
func (publicAdapter) Scope() string {
|
||||
return "system"
|
||||
}
|
||||
|
||||
func (publicAdapter) IsPublic() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func (publicAdapter) RequiresKnowledgeBaseID() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func init() { RegisterMapper(publicAdapter{}) }
|
||||
@@ -0,0 +1,151 @@
|
||||
package template_container_mapper
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/XingfenD/yoresee_doc/internal/dto"
|
||||
pb "github.com/XingfenD/yoresee_doc/pkg/gen/yoresee_doc/v1"
|
||||
)
|
||||
|
||||
const defaultContainerName = "own"
|
||||
|
||||
type Mapper interface {
|
||||
Name() string
|
||||
ProtoType() pb.CreateTemplateContainer
|
||||
DTOType() dto.TemplateContainer
|
||||
Scope() string
|
||||
IsPublic() bool
|
||||
RequiresKnowledgeBaseID() bool
|
||||
}
|
||||
|
||||
type registry struct {
|
||||
byName map[string]Mapper
|
||||
byProto map[pb.CreateTemplateContainer]Mapper
|
||||
byDTO map[dto.TemplateContainer]Mapper
|
||||
}
|
||||
|
||||
var globalRegistry = ®istry{
|
||||
byName: make(map[string]Mapper),
|
||||
byProto: make(map[pb.CreateTemplateContainer]Mapper),
|
||||
byDTO: make(map[dto.TemplateContainer]Mapper),
|
||||
}
|
||||
|
||||
func RegisterMapper(mapper Mapper) {
|
||||
if err := globalRegistry.register(mapper); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
func (r *registry) register(mapper Mapper) error {
|
||||
if mapper == nil {
|
||||
return fmt.Errorf("template container mapper is nil")
|
||||
}
|
||||
name := normalizeName(mapper.Name())
|
||||
if name == "" {
|
||||
return fmt.Errorf("template container mapper name is empty")
|
||||
}
|
||||
|
||||
protoType := mapper.ProtoType()
|
||||
dtoType := mapper.DTOType()
|
||||
if dtoType == "" {
|
||||
return fmt.Errorf("template container mapper %q has invalid dto mapping", name)
|
||||
}
|
||||
if strings.TrimSpace(mapper.Scope()) == "" {
|
||||
return fmt.Errorf("template container mapper %q has empty scope mapping", name)
|
||||
}
|
||||
|
||||
if _, exists := r.byName[name]; exists {
|
||||
return fmt.Errorf("template container mapper %q already registered", name)
|
||||
}
|
||||
if _, exists := r.byProto[protoType]; exists {
|
||||
return fmt.Errorf("template container proto mapping %v already registered", protoType)
|
||||
}
|
||||
if _, exists := r.byDTO[dtoType]; exists {
|
||||
return fmt.Errorf("template container dto mapping %q already registered", dtoType)
|
||||
}
|
||||
|
||||
r.byName[name] = mapper
|
||||
r.byProto[protoType] = mapper
|
||||
r.byDTO[dtoType] = mapper
|
||||
return nil
|
||||
}
|
||||
|
||||
func normalizeName(name string) string {
|
||||
return strings.ToLower(strings.TrimSpace(name))
|
||||
}
|
||||
|
||||
func (r *registry) findByName(name string) Mapper {
|
||||
return r.byName[normalizeName(name)]
|
||||
}
|
||||
|
||||
func (r *registry) findByProto(t pb.CreateTemplateContainer) Mapper {
|
||||
return r.byProto[t]
|
||||
}
|
||||
|
||||
func (r *registry) findByDTO(t dto.TemplateContainer) Mapper {
|
||||
return r.byDTO[t]
|
||||
}
|
||||
|
||||
func (r *registry) defaultMapper() Mapper {
|
||||
if mapper := r.findByName(defaultContainerName); mapper != nil {
|
||||
return mapper
|
||||
}
|
||||
for _, mapper := range r.byName {
|
||||
return mapper
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func DefaultDTOType() dto.TemplateContainer {
|
||||
mapper := globalRegistry.defaultMapper()
|
||||
if mapper == nil {
|
||||
return dto.TemplateContainerOwn
|
||||
}
|
||||
return mapper.DTOType()
|
||||
}
|
||||
|
||||
func IsSupportedDTOType(t dto.TemplateContainer) bool {
|
||||
return globalRegistry.findByDTO(t) != nil
|
||||
}
|
||||
|
||||
func FromProtoType(t pb.CreateTemplateContainer) dto.TemplateContainer {
|
||||
if mapper := globalRegistry.findByProto(t); mapper != nil {
|
||||
return mapper.DTOType()
|
||||
}
|
||||
return DefaultDTOType()
|
||||
}
|
||||
|
||||
func ToProtoType(t dto.TemplateContainer) pb.CreateTemplateContainer {
|
||||
if mapper := globalRegistry.findByDTO(t); mapper != nil {
|
||||
return mapper.ProtoType()
|
||||
}
|
||||
return pb.CreateTemplateContainer_OWN_TEMPLATE
|
||||
}
|
||||
|
||||
func ToScope(t dto.TemplateContainer) string {
|
||||
if mapper := globalRegistry.findByDTO(t); mapper != nil {
|
||||
return mapper.Scope()
|
||||
}
|
||||
if mapper := globalRegistry.defaultMapper(); mapper != nil {
|
||||
return mapper.Scope()
|
||||
}
|
||||
return "private"
|
||||
}
|
||||
|
||||
func ToIsPublic(t dto.TemplateContainer) bool {
|
||||
if mapper := globalRegistry.findByDTO(t); mapper != nil {
|
||||
return mapper.IsPublic()
|
||||
}
|
||||
if mapper := globalRegistry.defaultMapper(); mapper != nil {
|
||||
return mapper.IsPublic()
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func RequiresKnowledgeBaseID(t dto.TemplateContainer) bool {
|
||||
if mapper := globalRegistry.findByDTO(t); mapper != nil {
|
||||
return mapper.RequiresKnowledgeBaseID()
|
||||
}
|
||||
return false
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
package media
|
||||
|
||||
import (
|
||||
"strings"
|
||||
)
|
||||
|
||||
const (
|
||||
MaxAvatarSize = 5 * 1024 * 1024
|
||||
defaultAvatarExtension = ".jpg"
|
||||
)
|
||||
|
||||
var avatarContentTypes = map[string]string{
|
||||
"image/jpeg": ".jpg",
|
||||
"image/png": ".png",
|
||||
"image/webp": ".webp",
|
||||
}
|
||||
|
||||
func NormalizeAvatarContentType(file []byte, provided string) string {
|
||||
return NormalizeContentTypeByAllowed(file, provided, avatarContentTypes)
|
||||
}
|
||||
|
||||
func IsSupportedAvatarContentType(contentType string) bool {
|
||||
return IsContentTypeAllowed(contentType, avatarContentTypes)
|
||||
}
|
||||
|
||||
func ResolveAvatarExt(filename, contentType string) string {
|
||||
return ResolveExtension(filename, contentType, avatarContentTypes, defaultAvatarExtension)
|
||||
}
|
||||
|
||||
func BuildAvatarObjectKey(userExternalID string, avatarVersion int64, ext string) string {
|
||||
return BuildVersionedObjectKey("avatars", userExternalID, avatarVersion, ext)
|
||||
}
|
||||
|
||||
func BuildAvatarURL(userExternalID, objectKey string, avatarVersion int64) string {
|
||||
if strings.TrimSpace(userExternalID) == "" || strings.TrimSpace(objectKey) == "" {
|
||||
return ""
|
||||
}
|
||||
return BuildStorageURL(objectKey)
|
||||
}
|
||||
@@ -0,0 +1,84 @@
|
||||
package media
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"mime"
|
||||
"net/http"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/XingfenD/yoresee_doc/pkg/storage"
|
||||
)
|
||||
|
||||
func normalizeMIMEType(contentType string) string {
|
||||
normalized := strings.ToLower(strings.TrimSpace(contentType))
|
||||
if idx := strings.Index(normalized, ";"); idx > 0 {
|
||||
normalized = strings.TrimSpace(normalized[:idx])
|
||||
}
|
||||
if normalized == "image/jpg" {
|
||||
return "image/jpeg"
|
||||
}
|
||||
return normalized
|
||||
}
|
||||
|
||||
func NormalizeContentTypeByAllowed(file []byte, provided string, allowed map[string]string) string {
|
||||
normalized := normalizeMIMEType(provided)
|
||||
if _, ok := allowed[normalized]; ok {
|
||||
return normalized
|
||||
}
|
||||
|
||||
detected := normalizeMIMEType(http.DetectContentType(file))
|
||||
if _, ok := allowed[detected]; ok {
|
||||
return detected
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func IsContentTypeAllowed(contentType string, allowed map[string]string) bool {
|
||||
_, ok := allowed[normalizeMIMEType(contentType)]
|
||||
return ok
|
||||
}
|
||||
|
||||
func ResolveExtension(filename, contentType string, allowed map[string]string, defaultExt string) string {
|
||||
ext := strings.TrimSpace(strings.ToLower(filepath.Ext(filename)))
|
||||
if ext != "" {
|
||||
for _, candidate := range allowed {
|
||||
if ext == candidate {
|
||||
return ext
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
contentType = normalizeMIMEType(contentType)
|
||||
if mapped, ok := allowed[contentType]; ok {
|
||||
return mapped
|
||||
}
|
||||
|
||||
exts, err := mime.ExtensionsByType(contentType)
|
||||
if err == nil && len(exts) > 0 {
|
||||
for _, candidate := range exts {
|
||||
candidate = strings.TrimSpace(strings.ToLower(candidate))
|
||||
for _, allowedExt := range allowed {
|
||||
if candidate == allowedExt {
|
||||
return candidate
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return strings.TrimSpace(strings.ToLower(defaultExt))
|
||||
}
|
||||
|
||||
func BuildVersionedObjectKey(prefix, ownerExternalID string, version int64, ext string) string {
|
||||
prefix = strings.Trim(strings.TrimSpace(prefix), "/")
|
||||
if prefix == "" {
|
||||
return fmt.Sprintf("%s/v%d%s", ownerExternalID, version, ext)
|
||||
}
|
||||
return fmt.Sprintf("%s/%s/v%d%s", prefix, ownerExternalID, version, ext)
|
||||
}
|
||||
|
||||
func BuildStorageURL(objectKey string) string {
|
||||
if strings.TrimSpace(objectKey) == "" {
|
||||
return ""
|
||||
}
|
||||
return storage.BuildPublicObjectPath(objectKey)
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/XingfenD/yoresee_doc/internal/auth"
|
||||
"github.com/XingfenD/yoresee_doc/internal/status"
|
||||
)
|
||||
|
||||
var JWTAuth = &JWTAuthMiddleware{}
|
||||
|
||||
type JWTAuthMiddleware struct {
|
||||
}
|
||||
|
||||
func (m *JWTAuthMiddleware) ValidateAuthorizationHeader(authHeader string) (*auth.Claims, error) {
|
||||
return m.handle(authHeader)
|
||||
}
|
||||
|
||||
func (m *JWTAuthMiddleware) handle(authHeader string) (*auth.Claims, error) {
|
||||
if authHeader == "" {
|
||||
return nil, status.GenErrWithCustomMsg(status.StatusTokenInvalid, "unlogin or illegal access")
|
||||
}
|
||||
|
||||
parts := strings.SplitN(authHeader, " ", 2)
|
||||
if !(len(parts) == 2 && parts[0] == "Bearer") {
|
||||
return nil, status.GenErrWithCustomMsg(status.StatusTokenInvalid, "invalid token format")
|
||||
}
|
||||
token := parts[1]
|
||||
claims, err := auth.ParseToken(token)
|
||||
if err != nil {
|
||||
return nil, status.StatusTokenInvalid
|
||||
}
|
||||
|
||||
if (&auth.JWTValidator{}).IsExpired(claims) {
|
||||
return nil, status.StatusTokenExpired
|
||||
}
|
||||
if err := auth.ValidateJWTTokenInRedis(claims.ExternalID, token); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return claims, nil
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
package model
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
type Attachment struct {
|
||||
ID int64 `gorm:"primaryKey;autoIncrement" json:"id"`
|
||||
ExternalID string `gorm:"size:100;unique;not null" json:"external_id"`
|
||||
DocumentID int64 `gorm:"not null;index" json:"document_id"`
|
||||
Name string `gorm:"size:255;not null" json:"name"`
|
||||
Path string `gorm:"size:512;not null" json:"path"`
|
||||
Size int64 `json:"size"`
|
||||
MimeType string `gorm:"size:100" json:"mime_type"`
|
||||
UserID int64 `gorm:"not null" json:"user_id"`
|
||||
PresignedURL string `gorm:"-" json:"presigned_url"` // 预签名URL,不存储到数据库
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
DeletedAt gorm.DeletedAt `gorm:"index" json:"-"`
|
||||
}
|
||||
|
||||
func (Attachment) TableName() string {
|
||||
return "attachments"
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
package model
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
type DocumentComment struct {
|
||||
ID int64 `gorm:"primaryKey;autoIncrement" json:"id"`
|
||||
ExternalID string `gorm:"size:100;unique;index;not null" json:"external_id"`
|
||||
DocumentID int64 `gorm:"index;not null" json:"document_id"`
|
||||
ParentID int64 `gorm:"default:0;index" json:"parent_id"`
|
||||
CreatorID int64 `gorm:"index;not null" json:"creator_id"`
|
||||
Content string `gorm:"type:text;not null" json:"content"`
|
||||
AnchorID string `gorm:"size:128;index" json:"anchor_id"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
DeletedAt gorm.DeletedAt `gorm:"index" json:"-"`
|
||||
}
|
||||
|
||||
func (DocumentComment) TableName() string {
|
||||
return "document_comments"
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
package model
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
type DocumentType string
|
||||
|
||||
const DocumentType_Markdown DocumentType = "markdown"
|
||||
const DocumentType_Table DocumentType = "table"
|
||||
const DocumentType_MarkdownSlide DocumentType = "markdown_slide"
|
||||
const DocumentType_YoreseeRichText DocumentType = "yoresee_rich_text"
|
||||
|
||||
type ContainerType string
|
||||
|
||||
const ContainerType_Own ContainerType = "own"
|
||||
const ContainerType_KnowledgeBase ContainerType = "knowledge_base"
|
||||
|
||||
type Document struct {
|
||||
ID int64 `gorm:"primaryKey;autoIncrement" json:"id"`
|
||||
ExternalID string `gorm:"size:100;unique;index;not null" json:"external_id"`
|
||||
Title string `gorm:"size:200;not null" json:"title"`
|
||||
Type DocumentType `gorm:"size:20;default:'markdown'" json:"type"`
|
||||
Summary string `gorm:"type:text" json:"summary"`
|
||||
ParentID int64 `gorm:"default:0;index" json:"parent_id"` // 0 means root
|
||||
UserID int64 `gorm:"not null;index" json:"user_id"`
|
||||
KnowledgeID *int64 `gorm:"index" json:"knowledge_id"`
|
||||
ContainerType ContainerType `gorm:"not null;default:'own';index" json:"containter_type"`
|
||||
IsPublic bool `gorm:"default:false;index" json:"is_public"`
|
||||
Tags []string `gorm:"serializer:json" json:"tags"`
|
||||
Path string `gorm:"type:ltree;not null;default:'';index:idx_path_gist,using:gist"`
|
||||
Depth int `gorm:"not null;default:0;index"`
|
||||
ViewCount int `gorm:"default:0" json:"view_count"`
|
||||
EditCount int `gorm:"default:0" json:"edit_count"`
|
||||
Content string `gorm:"type:text" json:"content"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
DeletedAt gorm.DeletedAt `gorm:"index" json:"-"`
|
||||
}
|
||||
|
||||
func (Document) TableName() string {
|
||||
return "document_metas"
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
package model
|
||||
|
||||
import "time"
|
||||
|
||||
type DocumentVersion struct {
|
||||
ID int64 `gorm:"primaryKey;autoIncrement" json:"id"`
|
||||
DocumentID int64 `gorm:"not null;index" json:"document_id"`
|
||||
Version int `gorm:"not null" json:"version"`
|
||||
Title string `gorm:"size:200;not null" json:"title"`
|
||||
Content string `gorm:"type:text" json:"content"`
|
||||
UserID int64 `gorm:"not null;index" json:"user_id"`
|
||||
ChangeSummary string `gorm:"type:text" json:"change_summary"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
}
|
||||
|
||||
func (DocumentVersion) TableName() string {
|
||||
return "document_versions"
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
package model
|
||||
|
||||
import "time"
|
||||
|
||||
type DocumentYjsSnapshot struct {
|
||||
DocID int64 `gorm:"primaryKey;column:doc_id" json:"doc_id"`
|
||||
YjsState []byte `gorm:"type:bytea;not null" json:"yjs_state"`
|
||||
Version int64 `gorm:"not null" json:"version"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
}
|
||||
|
||||
func (DocumentYjsSnapshot) TableName() string {
|
||||
return "documents_yjs_snapshot"
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
package model
|
||||
|
||||
import (
|
||||
"time"
|
||||
)
|
||||
|
||||
type Invitation struct {
|
||||
ID int64 `gorm:"primaryKey;autoIncrement" json:"id"`
|
||||
Code string `gorm:"size:32;unique;not null" json:"code"`
|
||||
CreatedBy int64 `gorm:"not null" json:"created_by"`
|
||||
UsedCnt int64 `gorm:"default:0" json:"used_cnt"`
|
||||
MaxUsedCnt *int64 `gorm:"default:1" json:"max_used_cnt"`
|
||||
ExpiresAt *time.Time `json:"expires_at"`
|
||||
Note *string `gorm:"type:text" json:"note"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
DeletedAt *time.Time `gorm:"index" json:"deleted_at"`
|
||||
Disabled bool `gorm:"index,default:false" json:"disabled_at"`
|
||||
}
|
||||
|
||||
func (Invitation) TableName() string {
|
||||
return "invitations"
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
package model
|
||||
|
||||
import "time"
|
||||
|
||||
type InvitationRecord struct {
|
||||
ID int64 `gorm:"primaryKey;autoIncrement" json:"id"`
|
||||
Code string `gorm:"size:32;index;not null" json:"code"`
|
||||
UsedByUserID *int64 `gorm:"index" json:"used_by_user_id"`
|
||||
UsedBy string `gorm:"size:100" json:"used_by"`
|
||||
Status string `gorm:"size:20;index" json:"status"`
|
||||
UsedAt time.Time `json:"used_at"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
}
|
||||
|
||||
func (InvitationRecord) TableName() string {
|
||||
return "invitation_records"
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
package model
|
||||
|
||||
import (
|
||||
"time"
|
||||
)
|
||||
|
||||
type KnowledgeBase struct {
|
||||
ID int64 `gorm:"primaryKey;autoIncrement" json:"id"`
|
||||
ExternalID string `gorm:"size:100;unique;not null;index" json:"external_id"`
|
||||
Name string `gorm:"size:100;not null" json:"name"`
|
||||
Description string `gorm:"size:255" json:"description"`
|
||||
Cover string `gorm:"size:255" json:"cover"`
|
||||
CreatorUserID int64 `gorm:"not null" json:"creator_user_id"` // creator user id
|
||||
IsPublic bool `gorm:"default:false" json:"is_public"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
DeletedAt time.Time `gorm:"index" json:"deleted_at"`
|
||||
}
|
||||
|
||||
type RecentKnowledgeBase struct {
|
||||
ID int64 `gorm:"primaryKey;autoIncrement" json:"id"`
|
||||
UserID int64 `gorm:"not null;index" json:"user_id"`
|
||||
KnowledgeBaseID int64 `gorm:"not null;index" json:"knowledge_base_id"`
|
||||
AccessedAt time.Time `gorm:"not null" json:"accessed_at"`
|
||||
}
|
||||
|
||||
func (RecentKnowledgeBase) TableName() string {
|
||||
return "recent_knowledge_bases"
|
||||
}
|
||||
|
||||
func (KnowledgeBase) TableName() string {
|
||||
return "knowledge_bases"
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
package model
|
||||
|
||||
type MembershipType int64
|
||||
|
||||
const (
|
||||
MembershipType_UserGroup MembershipType = 1 // equals to SubjectTypeUserGroup
|
||||
MembershipType_OrgNode MembershipType = 2 // equals to SubjectTypeOrgNode
|
||||
)
|
||||
|
||||
type MembershipRelation struct {
|
||||
ID int64 `gorm:"primaryKey;autoIncrement"`
|
||||
Type MembershipType `gorm:"not null;index" json:"type"` // 1: UserGroup, 2: OrgNode
|
||||
UserID int64 `gorm:"not null;index" json:"user_id"`
|
||||
MembershipID int64 `gorm:"not null;index" json:"membership_id"` // user_group_id or org_node_id
|
||||
|
||||
// virtual field
|
||||
User User `gorm:"foreignKey:UserID;references:ID"`
|
||||
}
|
||||
|
||||
// query all user belong to membership with type and member_id
|
||||
|
||||
func (MembershipRelation) TableName() string {
|
||||
return "membership"
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
package model
|
||||
|
||||
import (
|
||||
"time"
|
||||
)
|
||||
|
||||
type Notification struct {
|
||||
ID int64 `gorm:"primaryKey;autoIncrement" json:"id"`
|
||||
ExternalID string `gorm:"size:100;unique;index" json:"external_id"`
|
||||
ReceiverID int64 `gorm:"index;not null" json:"receiver_id"`
|
||||
Type string `gorm:"size:64;not null" json:"type"`
|
||||
Status string `gorm:"size:32;not null;default:unread" json:"status"`
|
||||
Title string `gorm:"size:255" json:"title"`
|
||||
Content string `gorm:"size:1024" json:"content"`
|
||||
Payload string `gorm:"type:jsonb" json:"payload"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
}
|
||||
|
||||
func (Notification) TableName() string {
|
||||
return "notifications"
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
package model
|
||||
|
||||
type OrgNodeMeta struct {
|
||||
ID int64 `gorm:"primaryKey;autoIncrement"` // org_node_id
|
||||
ExternalID string `gorm:"not null;index;unique" json:"external_id"` // external org_node_id
|
||||
ParentID int64 `gorm:"not null;index" json:"parent_id"` // org_node_id, root node is 0
|
||||
Name string `gorm:"not null;index" json:"name"`
|
||||
Path string `gorm:"type:ltree;not null;index" json:"path"` // format: n<ID>.n<ID>...
|
||||
Description string `gorm:"type:text;index" json:"description"`
|
||||
CreatorID int64 `gorm:"not null;index" json:"creator_id"` // user_id
|
||||
}
|
||||
|
||||
func (OrgNodeMeta) TableName() string {
|
||||
return "org_node_meta"
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
package model
|
||||
|
||||
import "time"
|
||||
|
||||
type RecentDocument struct {
|
||||
ID int64 `gorm:"primaryKey;autoIncrement" json:"id"`
|
||||
UserID int64 `gorm:"not null;index" json:"user_id"`
|
||||
DocumentID int64 `gorm:"not null;index" json:"document_id"`
|
||||
AccessedAt time.Time `gorm:"not null" json:"accessed_at"`
|
||||
}
|
||||
|
||||
func (RecentDocument) TableName() string {
|
||||
return "recent_documents"
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
package model
|
||||
|
||||
import "time"
|
||||
|
||||
type RecentTemplate struct {
|
||||
ID int64 `gorm:"primaryKey;autoIncrement" json:"id"`
|
||||
UserID int64 `gorm:"not null;index" json:"user_id"`
|
||||
TemplateID int64 `gorm:"not null;index" json:"template_id"`
|
||||
AccessedAt time.Time `gorm:"not null" json:"accessed_at"`
|
||||
}
|
||||
|
||||
func (RecentTemplate) TableName() string {
|
||||
return "recent_templates"
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
package model
|
||||
|
||||
import (
|
||||
"time"
|
||||
)
|
||||
|
||||
type Template struct {
|
||||
ID int64 `gorm:"primaryKey;autoIncrement" json:"id"`
|
||||
Name string `gorm:"size:100;not null" json:"name"`
|
||||
Description string `gorm:"type:text" json:"description"`
|
||||
DocumentType DocumentType `gorm:"column:document_type;size:20;default:'markdown'" json:"document_type"`
|
||||
Content string `gorm:"type:text" json:"content"`
|
||||
UserID int64 `gorm:"not null" json:"user_id"`
|
||||
Scope string `gorm:"size:20;default:'private'" json:"scope"` // private, system, knowledge_base
|
||||
KnowledgeBaseID *int64 `json:"knowledge_base_id"`
|
||||
IsPublic bool `gorm:"default:false" json:"is_public"` // Deprecated, keep for migration
|
||||
Tags []string `gorm:"serializer:json" json:"tags"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
}
|
||||
|
||||
func (Template) TableName() string {
|
||||
return "templates"
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
package model
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
type User struct {
|
||||
ID int64 `gorm:"primaryKey;autoIncrement" json:"id"`
|
||||
ExternalID string `gorm:"size:100;unique" json:"external_id"`
|
||||
Username string `gorm:"size:50;not null" json:"username"`
|
||||
Email string `gorm:"size:100;unique;not null" json:"email"`
|
||||
PasswordHash string `gorm:"size:255;not null" json:"-"`
|
||||
Nickname string `gorm:"size:50" json:"nickname"`
|
||||
Avatar string `gorm:"size:255" json:"avatar"` // deprecated
|
||||
AvatarObjectKey string `gorm:"size:255" json:"avatar_object_key"`
|
||||
AvatarVersion int64 `gorm:"not null;default:0" json:"avatar_version"`
|
||||
AvatarUpdatedAt *time.Time `json:"avatar_updated_at"`
|
||||
Status int `gorm:"default:1" json:"status"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
DeletedAt gorm.DeletedAt `gorm:"index" json:"-"`
|
||||
InvitationCode *string `gorm:"size:32" json:"invitation_code"`
|
||||
}
|
||||
|
||||
func (User) TableName() string {
|
||||
return "users"
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
package model
|
||||
|
||||
type UserGroupMeta struct {
|
||||
ID int64 `gorm:"primaryKey;autoIncrement"` // user_group_id
|
||||
ExternalID string `gorm:"not null;index;unique" json:"external_id"` // external user_group_id
|
||||
Name string `gorm:"not null;index" json:"name"`
|
||||
CreatorID int64 `gorm:"not null;index" json:"creator_id"` // user_id
|
||||
Description string `gorm:"type:text;index" json:"description"`
|
||||
}
|
||||
|
||||
func (UserGroupMeta) TableName() string {
|
||||
return "user_group_meta"
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
package attachment_repo
|
||||
|
||||
import (
|
||||
"github.com/XingfenD/yoresee_doc/internal/model"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
type AttachmentRepository struct {
|
||||
db *gorm.DB
|
||||
}
|
||||
|
||||
func NewAttachmentRepository(db *gorm.DB) *AttachmentRepository {
|
||||
return &AttachmentRepository{db: db}
|
||||
}
|
||||
|
||||
func (r *AttachmentRepository) Create(attachment *model.Attachment) error {
|
||||
return r.db.Create(attachment).Error
|
||||
}
|
||||
|
||||
func (r *AttachmentRepository) ListByDocumentID(documentID int64) ([]*model.Attachment, error) {
|
||||
attachments := make([]*model.Attachment, 0)
|
||||
if err := r.db.
|
||||
Where("document_id = ?", documentID).
|
||||
Order("created_at desc").
|
||||
Find(&attachments).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return attachments, nil
|
||||
}
|
||||
|
||||
func (r *AttachmentRepository) GetByExternalIDAndDocumentID(externalID string, documentID int64) (*model.Attachment, error) {
|
||||
attachment := &model.Attachment{}
|
||||
if err := r.db.
|
||||
Where("external_id = ? AND document_id = ?", externalID, documentID).
|
||||
First(attachment).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return attachment, nil
|
||||
}
|
||||
|
||||
func (r *AttachmentRepository) DeleteByID(id int64) error {
|
||||
return r.db.Delete(&model.Attachment{}, id).Error
|
||||
}
|
||||
|
||||
func IsNotFound(err error) bool {
|
||||
return err == gorm.ErrRecordNotFound
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
package comment_repo
|
||||
|
||||
import "gorm.io/gorm"
|
||||
|
||||
type CommentRepository struct {
|
||||
db *gorm.DB
|
||||
}
|
||||
|
||||
func NewCommentRepository(db *gorm.DB) *CommentRepository {
|
||||
return &CommentRepository{db: db}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
package comment_repo
|
||||
|
||||
import (
|
||||
"github.com/XingfenD/yoresee_doc/internal/model"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
type CommentCreateOperation struct {
|
||||
repo *CommentRepository
|
||||
item *model.DocumentComment
|
||||
tx *gorm.DB
|
||||
}
|
||||
|
||||
func (r *CommentRepository) Create(item *model.DocumentComment) *CommentCreateOperation {
|
||||
return &CommentCreateOperation{
|
||||
repo: r,
|
||||
item: item,
|
||||
}
|
||||
}
|
||||
|
||||
func (op *CommentCreateOperation) WithTx(tx *gorm.DB) *CommentCreateOperation {
|
||||
op.tx = tx
|
||||
return op
|
||||
}
|
||||
|
||||
func (op *CommentCreateOperation) Exec() error {
|
||||
if op.item == nil {
|
||||
return nil
|
||||
}
|
||||
db := op.repo.db
|
||||
if op.tx != nil {
|
||||
db = op.tx
|
||||
}
|
||||
return db.Create(op.item).Error
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
package comment_repo
|
||||
|
||||
import (
|
||||
"github.com/XingfenD/yoresee_doc/internal/model"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
type CommentDeleteOperation struct {
|
||||
repo *CommentRepository
|
||||
id int64
|
||||
tx *gorm.DB
|
||||
}
|
||||
|
||||
func (r *CommentRepository) Delete(id int64) *CommentDeleteOperation {
|
||||
return &CommentDeleteOperation{
|
||||
repo: r,
|
||||
id: id,
|
||||
}
|
||||
}
|
||||
|
||||
func (op *CommentDeleteOperation) WithTx(tx *gorm.DB) *CommentDeleteOperation {
|
||||
op.tx = tx
|
||||
return op
|
||||
}
|
||||
|
||||
func (op *CommentDeleteOperation) Exec() error {
|
||||
db := op.repo.db
|
||||
if op.tx != nil {
|
||||
db = op.tx
|
||||
}
|
||||
return db.Delete(&model.DocumentComment{}, op.id).Error
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
package comment_repo
|
||||
|
||||
import (
|
||||
"github.com/XingfenD/yoresee_doc/internal/model"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
type CommentGetByExternalIDOperation struct {
|
||||
repo *CommentRepository
|
||||
externalID string
|
||||
tx *gorm.DB
|
||||
}
|
||||
|
||||
func (r *CommentRepository) GetByExternalID(externalID string) *CommentGetByExternalIDOperation {
|
||||
return &CommentGetByExternalIDOperation{
|
||||
repo: r,
|
||||
externalID: externalID,
|
||||
}
|
||||
}
|
||||
|
||||
func (op *CommentGetByExternalIDOperation) WithTx(tx *gorm.DB) *CommentGetByExternalIDOperation {
|
||||
op.tx = tx
|
||||
return op
|
||||
}
|
||||
|
||||
func (op *CommentGetByExternalIDOperation) Exec() (*model.DocumentComment, error) {
|
||||
var item model.DocumentComment
|
||||
db := op.repo.db
|
||||
if op.tx != nil {
|
||||
db = op.tx
|
||||
}
|
||||
if err := db.First(&item, "external_id = ?", op.externalID).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &item, nil
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
package comment_repo
|
||||
|
||||
import (
|
||||
"github.com/XingfenD/yoresee_doc/internal/model"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
type CommentListByDocumentOperation struct {
|
||||
repo *CommentRepository
|
||||
documentID int64
|
||||
page int
|
||||
pageSize int
|
||||
tx *gorm.DB
|
||||
}
|
||||
|
||||
func (r *CommentRepository) ListByDocument(documentID int64) *CommentListByDocumentOperation {
|
||||
return &CommentListByDocumentOperation{
|
||||
repo: r,
|
||||
documentID: documentID,
|
||||
}
|
||||
}
|
||||
|
||||
func (op *CommentListByDocumentOperation) WithTx(tx *gorm.DB) *CommentListByDocumentOperation {
|
||||
op.tx = tx
|
||||
return op
|
||||
}
|
||||
|
||||
func (op *CommentListByDocumentOperation) WithPagination(page, pageSize int) *CommentListByDocumentOperation {
|
||||
op.page = page
|
||||
op.pageSize = pageSize
|
||||
return op
|
||||
}
|
||||
|
||||
func (op *CommentListByDocumentOperation) ExecWithTotal() ([]model.DocumentComment, int64, error) {
|
||||
db := op.repo.db
|
||||
if op.tx != nil {
|
||||
db = op.tx
|
||||
}
|
||||
|
||||
query := db.Model(&model.DocumentComment{}).
|
||||
Where("document_id = ?", op.documentID).
|
||||
Where("anchor_id IS NOT NULL AND anchor_id <> ''")
|
||||
var total int64
|
||||
if err := query.Count(&total).Error; err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
|
||||
page := op.page
|
||||
pageSize := op.pageSize
|
||||
if page <= 0 {
|
||||
page = 1
|
||||
}
|
||||
if pageSize <= 0 {
|
||||
pageSize = 10
|
||||
}
|
||||
if pageSize > 100 {
|
||||
pageSize = 100
|
||||
}
|
||||
offset := (page - 1) * pageSize
|
||||
|
||||
var items []model.DocumentComment
|
||||
if err := query.Order("created_at DESC").Offset(offset).Limit(pageSize).Find(&items).Error; err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
return items, total, nil
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
package comment_repo
|
||||
|
||||
import (
|
||||
"github.com/XingfenD/yoresee_doc/internal/model"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
type CommentUpdateContentOperation struct {
|
||||
repo *CommentRepository
|
||||
id int64
|
||||
content string
|
||||
tx *gorm.DB
|
||||
}
|
||||
|
||||
func (r *CommentRepository) UpdateContentByID(id int64, content string) *CommentUpdateContentOperation {
|
||||
return &CommentUpdateContentOperation{
|
||||
repo: r,
|
||||
id: id,
|
||||
content: content,
|
||||
}
|
||||
}
|
||||
|
||||
func (op *CommentUpdateContentOperation) WithTx(tx *gorm.DB) *CommentUpdateContentOperation {
|
||||
op.tx = tx
|
||||
return op
|
||||
}
|
||||
|
||||
func (op *CommentUpdateContentOperation) Exec() error {
|
||||
db := op.repo.db
|
||||
if op.tx != nil {
|
||||
db = op.tx
|
||||
}
|
||||
return db.Model(&model.DocumentComment{}).Where("id = ?", op.id).Update("content", op.content).Error
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
package document_repo
|
||||
|
||||
import (
|
||||
"github.com/XingfenD/yoresee_doc/internal/model"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
type DocumentCreateOperation struct {
|
||||
repo *DocumentRepository
|
||||
doc *model.Document
|
||||
tx *gorm.DB
|
||||
}
|
||||
|
||||
func (r *DocumentRepository) Create(doc *model.Document) *DocumentCreateOperation {
|
||||
return &DocumentCreateOperation{
|
||||
repo: r,
|
||||
doc: doc,
|
||||
}
|
||||
}
|
||||
|
||||
func (op *DocumentCreateOperation) WithTx(tx *gorm.DB) *DocumentCreateOperation {
|
||||
op.tx = tx
|
||||
return op
|
||||
}
|
||||
|
||||
func (op *DocumentCreateOperation) Exec() error {
|
||||
if op.tx == nil {
|
||||
op.tx = op.repo.db
|
||||
}
|
||||
|
||||
return op.tx.Create(op.doc).Error
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
package document_repo
|
||||
|
||||
import (
|
||||
"github.com/XingfenD/yoresee_doc/internal/model"
|
||||
"gorm.io/gorm"
|
||||
"gorm.io/gorm/clause"
|
||||
)
|
||||
|
||||
type UpsertRecentDocumentOperation struct {
|
||||
repo *DocumentRepository
|
||||
m *model.RecentDocument
|
||||
tx *gorm.DB
|
||||
}
|
||||
|
||||
func (r *DocumentRepository) UpsertRecentDocument(m *model.RecentDocument) *UpsertRecentDocumentOperation {
|
||||
return &UpsertRecentDocumentOperation{
|
||||
repo: r,
|
||||
m: m,
|
||||
}
|
||||
}
|
||||
|
||||
func (op *UpsertRecentDocumentOperation) WithTx(tx *gorm.DB) *UpsertRecentDocumentOperation {
|
||||
op.tx = tx
|
||||
return op
|
||||
}
|
||||
|
||||
func (op *UpsertRecentDocumentOperation) Exec() error {
|
||||
db := op.repo.db
|
||||
if op.tx != nil {
|
||||
db = op.tx
|
||||
}
|
||||
return db.Clauses(clause.OnConflict{
|
||||
Columns: []clause.Column{{Name: "user_id"}, {Name: "document_id"}},
|
||||
DoUpdates: clause.Assignments(map[string]interface{}{"accessed_at": op.m.AccessedAt}),
|
||||
}).Create(op.m).Error
|
||||
}
|
||||
@@ -0,0 +1,92 @@
|
||||
package document_repo
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
cache_loader "github.com/XingfenD/yoresee_doc/internal/cache"
|
||||
"github.com/XingfenD/yoresee_doc/internal/model"
|
||||
"github.com/XingfenD/yoresee_doc/pkg/key"
|
||||
"github.com/redis/go-redis/v9"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
type DocumentRepository struct {
|
||||
db *gorm.DB
|
||||
redis *redis.Client
|
||||
Loader cache_loader.Loader
|
||||
}
|
||||
|
||||
func NewDocumentRepository(db *gorm.DB, redis *redis.Client) *DocumentRepository {
|
||||
return &DocumentRepository{
|
||||
db: db,
|
||||
redis: redis,
|
||||
Loader: *cache_loader.NewLoader(redis),
|
||||
}
|
||||
}
|
||||
|
||||
type DocumentDeleteOperation struct {
|
||||
repo *DocumentRepository
|
||||
id int64
|
||||
tx *gorm.DB
|
||||
ctx context.Context
|
||||
}
|
||||
|
||||
func (r *DocumentRepository) Delete(id int64) *DocumentDeleteOperation {
|
||||
return &DocumentDeleteOperation{
|
||||
repo: r,
|
||||
id: id,
|
||||
ctx: context.Background(),
|
||||
}
|
||||
}
|
||||
|
||||
func (op *DocumentDeleteOperation) WithTx(tx *gorm.DB) *DocumentDeleteOperation {
|
||||
op.tx = tx
|
||||
return op
|
||||
}
|
||||
|
||||
func (op *DocumentDeleteOperation) WithContext(ctx context.Context) *DocumentDeleteOperation {
|
||||
op.ctx = ctx
|
||||
return op
|
||||
}
|
||||
|
||||
func (op *DocumentDeleteOperation) Exec() error {
|
||||
var doc model.Document
|
||||
var db *gorm.DB
|
||||
var err error
|
||||
|
||||
if op.tx != nil {
|
||||
db = op.tx
|
||||
} else {
|
||||
db = op.repo.db
|
||||
}
|
||||
|
||||
if err := db.First(&doc, op.id).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if op.tx == nil {
|
||||
db = db.Begin()
|
||||
defer func() {
|
||||
if err != nil {
|
||||
db.Rollback()
|
||||
} else {
|
||||
db.Commit()
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
if err := db.Delete(&model.Document{}, op.id).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if op.tx == nil {
|
||||
docCacheKey := key.KeyModelByExternalID(key.KeyObjectTypeEnum_Doc, doc.ExternalID)
|
||||
if err := op.repo.redis.Del(op.ctx, docCacheKey).Err(); err != nil {
|
||||
}
|
||||
|
||||
if err := op.repo.BumpSubtreeVersionsByPath(op.ctx, doc.Path); err != nil {
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
package document_repo
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
cache_loader "github.com/XingfenD/yoresee_doc/internal/cache"
|
||||
"github.com/XingfenD/yoresee_doc/internal/model"
|
||||
"github.com/XingfenD/yoresee_doc/pkg/key"
|
||||
"github.com/sirupsen/logrus"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
type DocumentGetByExternalIDOperation struct {
|
||||
repo *DocumentRepository
|
||||
externalID string
|
||||
tx *gorm.DB
|
||||
}
|
||||
|
||||
func (r *DocumentRepository) GetByExternalID(externalID string) *DocumentGetByExternalIDOperation {
|
||||
return &DocumentGetByExternalIDOperation{
|
||||
repo: r,
|
||||
externalID: externalID,
|
||||
}
|
||||
}
|
||||
|
||||
func (op *DocumentGetByExternalIDOperation) WithTx(tx *gorm.DB) *DocumentGetByExternalIDOperation {
|
||||
op.tx = tx
|
||||
return op
|
||||
}
|
||||
|
||||
func (op *DocumentGetByExternalIDOperation) query(db *gorm.DB) (*model.Document, error) {
|
||||
var document model.Document
|
||||
var err error
|
||||
|
||||
err = db.First(&document, "external_id = ?", op.externalID).Error
|
||||
|
||||
return &document, err
|
||||
}
|
||||
|
||||
func (op *DocumentGetByExternalIDOperation) Exec(ctx context.Context) (*model.Document, error) {
|
||||
var err error
|
||||
|
||||
if op.tx != nil {
|
||||
return op.query(op.tx)
|
||||
}
|
||||
|
||||
documentCacheKey := key.KeyModelByExternalID(key.KeyObjectTypeEnum_Doc, op.externalID)
|
||||
document, err := cache_loader.NewCacheLoadOperation[model.Document](&op.repo.Loader).
|
||||
WithDBLoader(func() (*model.Document, error) {
|
||||
return op.query(op.repo.db)
|
||||
}).WithDefaultKeyAndParser(documentCacheKey, nil).
|
||||
Exec(ctx)
|
||||
|
||||
if err != nil {
|
||||
logrus.Errorf("load data failed for DocumentGetByExternalIDOperation: %+v", err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return document, nil
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
package document_repo
|
||||
|
||||
import (
|
||||
"github.com/XingfenD/yoresee_doc/internal/model"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
type DocumentGetContentOperation struct {
|
||||
repo *DocumentRepository
|
||||
documentID int64
|
||||
tx *gorm.DB
|
||||
}
|
||||
|
||||
func (r *DocumentRepository) GetContent(documentID int64) *DocumentGetContentOperation {
|
||||
return &DocumentGetContentOperation{
|
||||
repo: r,
|
||||
documentID: documentID,
|
||||
}
|
||||
}
|
||||
|
||||
func (op *DocumentGetContentOperation) WithTx(tx *gorm.DB) *DocumentGetContentOperation {
|
||||
op.tx = tx
|
||||
return op
|
||||
}
|
||||
|
||||
func (op *DocumentGetContentOperation) Exec() (string, error) {
|
||||
var docMeta model.Document
|
||||
var err error
|
||||
|
||||
db := op.repo.db
|
||||
if op.tx != nil {
|
||||
db = op.tx
|
||||
}
|
||||
|
||||
err = db.Where("id = ?", op.documentID).First(&docMeta).Error
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return docMeta.Content, nil
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
package document_repo
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
cache_loader "github.com/XingfenD/yoresee_doc/internal/cache"
|
||||
"github.com/XingfenD/yoresee_doc/internal/model"
|
||||
"github.com/XingfenD/yoresee_doc/pkg/key"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
type DocumentGetIDByExternalIDOperation struct {
|
||||
repo *DocumentRepository
|
||||
externalID string
|
||||
tx *gorm.DB
|
||||
}
|
||||
|
||||
func (r *DocumentRepository) GetIDByExternalID(externalID string) *DocumentGetIDByExternalIDOperation {
|
||||
return &DocumentGetIDByExternalIDOperation{
|
||||
repo: r,
|
||||
externalID: externalID,
|
||||
}
|
||||
}
|
||||
|
||||
func (op *DocumentGetIDByExternalIDOperation) WithTx(tx *gorm.DB) *DocumentGetIDByExternalIDOperation {
|
||||
op.tx = tx
|
||||
return op
|
||||
}
|
||||
|
||||
func (op DocumentGetIDByExternalIDOperation) query(tx *gorm.DB) (int64, error) {
|
||||
var id int64
|
||||
err := tx.Model(&model.Document{}).Where("external_id = ?", op.externalID).Pluck("id", &id).Error
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return id, nil
|
||||
}
|
||||
|
||||
func (op *DocumentGetIDByExternalIDOperation) Exec(ctx context.Context) (int64, error) {
|
||||
if op.tx != nil {
|
||||
return op.query(op.tx)
|
||||
}
|
||||
|
||||
extidKey := key.KeyIDByExternalID(key.KeyObjectTypeEnum_Doc, op.externalID)
|
||||
modelKey := key.KeyModelByExternalID(key.KeyObjectTypeEnum_Doc, op.externalID)
|
||||
|
||||
id, err := cache_loader.NewCacheLoadOperation[int64](&op.repo.Loader).
|
||||
WithDefaultKeyAndParser(extidKey, cache_loader.ParseInt64).
|
||||
WithKeyAndParser(modelKey, cache_loader.ParseIDFromDocument).
|
||||
WithDBLoader(func() (*int64, error) {
|
||||
id, err := op.query(op.repo.db)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &id, nil
|
||||
}).Exec(ctx)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
if id == nil {
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
return *id, nil
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user