feat(canvas): 绘图工具支持自定义 tldraw License Key (#100)
* feat(canvas): 绘图工具 - 支持自定义 tldraw License Key * docs(canvas): 绘图工具 - 添加 License Key 页面截图
This commit is contained in:
@@ -590,6 +590,17 @@ func (r *Repository) SaveSystemSetting(setting *model.SystemSetting) error {
|
||||
return r.db.Save(setting).Error
|
||||
}
|
||||
|
||||
func (r *Repository) SaveSystemSettings(settings ...*model.SystemSetting) error {
|
||||
return r.db.Transaction(func(tx *gorm.DB) error {
|
||||
for _, setting := range settings {
|
||||
if err := tx.Save(setting).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
func (r *Repository) DeleteSystemSetting(key string) error {
|
||||
return r.db.Delete(&model.SystemSetting{}, "key = ?", key).Error
|
||||
}
|
||||
|
||||
@@ -11,7 +11,10 @@ import (
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
const drawingEngineSettingKey = "drawing_engine"
|
||||
const (
|
||||
drawingEngineSettingKey = "drawing_engine"
|
||||
tldrawLicenseKeySettingKey = "tldraw_license_key"
|
||||
)
|
||||
|
||||
const (
|
||||
DrawingEngineTldraw = "tldraw"
|
||||
@@ -19,7 +22,8 @@ const (
|
||||
)
|
||||
|
||||
type DrawingEngineSetting struct {
|
||||
DefaultEngine string `json:"defaultEngine"`
|
||||
DefaultEngine string `json:"defaultEngine"`
|
||||
TldrawLicenseKey string `json:"tldrawLicenseKey"`
|
||||
}
|
||||
|
||||
type PublicDrawingEngineSetting struct {
|
||||
@@ -57,11 +61,22 @@ func (s *Service) UpdateDrawingEngineSetting(actor *model.User, value DrawingEng
|
||||
if err := validateDrawingEngineSetting(value); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
value.TldrawLicenseKey = strings.TrimSpace(value.TldrawLicenseKey)
|
||||
current, before, err := s.readDrawingEngineSetting()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
encoded, err := json.Marshal(value)
|
||||
currentLicenseSetting, _, err := s.readTldrawLicenseKey()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
encoded, err := json.Marshal(struct {
|
||||
DefaultEngine string `json:"defaultEngine"`
|
||||
}{DefaultEngine: value.DefaultEngine})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
encodedLicenseKey, err := json.Marshal(value.TldrawLicenseKey)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -69,10 +84,14 @@ func (s *Service) UpdateDrawingEngineSetting(actor *model.User, value DrawingEng
|
||||
if current != nil {
|
||||
setting.CreatedAt = current.CreatedAt
|
||||
}
|
||||
if err := s.repo.SaveSystemSetting(&setting); err != nil {
|
||||
licenseSetting := model.SystemSetting{Key: tldrawLicenseKeySettingKey, ValueJSON: string(encodedLicenseKey), UpdatedBy: actor.ID}
|
||||
if currentLicenseSetting != nil {
|
||||
licenseSetting.CreatedAt = currentLicenseSetting.CreatedAt
|
||||
}
|
||||
if err := s.repo.SaveSystemSettings(&setting, &licenseSetting); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := s.appendAdminAudit(actor, "drawing_engine.update", "system_setting", drawingEngineSettingKey, "更新默认绘图工具", map[string]any{"before": before, "after": value}); err != nil {
|
||||
if err := s.appendAdminAudit(actor, "drawing_engine.update", "system_setting", drawingEngineSettingKey, "更新绘图工具配置", map[string]any{"before": drawingEngineAuditValue(before), "after": drawingEngineAuditValue(value)}); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return publicDrawingEngineSetting(&setting, value), nil
|
||||
@@ -80,22 +99,42 @@ func (s *Service) UpdateDrawingEngineSetting(actor *model.User, value DrawingEng
|
||||
|
||||
func (s *Service) readDrawingEngineSetting() (*model.SystemSetting, DrawingEngineSetting, error) {
|
||||
setting, err := s.repo.SystemSetting(drawingEngineSettingKey)
|
||||
value := defaultDrawingEngineSetting()
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return nil, defaultDrawingEngineSetting(), nil
|
||||
setting = nil
|
||||
} else if err != nil {
|
||||
return nil, DrawingEngineSetting{}, err
|
||||
} else {
|
||||
if strings.TrimSpace(setting.ValueJSON) == "" || json.Unmarshal([]byte(setting.ValueJSON), &value) != nil {
|
||||
return nil, DrawingEngineSetting{}, errors.New("绘图工具配置格式无效")
|
||||
}
|
||||
if err := validateDrawingEngineSetting(value); err != nil {
|
||||
return nil, DrawingEngineSetting{}, err
|
||||
}
|
||||
}
|
||||
_, licenseKey, err := s.readTldrawLicenseKey()
|
||||
if err != nil {
|
||||
return nil, DrawingEngineSetting{}, err
|
||||
}
|
||||
value := DrawingEngineSetting{}
|
||||
if strings.TrimSpace(setting.ValueJSON) == "" || json.Unmarshal([]byte(setting.ValueJSON), &value) != nil {
|
||||
return nil, DrawingEngineSetting{}, errors.New("绘图工具配置格式无效")
|
||||
}
|
||||
if err := validateDrawingEngineSetting(value); err != nil {
|
||||
return nil, DrawingEngineSetting{}, err
|
||||
}
|
||||
value.TldrawLicenseKey = licenseKey
|
||||
return setting, value, nil
|
||||
}
|
||||
|
||||
func (s *Service) readTldrawLicenseKey() (*model.SystemSetting, string, error) {
|
||||
setting, err := s.repo.SystemSetting(tldrawLicenseKeySettingKey)
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return nil, "", nil
|
||||
}
|
||||
if err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
var value string
|
||||
if strings.TrimSpace(setting.ValueJSON) == "" || json.Unmarshal([]byte(setting.ValueJSON), &value) != nil {
|
||||
return nil, "", errors.New("tldraw License Key 配置格式无效")
|
||||
}
|
||||
return setting, strings.TrimSpace(value), nil
|
||||
}
|
||||
|
||||
func validateDrawingEngineSetting(value DrawingEngineSetting) error {
|
||||
if value.DefaultEngine != DrawingEngineTldraw && value.DefaultEngine != DrawingEngineExcalidraw {
|
||||
return BadAuthRequest("默认绘图工具必须是 tldraw 或 Excalidraw")
|
||||
@@ -103,6 +142,10 @@ func validateDrawingEngineSetting(value DrawingEngineSetting) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func drawingEngineAuditValue(value DrawingEngineSetting) map[string]any {
|
||||
return map[string]any{"defaultEngine": value.DefaultEngine, "hasTldrawLicenseKey": strings.TrimSpace(value.TldrawLicenseKey) != ""}
|
||||
}
|
||||
|
||||
func publicDrawingEngineSetting(setting *model.SystemSetting, value DrawingEngineSetting) *PublicDrawingEngineSetting {
|
||||
result := &PublicDrawingEngineSetting{DrawingEngineSetting: value, Configured: setting != nil}
|
||||
if setting != nil {
|
||||
|
||||
@@ -1,6 +1,16 @@
|
||||
package service
|
||||
|
||||
import "testing"
|
||||
import (
|
||||
"encoding/json"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"infinite-canvas/backend/internal/model"
|
||||
"infinite-canvas/backend/internal/repository"
|
||||
|
||||
"gorm.io/driver/sqlite"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
func TestDefaultDrawingEngineUsesExcalidraw(t *testing.T) {
|
||||
setting := defaultDrawingEngineSetting()
|
||||
@@ -19,3 +29,53 @@ func TestValidateDrawingEngineSetting(t *testing.T) {
|
||||
t.Fatal("validate unknown engine = nil")
|
||||
}
|
||||
}
|
||||
|
||||
func TestUpdateDrawingEngineSettingKeepsDefaultEngineAndStoresTldrawLicenseSeparately(t *testing.T) {
|
||||
db, err := gorm.Open(sqlite.Open(":memory:"), &gorm.Config{})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if sqlDB, err := db.DB(); err == nil {
|
||||
sqlDB.SetMaxOpenConns(1)
|
||||
}
|
||||
if err := db.AutoMigrate(&model.SystemSetting{}, &model.AdminAuditEvent{}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
repo := repository.New(db)
|
||||
svc := New(repo, t.TempDir())
|
||||
actor := &model.User{ID: "admin-1", Role: model.UserRoleAdmin}
|
||||
|
||||
setting, err := svc.UpdateDrawingEngineSetting(actor, DrawingEngineSetting{
|
||||
DefaultEngine: DrawingEngineExcalidraw,
|
||||
TldrawLicenseKey: "license-key-for-test",
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if setting.DefaultEngine != DrawingEngineExcalidraw {
|
||||
t.Fatalf("default engine = %q, want %q", setting.DefaultEngine, DrawingEngineExcalidraw)
|
||||
}
|
||||
if setting.TldrawLicenseKey != "license-key-for-test" {
|
||||
t.Fatalf("license key = %q", setting.TldrawLicenseKey)
|
||||
}
|
||||
|
||||
licenseSetting, err := repo.SystemSetting(tldrawLicenseKeySettingKey)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
var savedLicenseKey string
|
||||
if err := json.Unmarshal([]byte(licenseSetting.ValueJSON), &savedLicenseKey); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if savedLicenseKey != "license-key-for-test" {
|
||||
t.Fatalf("stored license key = %q", savedLicenseKey)
|
||||
}
|
||||
|
||||
engineSetting, err := repo.SystemSetting(drawingEngineSettingKey)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if strings.Contains(engineSetting.ValueJSON, "license-key-for-test") {
|
||||
t.Fatalf("drawing engine setting must not contain the license key: %s", engineSetting.ValueJSON)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user