Compare commits

..

2 Commits

Author SHA1 Message Date
coltea 0624cfc340 feat: node release user info 2025-11-04 19:00:47 +08:00
coltea 29ccdad483 feat widget setting 2025-11-04 18:58:39 +08:00
14 changed files with 356 additions and 26 deletions

View File

@ -13,17 +13,23 @@ type GetNodeDetailReq struct {
}
type NodeDetailResp struct {
ID string `json:"id"`
KbID string `json:"kb_id"`
Type domain.NodeType `json:"type"`
Status domain.NodeStatus `json:"status"`
Name string `json:"name"`
Content string `json:"content"`
Meta domain.NodeMeta `json:"meta"`
ParentID string `json:"parent_id"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
Permissions domain.NodePermissions `json:"permissions"`
ID string `json:"id"`
KbID string `json:"kb_id"`
Type domain.NodeType `json:"type"`
Status domain.NodeStatus `json:"status"`
Name string `json:"name"`
Content string `json:"content"`
Meta domain.NodeMeta `json:"meta"`
ParentID string `json:"parent_id"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
Permissions domain.NodePermissions `json:"permissions"`
CreatorId string `json:"creator_id"`
EditorId string `json:"editor_id"`
PublisherId string `json:"publisher_id" gorm:"-"`
CreatorAccount string `json:"creator_account"`
EditorAccount string `json:"editor_account"`
PublisherAccount string `json:"publisher_account" gorm:"-"`
}
type NodePermissionReq struct {

View File

@ -0,0 +1,27 @@
package v1
import (
"time"
"github.com/chaitin/panda-wiki/domain"
)
type ShareNodeDetailResp struct {
ID string `json:"id"`
KbID string `json:"kb_id"`
Type domain.NodeType `json:"type"`
Status domain.NodeStatus `json:"status"`
Name string `json:"name"`
Content string `json:"content"`
Meta domain.NodeMeta `json:"meta"`
ParentID string `json:"parent_id"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
Permissions domain.NodePermissions `json:"permissions"`
CreatorId string `json:"creator_id"`
EditorId string `json:"editor_id"`
PublisherId string `json:"publisher_id"`
CreatorAccount string `json:"creator_account"`
EditorAccount string `json:"editor_account"`
PublisherAccount string `json:"publisher_account"`
}

View File

@ -96,7 +96,7 @@ func createApp() (*App, error) {
return nil, err
}
authRepo := pg2.NewAuthRepo(db, logger, cacheCache)
nodeUsecase := usecase.NewNodeUsecase(nodeRepository, appRepository, ragRepository, knowledgeBaseRepository, llmUsecase, ragService, logger, minioClient, modelRepository, authRepo)
nodeUsecase := usecase.NewNodeUsecase(nodeRepository, appRepository, ragRepository, userRepository, knowledgeBaseRepository, llmUsecase, ragService, logger, minioClient, modelRepository, authRepo)
nodeHandler := v1.NewNodeHandler(baseHandler, echo, nodeUsecase, authMiddleware, logger)
geoRepo := cache2.NewGeoCache(cacheCache, db, logger)
ipdbIPDB, err := ipdb.NewIPDB(configConfig, logger)

View File

@ -76,11 +76,12 @@ func createApp() (*App, error) {
return nil, err
}
ragRepository := mq3.NewRAGRepository(mqProducer)
userRepository := pg2.NewUserRepository(db, logger)
minioClient, err := s3.NewMinioClient(configConfig)
if err != nil {
return nil, err
}
nodeUsecase := usecase.NewNodeUsecase(nodeRepository, appRepository, ragRepository, knowledgeBaseRepository, llmUsecase, ragService, logger, minioClient, modelRepository, authRepo)
nodeUsecase := usecase.NewNodeUsecase(nodeRepository, appRepository, ragRepository, userRepository, knowledgeBaseRepository, llmUsecase, ragService, logger, minioClient, modelRepository, authRepo)
cronHandler, err := mq2.NewStatCronHandler(logger, statRepository, statUseCase, nodeUsecase)
if err != nil {
return nil, err

View File

@ -41,6 +41,7 @@ func createApp() (*App, error) {
return nil, err
}
ragRepository := mq2.NewRAGRepository(mqProducer)
userRepository := pg2.NewUserRepository(db, logger)
ragService, err := rag.NewRAGService(configConfig, logger)
if err != nil {
return nil, err
@ -59,8 +60,7 @@ func createApp() (*App, error) {
return nil, err
}
authRepo := pg2.NewAuthRepo(db, logger, cacheCache)
nodeUsecase := usecase.NewNodeUsecase(nodeRepository, appRepository, ragRepository, knowledgeBaseRepository, llmUsecase, ragService, logger, minioClient, modelRepository, authRepo)
userRepository := pg2.NewUserRepository(db, logger)
nodeUsecase := usecase.NewNodeUsecase(nodeRepository, appRepository, ragRepository, userRepository, knowledgeBaseRepository, llmUsecase, ragService, logger, minioClient, modelRepository, authRepo)
kbRepo := cache2.NewKBRepo(cacheCache)
knowledgeBaseUsecase, err := usecase.NewKnowledgeBaseUsecase(knowledgeBaseRepository, nodeRepository, ragRepository, userRepository, ragService, kbRepo, logger, configConfig)
if err != nil {

View File

@ -3616,7 +3616,19 @@ const docTemplate = `{
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/domain.Response"
"allOf": [
{
"$ref": "#/definitions/domain.Response"
},
{
"type": "object",
"properties": {
"data": {
"$ref": "#/definitions/v1.ShareNodeDetailResp"
}
}
}
]
}
}
}
@ -8070,6 +8082,18 @@ const docTemplate = `{
"created_at": {
"type": "string"
},
"creator_account": {
"type": "string"
},
"creator_id": {
"type": "string"
},
"editor_account": {
"type": "string"
},
"editor_id": {
"type": "string"
},
"id": {
"type": "string"
},
@ -8088,6 +8112,12 @@ const docTemplate = `{
"permissions": {
"$ref": "#/definitions/domain.NodePermissions"
},
"publisher_account": {
"type": "string"
},
"publisher_id": {
"type": "string"
},
"status": {
"$ref": "#/definitions/domain.NodeStatus"
},
@ -8192,6 +8222,62 @@ const docTemplate = `{
}
}
},
"v1.ShareNodeDetailResp": {
"type": "object",
"properties": {
"content": {
"type": "string"
},
"created_at": {
"type": "string"
},
"creator_account": {
"type": "string"
},
"creator_id": {
"type": "string"
},
"editor_account": {
"type": "string"
},
"editor_id": {
"type": "string"
},
"id": {
"type": "string"
},
"kb_id": {
"type": "string"
},
"meta": {
"$ref": "#/definitions/domain.NodeMeta"
},
"name": {
"type": "string"
},
"parent_id": {
"type": "string"
},
"permissions": {
"$ref": "#/definitions/domain.NodePermissions"
},
"publisher_account": {
"type": "string"
},
"publisher_id": {
"type": "string"
},
"status": {
"$ref": "#/definitions/domain.NodeStatus"
},
"type": {
"$ref": "#/definitions/domain.NodeType"
},
"updated_at": {
"type": "string"
}
}
},
"v1.StatCountResp": {
"type": "object",
"properties": {

View File

@ -3609,7 +3609,19 @@
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/domain.Response"
"allOf": [
{
"$ref": "#/definitions/domain.Response"
},
{
"type": "object",
"properties": {
"data": {
"$ref": "#/definitions/v1.ShareNodeDetailResp"
}
}
}
]
}
}
}
@ -8063,6 +8075,18 @@
"created_at": {
"type": "string"
},
"creator_account": {
"type": "string"
},
"creator_id": {
"type": "string"
},
"editor_account": {
"type": "string"
},
"editor_id": {
"type": "string"
},
"id": {
"type": "string"
},
@ -8081,6 +8105,12 @@
"permissions": {
"$ref": "#/definitions/domain.NodePermissions"
},
"publisher_account": {
"type": "string"
},
"publisher_id": {
"type": "string"
},
"status": {
"$ref": "#/definitions/domain.NodeStatus"
},
@ -8185,6 +8215,62 @@
}
}
},
"v1.ShareNodeDetailResp": {
"type": "object",
"properties": {
"content": {
"type": "string"
},
"created_at": {
"type": "string"
},
"creator_account": {
"type": "string"
},
"creator_id": {
"type": "string"
},
"editor_account": {
"type": "string"
},
"editor_id": {
"type": "string"
},
"id": {
"type": "string"
},
"kb_id": {
"type": "string"
},
"meta": {
"$ref": "#/definitions/domain.NodeMeta"
},
"name": {
"type": "string"
},
"parent_id": {
"type": "string"
},
"permissions": {
"$ref": "#/definitions/domain.NodePermissions"
},
"publisher_account": {
"type": "string"
},
"publisher_id": {
"type": "string"
},
"status": {
"$ref": "#/definitions/domain.NodeStatus"
},
"type": {
"$ref": "#/definitions/domain.NodeType"
},
"updated_at": {
"type": "string"
}
}
},
"v1.StatCountResp": {
"type": "object",
"properties": {

View File

@ -2860,6 +2860,14 @@ definitions:
type: string
created_at:
type: string
creator_account:
type: string
creator_id:
type: string
editor_account:
type: string
editor_id:
type: string
id:
type: string
kb_id:
@ -2872,6 +2880,10 @@ definitions:
type: string
permissions:
$ref: '#/definitions/domain.NodePermissions'
publisher_account:
type: string
publisher_id:
type: string
status:
$ref: '#/definitions/domain.NodeStatus'
type:
@ -2943,6 +2955,43 @@ definitions:
- id
- new_password
type: object
v1.ShareNodeDetailResp:
properties:
content:
type: string
created_at:
type: string
creator_account:
type: string
creator_id:
type: string
editor_account:
type: string
editor_id:
type: string
id:
type: string
kb_id:
type: string
meta:
$ref: '#/definitions/domain.NodeMeta'
name:
type: string
parent_id:
type: string
permissions:
$ref: '#/definitions/domain.NodePermissions'
publisher_account:
type: string
publisher_id:
type: string
status:
$ref: '#/definitions/domain.NodeStatus'
type:
$ref: '#/definitions/domain.NodeType'
updated_at:
type: string
type: object
v1.StatCountResp:
properties:
conversation_count:
@ -5216,7 +5265,12 @@ paths:
"200":
description: OK
schema:
$ref: '#/definitions/domain.Response'
allOf:
- $ref: '#/definitions/domain.Response'
- properties:
data:
$ref: '#/definitions/v1.ShareNodeDetailResp'
type: object
summary: GetNodeDetail
tags:
- share_node

View File

@ -320,3 +320,9 @@ type NodeCreateInfo struct {
Account string `json:"account"`
CreatorId string `json:"creator_id"`
}
type NodeReleaseWithPublisher struct {
ID string `json:"id" gorm:"primaryKey"`
PublisherId string `json:"publisher_id"`
PublisherAccount string `json:"publisher_account"`
}

View File

@ -70,7 +70,7 @@ func (h *ShareNodeHandler) GetNodeList(c echo.Context) error {
// @Param X-KB-ID header string true "kb id"
// @Param id query string true "node id"
// @Param format query string true "format"
// @Success 200 {object} domain.Response
// @Success 200 {object} domain.Response{data=v1.ShareNodeDetailResp}
// @Router /share/v1/node/detail [get]
func (h *ShareNodeHandler) GetNodeDetail(c echo.Context) error {
kbID := c.Request().Header.Get("X-KB-ID")

@ -1 +1 @@
Subproject commit 9c899db37070ea801d74370ef7e01853c5c5fde9
Subproject commit 1b0bb2ce039ac1d8352be941380872fcede5bf7a

View File

@ -16,6 +16,7 @@ import (
"github.com/samber/lo/mutable"
v1 "github.com/chaitin/panda-wiki/api/node/v1"
shareV1 "github.com/chaitin/panda-wiki/api/share/v1"
"github.com/chaitin/panda-wiki/consts"
"github.com/chaitin/panda-wiki/domain"
"github.com/chaitin/panda-wiki/log"
@ -258,8 +259,11 @@ func (r *NodeRepository) GetByID(ctx context.Context, id, kbId string) (*v1.Node
var node *v1.NodeDetailResp
if err := r.db.WithContext(ctx).
Model(&domain.Node{}).
Where("id = ?", id).
Where("kb_id = ?", kbId).
Select("nodes.*, creator.id as creator_id, creator.account as creator_account, editor.id as editor_id, editor.account as editor_account").
Joins("left join users creator on creator.id = nodes.creator_id").
Joins("left join users editor on editor.id = nodes.editor_id").
Where("nodes.id = ?", id).
Where("nodes.kb_id = ?", kbId).
First(&node).Error; err != nil {
return nil, err
}
@ -425,6 +429,20 @@ func (r *NodeRepository) GetLatestNodeReleaseByNodeID(ctx context.Context, nodeI
return nodeRelease, nil
}
func (r *NodeRepository) GetLatestNodeReleaseWithPublishAccount(ctx context.Context, nodeID string) (*domain.NodeReleaseWithPublisher, error) {
var nodeRelease *domain.NodeReleaseWithPublisher
if err := r.db.WithContext(ctx).
Model(&domain.NodeRelease{}).Debug().
Select("node_releases.id, node_releases.publisher_id, users.account as publisher_account").
Joins("left join users on users.id = node_releases.publisher_id").
Where("node_releases.node_id = ?", nodeID).
Order("node_releases.updated_at DESC").
Find(&nodeRelease).Error; err != nil {
return nil, err
}
return nodeRelease, nil
}
// GetNodeReleaseWithDirPathByID gets a node release by ID and includes its directory path
func (r *NodeRepository) GetNodeReleaseWithDirPathByID(ctx context.Context, id string) (*domain.NodeReleaseWithDirPath, error) {
// First get the node release
@ -672,7 +690,7 @@ func (r *NodeRepository) GetNodeReleaseListByKBID(ctx context.Context, kbID stri
return nodes, nil
}
func (r *NodeRepository) GetNodeReleaseDetailByKBIDAndID(ctx context.Context, kbID, id string) (*v1.NodeDetailResp, error) {
func (r *NodeRepository) GetNodeReleaseDetailByKBIDAndID(ctx context.Context, kbID, id string) (*shareV1.ShareNodeDetailResp, error) {
// get kb release
var kbRelease *domain.KBRelease
if err := r.db.WithContext(ctx).
@ -683,16 +701,16 @@ func (r *NodeRepository) GetNodeReleaseDetailByKBIDAndID(ctx context.Context, kb
return nil, err
}
var node *v1.NodeDetailResp
var node *shareV1.ShareNodeDetailResp
if err := r.db.WithContext(ctx).
Model(&domain.KBReleaseNodeRelease{}).
Select("node_releases.*, nodes.permissions, nodes.creator_id").
Joins("LEFT JOIN node_releases ON node_releases.id = kb_release_node_releases.node_release_id").
Joins("LEFT JOIN nodes ON nodes.id = kb_release_node_releases.node_id").
Where("kb_release_node_releases.release_id = ?", kbRelease.ID).
Where("node_releases.node_id = ?", id).
Where("node_releases.kb_id = ?", kbID).
Where("nodes.permissions->>'visitable' != ?", consts.NodeAccessPermClosed).
Select("node_releases.*, nodes.permissions").
First(&node).Error; err != nil {
return nil, err
}

View File

@ -5,6 +5,7 @@ import (
"errors"
"fmt"
"github.com/samber/lo"
"golang.org/x/crypto/bcrypt"
"gorm.io/gorm"
@ -113,6 +114,22 @@ func (r *UserRepository) ListUsers(ctx context.Context) ([]v1.UserListItemResp,
return users, nil
}
func (r *UserRepository) GetUsersAccountMap(ctx context.Context) (map[string]string, error) {
var users []v1.UserListItemResp
err := r.db.WithContext(ctx).
Model(&domain.User{}).
Find(&users).Error
if err != nil {
return nil, err
}
m := lo.SliceToMap(users, func(user v1.UserListItemResp) (string, string) {
return user.ID, user.Account
})
return m, nil
}
func (r *UserRepository) UpdateUserPassword(ctx context.Context, userID string, newPassword string) error {
hashedPassword, err := bcrypt.GenerateFromPassword([]byte(newPassword), bcrypt.DefaultCost)
if err != nil {

View File

@ -15,6 +15,7 @@ import (
"gorm.io/gorm"
v1 "github.com/chaitin/panda-wiki/api/node/v1"
shareV1 "github.com/chaitin/panda-wiki/api/share/v1"
"github.com/chaitin/panda-wiki/consts"
"github.com/chaitin/panda-wiki/domain"
"github.com/chaitin/panda-wiki/log"
@ -31,6 +32,7 @@ type NodeUsecase struct {
ragRepo *mq.RAGRepository
kbRepo *pg.KnowledgeBaseRepository
modelRepo *pg.ModelRepository
userRepo *pg.UserRepository
authRepo *pg.AuthRepo
llmUsecase *LLMUsecase
logger *log.Logger
@ -42,6 +44,7 @@ func NewNodeUsecase(
nodeRepo *pg.NodeRepository,
appRepo *pg.AppRepository,
ragRepo *mq.RAGRepository,
userRepo *pg.UserRepository,
kbRepo *pg.KnowledgeBaseRepository,
llmUsecase *LLMUsecase,
ragService rag.RAGService,
@ -57,6 +60,7 @@ func NewNodeUsecase(
ragRepo: ragRepo,
kbRepo: kbRepo,
authRepo: authRepo,
userRepo: userRepo,
llmUsecase: llmUsecase,
modelRepo: modelRepo,
logger: logger.WithModule("usecase.node"),
@ -87,6 +91,16 @@ func (u *NodeUsecase) GetNodeByKBID(ctx context.Context, id, kbId, format string
if err != nil {
return nil, err
}
nodeRelease, err := u.nodeRepo.GetLatestNodeReleaseWithPublishAccount(ctx, node.ID)
if err != nil {
return nil, err
}
if nodeRelease != nil {
node.PublisherId = nodeRelease.PublisherId
node.PublisherAccount = nodeRelease.PublisherAccount
}
if node.Meta.ContentType == domain.ContentTypeMD {
return node, nil
}
@ -169,11 +183,26 @@ func (u *NodeUsecase) ValidateNodePerm(ctx context.Context, kbID, nodeId string,
return nil
}
func (u *NodeUsecase) GetNodeReleaseDetailByKBIDAndID(ctx context.Context, kbID, nodeId, format string) (*v1.NodeDetailResp, error) {
func (u *NodeUsecase) GetNodeReleaseDetailByKBIDAndID(ctx context.Context, kbID, nodeId, format string) (*shareV1.ShareNodeDetailResp, error) {
node, err := u.nodeRepo.GetNodeReleaseDetailByKBIDAndID(ctx, kbID, nodeId)
if err != nil {
return nil, err
}
userMap, err := u.userRepo.GetUsersAccountMap(ctx)
if err != nil {
return nil, err
}
if account, ok := userMap[node.CreatorId]; ok {
node.CreatorAccount = account
}
if account, ok := userMap[node.EditorId]; ok {
node.EditorAccount = account
}
if account, ok := userMap[node.PublisherId]; ok {
node.PublisherAccount = account
}
if node.Meta.ContentType == domain.ContentTypeMD {
return node, nil
}