Compare commits

..

2 Commits

Author SHA1 Message Date
coltea 2f706a6100 fix share nodes position 2025-11-13 18:40:33 +08:00
coltea febcb06654 feat edition 2025-11-13 15:50:05 +08:00
15 changed files with 69 additions and 142 deletions

View File

@ -1,8 +1,6 @@
package consts
import (
"math"
"github.com/labstack/echo/v4"
)
@ -19,106 +17,7 @@ const (
LicenseEditionBusiness LicenseEdition = 3 // 商业版
)
type EditionLimitation struct {
MaxKb int `json:"max_kb"` // 知识库站点数量
MaxNode int `json:"max_node"` // 单个知识库下文档数量
MaxSSOUser int `json:"max_sso_users"` // SSO认证用户数量
MaxAdmin int64 `json:"max_admin"` // 后台管理员数量
AllowAdminPerm bool `json:"allow_admin_perm"` // 支持管理员分权控制
AllowCustomCopyright bool `json:"allow_custom_copyright"` // 支持自定义版权信息
AllowAdvancedStats bool `json:"allow_advanced_stats"` // 支持高级访问流量分析
AllowCustomPrompt bool `json:"allow_custom_prompt"` // 支持自定义AI提示词
AllowContribute bool `json:"allow_contribute"` // 支持文档贡献
AllowCommentAudit bool `json:"allow_comment_audit"` // 支持评论审核
AllowAdvancedBot bool `json:"allow_advanced_bot"` // 支持高级机器人配置
AllowSSOAuth bool `json:"allow_sso_auth"` // 支持SSO登录
AllowNodeUserPerm bool `json:"allow_node_user_perm"` // 支持文档访客权限控制
AllowWatermark bool `json:"allow_watermark"` // 支持水印
AllowCopyProtection bool `json:"allow_copy_protection"` // 支持内容复制保护
AllowSensitiveFilter bool `json:"allow_sensitive_filter"` // 支持敏感内容过滤
AllowNodeHistory bool `json:"allow_node_history"` // 支持管理文档历史版本数量
AllowAPI bool `json:"allow_api"` // 支持api调用
}
func GetLicenseEdition(c echo.Context) LicenseEdition {
edition, _ := c.Get("edition").(LicenseEdition)
return edition
}
// GetLimitation 获取版本对应限制
func (e LicenseEdition) GetLimitation() EditionLimitation {
switch e {
case LicenseEditionProfession:
return EditionLimitation{
MaxSSOUser: 0,
MaxNode: 10000,
MaxKb: 10,
MaxAdmin: 20,
AllowAdminPerm: true,
AllowCustomCopyright: true,
AllowAdvancedStats: true,
AllowCustomPrompt: true,
AllowCommentAudit: true,
AllowContribute: true,
AllowAdvancedBot: true,
AllowSSOAuth: false,
AllowNodeUserPerm: false,
AllowWatermark: false,
AllowCopyProtection: false,
AllowSensitiveFilter: false,
AllowNodeHistory: false,
AllowAPI: false,
}
case LicenseEditionBusiness:
return EditionLimitation{
MaxSSOUser: 2000,
MaxNode: 10000,
MaxKb: 10,
MaxAdmin: 50,
AllowAdminPerm: true,
AllowCustomCopyright: true,
AllowAdvancedStats: true,
AllowCustomPrompt: true,
AllowCommentAudit: true,
AllowContribute: true,
AllowAdvancedBot: true,
AllowSSOAuth: true,
AllowNodeUserPerm: true,
AllowWatermark: true,
AllowCopyProtection: true,
AllowSensitiveFilter: true,
AllowNodeHistory: true,
AllowAPI: true,
}
case LicenseEditionEnterprise:
return EditionLimitation{
MaxSSOUser: math.MaxInt,
MaxNode: math.MaxInt,
MaxKb: math.MaxInt,
MaxAdmin: math.MaxInt,
AllowAdminPerm: true,
AllowCustomCopyright: true,
AllowAdvancedStats: true,
AllowCustomPrompt: true,
AllowCommentAudit: true,
AllowContribute: true,
AllowAdvancedBot: true,
AllowSSOAuth: true,
AllowNodeUserPerm: true,
AllowWatermark: true,
AllowCopyProtection: true,
AllowSensitiveFilter: true,
AllowNodeHistory: true,
AllowAPI: true,
}
default:
// LicenseEditionFree
return EditionLimitation{
MaxSSOUser: 0,
MaxNode: 300,
MaxKb: 1,
MaxAdmin: 1,
}
}
}

43
backend/domain/license.go Normal file
View File

@ -0,0 +1,43 @@
package domain
import (
"context"
"encoding/json"
)
const ContextKeyEditionLimitation contextKey = "edition_limitation"
type BaseEditionLimitation struct {
MaxKb int `json:"max_kb"` // 知识库站点数量
MaxNode int `json:"max_node"` // 单个知识库下文档数量
MaxSSOUser int `json:"max_sso_users"` // SSO认证用户数量
MaxAdmin int64 `json:"max_admin"` // 后台管理员数量
AllowAdminPerm bool `json:"allow_admin_perm"` // 支持管理员分权控制
AllowCustomCopyright bool `json:"allow_custom_copyright"` // 支持自定义版权信息
AllowCommentAudit bool `json:"allow_comment_audit"` // 支持评论审核
AllowAdvancedBot bool `json:"allow_advanced_bot"` // 支持高级机器人配置
AllowWatermark bool `json:"allow_watermark"` // 支持水印
AllowCopyProtection bool `json:"allow_copy_protection"` // 支持内容复制保护
AllowOpenAIBotSettings bool `json:"allow_open_ai_bot_settings"` // 支持问答机器人
}
var baseEditionLimitationDefault = BaseEditionLimitation{
MaxKb: 1,
MaxAdmin: 1,
MaxNode: 300,
}
func GetBaseEditionLimitation(c context.Context) BaseEditionLimitation {
edition, ok := c.Value(ContextKeyEditionLimitation).([]byte)
if !ok {
return baseEditionLimitationDefault
}
var editionLimitation BaseEditionLimitation
if err := json.Unmarshal(edition, &editionLimitation); err != nil {
return baseEditionLimitationDefault
}
return editionLimitation
}

View File

@ -6,7 +6,6 @@ import (
"github.com/labstack/echo/v4"
"github.com/chaitin/panda-wiki/consts"
"github.com/chaitin/panda-wiki/domain"
"github.com/chaitin/panda-wiki/handler"
"github.com/chaitin/panda-wiki/log"
@ -157,7 +156,7 @@ func (h *ShareCommentHandler) GetCommentList(c echo.Context) error {
}
// 查询数据库获取所有评论-->0 所有, 12 为需要审核的评论
commentsList, err := h.usecase.GetCommentListByNodeID(ctx, nodeID, consts.GetLicenseEdition(c))
commentsList, err := h.usecase.GetCommentListByNodeID(ctx, nodeID)
if err != nil {
return h.NewResponseWithError(c, "failed to get comment list", err)
}

View File

@ -5,6 +5,7 @@ import (
v1 "github.com/chaitin/panda-wiki/api/kb/v1"
"github.com/chaitin/panda-wiki/consts"
"github.com/chaitin/panda-wiki/domain"
)
// KBUserList
@ -55,7 +56,7 @@ func (h *KnowledgeBaseHandler) KBUserInvite(c echo.Context) error {
return h.NewResponseWithError(c, "validate request failed", err)
}
if !consts.GetLicenseEdition(c).GetLimitation().AllowAdminPerm && req.Perm != consts.UserKBPermissionFullControl {
if !domain.GetBaseEditionLimitation(c.Request().Context()).AllowAdminPerm && req.Perm != consts.UserKBPermissionFullControl {
return h.NewResponseWithError(c, "当前版本不支持管理员分权控制", nil)
}
@ -87,7 +88,7 @@ func (h *KnowledgeBaseHandler) KBUserUpdate(c echo.Context) error {
return h.NewResponseWithError(c, "validate request failed", err)
}
if !consts.GetLicenseEdition(c).GetLimitation().AllowAdminPerm && req.Perm != consts.UserKBPermissionFullControl {
if !domain.GetBaseEditionLimitation(c.Request().Context()).AllowAdminPerm && req.Perm != consts.UserKBPermissionFullControl {
return h.NewResponseWithError(c, "当前版本不支持管理员分权控制", nil)
}

View File

@ -91,7 +91,7 @@ func (h *KnowledgeBaseHandler) CreateKnowledgeBase(c echo.Context) error {
return h.NewResponseWithError(c, "ports is required", nil)
}
req.MaxKB = consts.GetLicenseEdition(c).GetLimitation().MaxKb
req.MaxKB = domain.GetBaseEditionLimitation(c.Request().Context()).MaxKb
did, err := h.usecase.CreateKnowledgeBase(c.Request().Context(), &req)
if err != nil {

View File

@ -82,7 +82,7 @@ func (h *NodeHandler) CreateNode(c echo.Context) error {
return h.NewResponseWithError(c, "validate request body failed", err)
}
req.MaxNode = consts.GetLicenseEdition(c).GetLimitation().MaxNode
req.MaxNode = domain.GetBaseEditionLimitation(ctx).MaxNode
id, err := h.usecase.Create(c.Request().Context(), req, authInfo.UserId)
if err != nil {

View File

@ -16,7 +16,6 @@ type AuthMiddleware interface {
ValidateUserRole(role consts.UserRole) echo.MiddlewareFunc
ValidateKBUserPerm(role consts.UserKBPermission) echo.MiddlewareFunc
ValidateLicenseEdition(edition ...consts.LicenseEdition) echo.MiddlewareFunc
ValidateLicenseEditionLimitation(f func(consts.EditionLimitation) bool) echo.MiddlewareFunc
MustGetUserID(c echo.Context) (string, bool)
}

View File

@ -219,20 +219,6 @@ func (m *JWTMiddleware) ValidateLicenseEdition(needEditions ...consts.LicenseEdi
}
}
func (m *JWTMiddleware) ValidateLicenseEditionLimitation(f func(consts.EditionLimitation) bool) echo.MiddlewareFunc {
return func(next echo.HandlerFunc) echo.HandlerFunc {
return func(c echo.Context) error {
if !f(consts.GetLicenseEdition(c).GetLimitation()) {
return c.JSON(http.StatusForbidden, domain.PWResponse{
Success: false,
Message: "Validate EditionLimitation Failed",
})
}
return next(c)
}
}
}
func (m *JWTMiddleware) MustGetUserID(c echo.Context) (string, bool) {
user, ok := c.Get("user").(*jwt.Token)
if !ok || user == nil {

@ -1 +1 @@
Subproject commit 7db7fe94c7a7511f5c7415b4776bf76d826e58bb
Subproject commit bcf7e0f0bedb18f43cf36463ddb45ace6c1dbab9

View File

@ -300,8 +300,8 @@ func (r *AuthRepo) GetOrCreateAuth(ctx context.Context, auth *domain.Auth, sourc
return err
}
if int(count) >= licenseEdition.GetLimitation().MaxSSOUser {
return fmt.Errorf("exceed max auth limit for kb %s, current count: %d, max limit: %d", auth.KBID, count, licenseEdition.GetLimitation().MaxSSOUser)
if int(count) >= domain.GetBaseEditionLimitation(ctx).MaxSSOUser {
return fmt.Errorf("exceed max auth limit for kb %s, current count: %d, max limit: %d", auth.KBID, count, domain.GetBaseEditionLimitation(ctx).MaxSSOUser)
}
auth.LastLoginTime = time.Now()

View File

@ -26,12 +26,12 @@ func (r *CommentRepository) CreateComment(ctx context.Context, comment *domain.C
return nil
}
func (r *CommentRepository) GetCommentList(ctx context.Context, nodeID string, edition consts.LicenseEdition) ([]*domain.ShareCommentListItem, int64, error) {
func (r *CommentRepository) GetCommentList(ctx context.Context, nodeID string) ([]*domain.ShareCommentListItem, int64, error) {
// 按照时间排序来查询node_id的comments
var comments []*domain.ShareCommentListItem
query := r.db.WithContext(ctx).Model(&domain.Comment{}).Where("node_id = ?", nodeID)
if edition.GetLimitation().AllowCommentAudit {
if domain.GetBaseEditionLimitation(ctx).AllowCommentAudit {
query = query.Where("status = ?", domain.CommentStatusAccepted) //accepted
}
@ -50,14 +50,14 @@ func (r *CommentRepository) GetCommentList(ctx context.Context, nodeID string, e
func (r *CommentRepository) GetCommentListByKbID(ctx context.Context, req *domain.CommentListReq, edition consts.LicenseEdition) ([]*domain.CommentListItem, int64, error) {
comments := []*domain.CommentListItem{}
query := r.db.Model(&domain.Comment{}).Where("comments.kb_id = ?", req.KbID)
query := r.db.WithContext(ctx).Model(&domain.Comment{}).Where("comments.kb_id = ?", req.KbID)
var count int64
if req.Status == nil {
if err := query.Count(&count).Error; err != nil {
return nil, 0, err
}
} else {
if edition != consts.LicenseEditionFree {
if domain.GetBaseEditionLimitation(ctx).AllowCommentAudit {
query = query.Where("comments.status = ?", *req.Status)
}
// 按照时间排序来查询kb_id的comments ->reject pending accepted
@ -84,7 +84,7 @@ func (r *CommentRepository) GetCommentListByKbID(ctx context.Context, req *domai
func (r *CommentRepository) DeleteCommentList(ctx context.Context, commentID []string) error {
// 批量删除指定id的comment,获取删除的总的数量、
query := r.db.Model(&domain.Comment{}).Where("id IN (?)", commentID)
query := r.db.WithContext(ctx).Model(&domain.Comment{}).Where("id IN (?)", commentID)
if err := query.Delete(&domain.Comment{}).Error; err != nil {
return err

View File

@ -683,7 +683,7 @@ func (r *NodeRepository) GetNodeReleaseListByKBID(ctx context.Context, kbID stri
Where("kb_release_node_releases.kb_id = ?", kbID).
Where("kb_release_node_releases.release_id = ?", kbRelease.ID).
Where("nodes.permissions->>'visible' != ?", consts.NodeAccessPermClosed).
Select("node_releases.node_id as id, node_releases.name, node_releases.type, node_releases.parent_id, node_releases.position, node_releases.meta->>'emoji' as emoji, node_releases.updated_at, nodes.permissions").
Select("node_releases.node_id as id, node_releases.name, node_releases.type, node_releases.parent_id, nodes.position, node_releases.meta->>'emoji' as emoji, node_releases.updated_at, nodes.permissions").
Find(&nodes).Error; err != nil {
return nil, err
}

View File

@ -64,8 +64,8 @@ func (r *UserRepository) CreateUser(ctx context.Context, user *domain.User, edit
if err := tx.Model(&domain.User{}).Count(&count).Error; err != nil {
return err
}
if count >= edition.GetLimitation().MaxAdmin {
return fmt.Errorf("exceed max admin limit, current count: %d, max limit: %d", count, edition.GetLimitation().MaxAdmin)
if count >= domain.GetBaseEditionLimitation(ctx).MaxAdmin {
return fmt.Errorf("exceed max admin limit, current count: %d, max limit: %d", count, domain.GetBaseEditionLimitation(ctx).MaxAdmin)
}
if err := tx.Create(user).Error; err != nil {

View File

@ -93,7 +93,7 @@ func (u *AppUsecase) ValidateUpdateApp(ctx context.Context, id string, req *doma
return err
}
limitation := edition.GetLimitation()
limitation := domain.GetBaseEditionLimitation(ctx)
if !limitation.AllowCopyProtection && app.Settings.CopySetting != req.Settings.CopySetting {
return domain.ErrPermissionDenied
}
@ -104,10 +104,6 @@ func (u *AppUsecase) ValidateUpdateApp(ctx context.Context, id string, req *doma
}
}
if !limitation.AllowContribute && app.Settings.ContributeSettings != req.Settings.ContributeSettings {
return domain.ErrPermissionDenied
}
if !limitation.AllowAdvancedBot {
if !slices.Equal(app.Settings.WechatServiceContainKeywords, req.Settings.WechatServiceContainKeywords) ||
!slices.Equal(app.Settings.WechatServiceEqualKeywords, req.Settings.WechatServiceEqualKeywords) {
@ -119,6 +115,11 @@ func (u *AppUsecase) ValidateUpdateApp(ctx context.Context, id string, req *doma
return domain.ErrPermissionDenied
}
if !limitation.AllowOpenAIBotSettings {
if app.Settings.OpenAIAPIBotSettings.IsEnabled != req.Settings.OpenAIAPIBotSettings.IsEnabled || app.Settings.OpenAIAPIBotSettings.SecretKey != req.Settings.OpenAIAPIBotSettings.SecretKey {
return domain.ErrPermissionDenied
}
}
return nil
}
@ -621,9 +622,8 @@ func (u *AppUsecase) ShareGetWebAppInfo(ctx context.Context, kbID string, authId
}
showBrand := true
defaultDisclaimer := "本回答由 PandaWiki 基于 AI 生成,仅供参考。"
licenseEdition, _ := ctx.Value(consts.ContextKeyEdition).(consts.LicenseEdition)
if !licenseEdition.GetLimitation().AllowCustomCopyright {
if !domain.GetBaseEditionLimitation(ctx).AllowCustomCopyright {
appInfo.Settings.WebAppCustomSettings.ShowBrandInfo = &showBrand
appInfo.Settings.DisclaimerSettings.Content = &defaultDisclaimer
} else {

View File

@ -72,8 +72,8 @@ func (u *CommentUsecase) CreateComment(ctx context.Context, commentReq *domain.C
return CommentStr, nil
}
func (u *CommentUsecase) GetCommentListByNodeID(ctx context.Context, nodeID string, edition consts.LicenseEdition) (*domain.PaginatedResult[[]*domain.ShareCommentListItem], error) {
comments, total, err := u.CommentRepo.GetCommentList(ctx, nodeID, edition)
func (u *CommentUsecase) GetCommentListByNodeID(ctx context.Context, nodeID string) (*domain.PaginatedResult[[]*domain.ShareCommentListItem], error) {
comments, total, err := u.CommentRepo.GetCommentList(ctx, nodeID)
if err != nil {
return nil, err
}