🎨 Optimize storage logic
This commit is contained in:
@@ -124,6 +124,7 @@ type AppConfig struct {
|
||||
Editing EditingConfig `json:"editing"` // 编辑设置
|
||||
Appearance AppearanceConfig `json:"appearance"` // 外观设置
|
||||
Updates UpdatesConfig `json:"updates"` // 更新设置
|
||||
Sync GitSyncConfig `json:"sync"` // Git同步设置
|
||||
Metadata ConfigMetadata `json:"metadata"` // 配置元数据
|
||||
}
|
||||
|
||||
@@ -138,6 +139,7 @@ func NewDefaultAppConfig() *AppConfig {
|
||||
|
||||
currentDir, _ := os.UserHomeDir()
|
||||
dataDir := filepath.Join(currentDir, ".voidraft", "data")
|
||||
repoPath := filepath.Join(currentDir, ".voidraft", "sync-repo")
|
||||
|
||||
return &AppConfig{
|
||||
General: GeneralConfig{
|
||||
@@ -173,7 +175,7 @@ func NewDefaultAppConfig() *AppConfig {
|
||||
CustomTheme: *NewDefaultCustomThemeConfig(),
|
||||
},
|
||||
Updates: UpdatesConfig{
|
||||
Version: "1.0.0",
|
||||
Version: "1.2.0",
|
||||
AutoUpdate: true,
|
||||
PrimarySource: UpdateSourceGithub,
|
||||
BackupSource: UpdateSourceGitea,
|
||||
@@ -189,9 +191,25 @@ func NewDefaultAppConfig() *AppConfig {
|
||||
Repo: "voidraft",
|
||||
},
|
||||
},
|
||||
Sync: GitSyncConfig{
|
||||
Enabled: false,
|
||||
RepoURL: "",
|
||||
Branch: "main",
|
||||
AuthMethod: Token,
|
||||
Username: "",
|
||||
Password: "",
|
||||
Token: "",
|
||||
SSHKeyPath: "",
|
||||
SyncInterval: 60,
|
||||
LastSyncTime: time.Time{},
|
||||
AutoSync: true,
|
||||
LocalRepoPath: repoPath,
|
||||
SyncStrategy: LocalFirst,
|
||||
FilesToSync: []string{"voidraft.db"},
|
||||
},
|
||||
Metadata: ConfigMetadata{
|
||||
LastUpdated: time.Now().Format(time.RFC3339),
|
||||
Version: "1.0.0",
|
||||
Version: "1.2.0",
|
||||
},
|
||||
}
|
||||
}
|
||||
|
@@ -12,9 +12,10 @@ type Document struct {
|
||||
CreatedAt time.Time `json:"createdAt" db:"created_at"`
|
||||
UpdatedAt time.Time `json:"updatedAt" db:"updated_at"`
|
||||
IsDeleted bool `json:"is_deleted"`
|
||||
IsLocked bool `json:"is_locked" db:"is_locked"` // 锁定标志,锁定的文档无法被删除
|
||||
}
|
||||
|
||||
// NewDocument 创建新文档(不需要传ID,由数据库自增)
|
||||
// NewDocument 创建新文档
|
||||
func NewDocument(title, content string) *Document {
|
||||
now := time.Now()
|
||||
return &Document{
|
||||
@@ -23,6 +24,7 @@ func NewDocument(title, content string) *Document {
|
||||
CreatedAt: now,
|
||||
UpdatedAt: now,
|
||||
IsDeleted: false,
|
||||
IsLocked: false, // 默认不锁定
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -4,10 +4,10 @@ import "time"
|
||||
|
||||
// Extension 单个扩展配置
|
||||
type Extension struct {
|
||||
ID ExtensionID `json:"id"` // 扩展唯一标识
|
||||
Enabled bool `json:"enabled"` // 是否启用
|
||||
IsDefault bool `json:"isDefault"` // 是否为默认扩展
|
||||
Config ExtensionConfig `json:"config"` // 扩展配置项
|
||||
ID ExtensionID `json:"id" db:"id"` // 扩展唯一标识
|
||||
Enabled bool `json:"enabled" db:"enabled"` // 是否启用
|
||||
IsDefault bool `json:"isDefault" db:"is_default"` // 是否为默认扩展
|
||||
Config ExtensionConfig `json:"config" db:"config"` // 扩展配置项
|
||||
}
|
||||
|
||||
// ExtensionID 扩展标识符
|
||||
|
@@ -4,11 +4,11 @@ import "time"
|
||||
|
||||
// KeyBinding 单个快捷键绑定
|
||||
type KeyBinding struct {
|
||||
Command KeyBindingCommand `json:"command"` // 快捷键动作
|
||||
Extension ExtensionID `json:"extension"` // 所属扩展
|
||||
Key string `json:"key"` // 快捷键组合(如 "Mod-f", "Ctrl-Shift-p")
|
||||
Enabled bool `json:"enabled"` // 是否启用
|
||||
IsDefault bool `json:"isDefault"` // 是否为默认快捷键
|
||||
Command KeyBindingCommand `json:"command" db:"command"` // 快捷键动作
|
||||
Extension ExtensionID `json:"extension" db:"extension"` // 所属扩展
|
||||
Key string `json:"key" db:"key"` // 快捷键组合(如 "Mod-f", "Ctrl-Shift-p")
|
||||
Enabled bool `json:"enabled" db:"enabled"` // 是否启用
|
||||
IsDefault bool `json:"isDefault" db:"is_default"` // 是否为默认快捷键
|
||||
}
|
||||
|
||||
// KeyBindingCommand 快捷键命令
|
||||
|
63
internal/models/sync.go
Normal file
63
internal/models/sync.go
Normal file
@@ -0,0 +1,63 @@
|
||||
package models
|
||||
|
||||
import "time"
|
||||
|
||||
// AuthMethod 定义Git认证方式
|
||||
type AuthMethod string
|
||||
|
||||
const (
|
||||
Token AuthMethod = "token" // 个人访问令牌
|
||||
SSHKey AuthMethod = "ssh_key" // SSH密钥
|
||||
UserPass AuthMethod = "user_pass" // 用户名密码
|
||||
)
|
||||
|
||||
// SyncStrategy 定义同步策略
|
||||
type SyncStrategy string
|
||||
|
||||
const (
|
||||
// LocalFirst 本地优先:如有冲突,保留本地修改
|
||||
LocalFirst SyncStrategy = "local_first"
|
||||
// RemoteFirst 远程优先:如有冲突,采用远程版本
|
||||
RemoteFirst SyncStrategy = "remote_first"
|
||||
)
|
||||
|
||||
// GitSyncConfig 保存Git同步的配置信息
|
||||
type GitSyncConfig struct {
|
||||
Enabled bool `json:"enabled"`
|
||||
RepoURL string `json:"repo_url"`
|
||||
Branch string `json:"branch"`
|
||||
AuthMethod AuthMethod `json:"auth_method"`
|
||||
Username string `json:"username,omitempty"`
|
||||
Password string `json:"password,omitempty"`
|
||||
Token string `json:"token,omitempty"`
|
||||
SSHKeyPath string `json:"ssh_key_path,omitempty"`
|
||||
SSHKeyPassphrase string `json:"ssh_key_passphrase,omitempty"`
|
||||
SyncInterval int `json:"sync_interval"` // 同步间隔(分钟)
|
||||
LastSyncTime time.Time `json:"last_sync_time"`
|
||||
AutoSync bool `json:"auto_sync"` // 是否启用自动同步
|
||||
LocalRepoPath string `json:"local_repo_path"`
|
||||
SyncStrategy SyncStrategy `json:"sync_strategy"` // 合并冲突策略
|
||||
FilesToSync []string `json:"files_to_sync"` // 要同步的文件列表,默认为数据库文件
|
||||
}
|
||||
|
||||
// GitSyncStatus 保存同步状态信息
|
||||
type GitSyncStatus struct {
|
||||
IsSyncing bool `json:"is_syncing"`
|
||||
LastSyncTime time.Time `json:"last_sync_time"`
|
||||
LastSyncStatus string `json:"last_sync_status"` // success, failed, conflict
|
||||
LastErrorMsg string `json:"last_error_msg,omitempty"`
|
||||
LastCommitID string `json:"last_commit_id,omitempty"`
|
||||
RemoteCommitID string `json:"remote_commit_id,omitempty"`
|
||||
CommitAhead int `json:"commit_ahead"` // 本地领先远程的提交数
|
||||
CommitBehind int `json:"commit_behind"` // 本地落后远程的提交数
|
||||
}
|
||||
|
||||
// SyncLogEntry 记录每次同步操作的日志
|
||||
type SyncLogEntry struct {
|
||||
ID int64 `json:"id"`
|
||||
Timestamp time.Time `json:"timestamp"`
|
||||
Action string `json:"action"` // push, pull, reset
|
||||
Status string `json:"status"` // success, failed
|
||||
Message string `json:"message,omitempty"`
|
||||
ChangedFiles int `json:"changed_files"`
|
||||
}
|
@@ -5,7 +5,11 @@ import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"reflect"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
"voidraft/internal/models"
|
||||
|
||||
"github.com/wailsapp/wails/v3/pkg/application"
|
||||
"github.com/wailsapp/wails/v3/pkg/services/log"
|
||||
@@ -31,7 +35,8 @@ CREATE TABLE IF NOT EXISTS documents (
|
||||
content TEXT DEFAULT '∞∞∞text-a',
|
||||
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||
is_deleted INTEGER DEFAULT 0
|
||||
is_deleted INTEGER DEFAULT 0,
|
||||
is_locked INTEGER DEFAULT 0
|
||||
)`
|
||||
|
||||
// Extensions table
|
||||
@@ -58,8 +63,34 @@ CREATE TABLE IF NOT EXISTS key_bindings (
|
||||
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||
UNIQUE(command, extension)
|
||||
)`
|
||||
|
||||
// Git sync logs table
|
||||
sqlCreateSyncLogsTable = `
|
||||
CREATE TABLE IF NOT EXISTS sync_logs (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
timestamp DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||
action TEXT NOT NULL,
|
||||
status TEXT NOT NULL,
|
||||
message TEXT,
|
||||
commit_id TEXT,
|
||||
changed_files INTEGER DEFAULT 0,
|
||||
repo_url TEXT NOT NULL,
|
||||
branch TEXT NOT NULL
|
||||
)`
|
||||
)
|
||||
|
||||
// ColumnInfo 存储列的信息
|
||||
type ColumnInfo struct {
|
||||
SQLType string
|
||||
DefaultValue string
|
||||
}
|
||||
|
||||
// TableModel 表示表与模型之间的映射关系
|
||||
type TableModel struct {
|
||||
TableName string
|
||||
Model interface{}
|
||||
}
|
||||
|
||||
// DatabaseService provides shared database functionality
|
||||
type DatabaseService struct {
|
||||
configService *ConfigService
|
||||
@@ -67,6 +98,7 @@ type DatabaseService struct {
|
||||
SQLite *sqlite.Service
|
||||
mu sync.RWMutex
|
||||
ctx context.Context
|
||||
tableModels []TableModel // 注册的表模型
|
||||
}
|
||||
|
||||
// NewDatabaseService creates a new database service
|
||||
@@ -75,11 +107,28 @@ func NewDatabaseService(configService *ConfigService, logger *log.Service) *Data
|
||||
logger = log.New()
|
||||
}
|
||||
|
||||
return &DatabaseService{
|
||||
ds := &DatabaseService{
|
||||
configService: configService,
|
||||
logger: logger,
|
||||
SQLite: sqlite.New(),
|
||||
}
|
||||
|
||||
// 注册所有模型
|
||||
ds.registerAllModels()
|
||||
|
||||
return ds
|
||||
}
|
||||
|
||||
// registerAllModels 注册所有数据模型,集中管理表-模型映射
|
||||
func (ds *DatabaseService) registerAllModels() {
|
||||
// 文档表
|
||||
ds.RegisterModel("documents", &models.Document{})
|
||||
// 扩展表
|
||||
ds.RegisterModel("extensions", &models.Extension{})
|
||||
// 快捷键表
|
||||
ds.RegisterModel("key_bindings", &models.KeyBinding{})
|
||||
// 同步日志表
|
||||
ds.RegisterModel("sync_logs", &models.SyncLogEntry{})
|
||||
}
|
||||
|
||||
// ServiceStartup initializes the service when the application starts
|
||||
@@ -135,6 +184,11 @@ func (ds *DatabaseService) initDatabase() error {
|
||||
return fmt.Errorf("failed to create indexes: %w", err)
|
||||
}
|
||||
|
||||
// 执行模型与表结构同步
|
||||
if err := ds.syncAllModelTables(); err != nil {
|
||||
return fmt.Errorf("failed to sync model tables: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -153,6 +207,7 @@ func (ds *DatabaseService) createTables() error {
|
||||
sqlCreateDocumentsTable,
|
||||
sqlCreateExtensionsTable,
|
||||
sqlCreateKeyBindingsTable,
|
||||
sqlCreateSyncLogsTable,
|
||||
}
|
||||
|
||||
for _, table := range tables {
|
||||
@@ -176,6 +231,10 @@ func (ds *DatabaseService) createIndexes() error {
|
||||
`CREATE INDEX IF NOT EXISTS idx_key_bindings_command ON key_bindings(command)`,
|
||||
`CREATE INDEX IF NOT EXISTS idx_key_bindings_extension ON key_bindings(extension)`,
|
||||
`CREATE INDEX IF NOT EXISTS idx_key_bindings_enabled ON key_bindings(enabled)`,
|
||||
// Sync logs indexes
|
||||
`CREATE INDEX IF NOT EXISTS idx_sync_logs_timestamp ON sync_logs(timestamp DESC)`,
|
||||
`CREATE INDEX IF NOT EXISTS idx_sync_logs_action ON sync_logs(action)`,
|
||||
`CREATE INDEX IF NOT EXISTS idx_sync_logs_status ON sync_logs(status)`,
|
||||
}
|
||||
|
||||
for _, index := range indexes {
|
||||
@@ -186,6 +245,149 @@ func (ds *DatabaseService) createIndexes() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// RegisterModel 注册模型与表的映射关系
|
||||
func (ds *DatabaseService) RegisterModel(tableName string, model interface{}) {
|
||||
ds.mu.Lock()
|
||||
defer ds.mu.Unlock()
|
||||
|
||||
ds.tableModels = append(ds.tableModels, TableModel{
|
||||
TableName: tableName,
|
||||
Model: model,
|
||||
})
|
||||
}
|
||||
|
||||
// syncAllModelTables 同步所有注册的模型与表结构
|
||||
func (ds *DatabaseService) syncAllModelTables() error {
|
||||
for _, tm := range ds.tableModels {
|
||||
if err := ds.syncModelTable(tm.TableName, tm.Model); err != nil {
|
||||
return fmt.Errorf("failed to sync table %s: %w", tm.TableName, err)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// syncModelTable 同步模型与表结构
|
||||
func (ds *DatabaseService) syncModelTable(tableName string, model interface{}) error {
|
||||
// 获取表结构元数据
|
||||
columns, err := ds.getTableColumns(tableName)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get table columns: %w", err)
|
||||
}
|
||||
|
||||
// 使用反射从模型中提取字段信息
|
||||
expectedColumns, err := ds.getModelColumns(model)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get model columns: %w", err)
|
||||
}
|
||||
|
||||
// 检查缺失的列并添加
|
||||
for colName, colInfo := range expectedColumns {
|
||||
if _, exists := columns[colName]; !exists {
|
||||
// 执行添加列的SQL
|
||||
alterSQL := fmt.Sprintf("ALTER TABLE %s ADD COLUMN %s %s DEFAULT %s",
|
||||
tableName, colName, colInfo.SQLType, colInfo.DefaultValue)
|
||||
if err := ds.SQLite.Execute(alterSQL); err != nil {
|
||||
return fmt.Errorf("failed to add column %s: %w", colName, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// getTableColumns 获取表的列信息
|
||||
func (ds *DatabaseService) getTableColumns(table string) (map[string]string, error) {
|
||||
query := fmt.Sprintf("PRAGMA table_info(%s)", table)
|
||||
rows, err := ds.SQLite.Query(query)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
columns := make(map[string]string)
|
||||
for _, row := range rows {
|
||||
name, ok1 := row["name"].(string)
|
||||
typeName, ok2 := row["type"].(string)
|
||||
|
||||
if !ok1 || !ok2 {
|
||||
continue
|
||||
}
|
||||
|
||||
columns[name] = typeName
|
||||
}
|
||||
|
||||
return columns, nil
|
||||
}
|
||||
|
||||
// getModelColumns 从模型结构体中提取数据库列信息
|
||||
func (ds *DatabaseService) getModelColumns(model interface{}) (map[string]ColumnInfo, error) {
|
||||
columns := make(map[string]ColumnInfo)
|
||||
|
||||
// 使用反射获取结构体的类型信息
|
||||
t := reflect.TypeOf(model)
|
||||
if t.Kind() == reflect.Ptr {
|
||||
t = t.Elem()
|
||||
}
|
||||
|
||||
if t.Kind() != reflect.Struct {
|
||||
return nil, fmt.Errorf("model must be a struct or a pointer to struct")
|
||||
}
|
||||
|
||||
// 遍历所有字段
|
||||
for i := 0; i < t.NumField(); i++ {
|
||||
field := t.Field(i)
|
||||
|
||||
// 获取数据库字段名
|
||||
dbTag := field.Tag.Get("db")
|
||||
if dbTag == "" {
|
||||
// 如果没有db标签,则使用字段名的蛇形命名方式
|
||||
dbTag = toSnakeCase(field.Name)
|
||||
}
|
||||
|
||||
// 获取字段类型对应的SQL类型和默认值
|
||||
sqlType, defaultVal := getSQLTypeAndDefault(field.Type)
|
||||
|
||||
columns[dbTag] = ColumnInfo{
|
||||
SQLType: sqlType,
|
||||
DefaultValue: defaultVal,
|
||||
}
|
||||
}
|
||||
|
||||
return columns, nil
|
||||
}
|
||||
|
||||
// toSnakeCase 将驼峰命名转换为蛇形命名
|
||||
func toSnakeCase(s string) string {
|
||||
var result strings.Builder
|
||||
for i, r := range s {
|
||||
if i > 0 && 'A' <= r && r <= 'Z' {
|
||||
result.WriteRune('_')
|
||||
}
|
||||
result.WriteRune(r)
|
||||
}
|
||||
return strings.ToLower(result.String())
|
||||
}
|
||||
|
||||
// getSQLTypeAndDefault 根据Go类型获取对应的SQL类型和默认值
|
||||
func getSQLTypeAndDefault(t reflect.Type) (string, string) {
|
||||
switch t.Kind() {
|
||||
case reflect.Bool:
|
||||
return "INTEGER", "0"
|
||||
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64,
|
||||
reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
|
||||
return "INTEGER", "0"
|
||||
case reflect.Float32, reflect.Float64:
|
||||
return "REAL", "0.0"
|
||||
case reflect.String:
|
||||
return "TEXT", "''"
|
||||
default:
|
||||
// 处理特殊类型
|
||||
if t == reflect.TypeOf(time.Time{}) {
|
||||
return "DATETIME", "CURRENT_TIMESTAMP"
|
||||
}
|
||||
return "TEXT", "NULL"
|
||||
}
|
||||
}
|
||||
|
||||
// ServiceShutdown shuts down the service when the application closes
|
||||
func (ds *DatabaseService) ServiceShutdown() error {
|
||||
return ds.SQLite.Close()
|
||||
|
@@ -18,28 +18,28 @@ const (
|
||||
|
||||
// Document operations
|
||||
sqlGetDocumentByID = `
|
||||
SELECT id, title, content, created_at, updated_at, is_deleted
|
||||
SELECT id, title, content, created_at, updated_at, is_deleted, is_locked
|
||||
FROM documents
|
||||
WHERE id = ?`
|
||||
|
||||
sqlInsertDocument = `
|
||||
INSERT INTO documents (title, content, created_at, updated_at, is_deleted)
|
||||
VALUES (?, ?, ?, ?, 0)`
|
||||
INSERT INTO documents (title, content, created_at, updated_at, is_deleted, is_locked)
|
||||
VALUES (?, ?, ?, ?, 0, 0)`
|
||||
|
||||
sqlUpdateDocumentContent = `
|
||||
UPDATE documents
|
||||
SET content = ?, updated_at = ?
|
||||
WHERE id = ?`
|
||||
WHERE id = ? AND is_deleted = 0`
|
||||
|
||||
sqlUpdateDocumentTitle = `
|
||||
UPDATE documents
|
||||
SET title = ?, updated_at = ?
|
||||
WHERE id = ?`
|
||||
WHERE id = ? AND is_deleted = 0`
|
||||
|
||||
sqlMarkDocumentAsDeleted = `
|
||||
UPDATE documents
|
||||
SET is_deleted = 1, updated_at = ?
|
||||
WHERE id = ?`
|
||||
WHERE id = ? AND is_locked = 0`
|
||||
|
||||
sqlRestoreDocument = `
|
||||
UPDATE documents
|
||||
@@ -47,13 +47,13 @@ SET is_deleted = 0, updated_at = ?
|
||||
WHERE id = ?`
|
||||
|
||||
sqlListAllDocumentsMeta = `
|
||||
SELECT id, title, created_at, updated_at
|
||||
SELECT id, title, created_at, updated_at, is_locked
|
||||
FROM documents
|
||||
WHERE is_deleted = 0
|
||||
ORDER BY updated_at DESC`
|
||||
|
||||
sqlListDeletedDocumentsMeta = `
|
||||
SELECT id, title, created_at, updated_at
|
||||
SELECT id, title, created_at, updated_at, is_locked
|
||||
FROM documents
|
||||
WHERE is_deleted = 1
|
||||
ORDER BY updated_at DESC`
|
||||
@@ -63,6 +63,16 @@ SELECT id FROM documents WHERE is_deleted = 0 ORDER BY id LIMIT 1`
|
||||
|
||||
sqlCountDocuments = `SELECT COUNT(*) FROM documents WHERE is_deleted = 0`
|
||||
|
||||
sqlSetDocumentLocked = `
|
||||
UPDATE documents
|
||||
SET is_locked = 1, updated_at = ?
|
||||
WHERE id = ?`
|
||||
|
||||
sqlSetDocumentUnlocked = `
|
||||
UPDATE documents
|
||||
SET is_locked = 0, updated_at = ?
|
||||
WHERE id = ?`
|
||||
|
||||
sqlDefaultDocumentID = 1 // 默认文档的ID
|
||||
)
|
||||
|
||||
@@ -80,16 +90,19 @@ func NewDocumentService(databaseService *DatabaseService, logger *log.Service) *
|
||||
logger = log.New()
|
||||
}
|
||||
|
||||
return &DocumentService{
|
||||
ds := &DocumentService{
|
||||
databaseService: databaseService,
|
||||
logger: logger,
|
||||
}
|
||||
|
||||
return ds
|
||||
}
|
||||
|
||||
// ServiceStartup initializes the service when the application starts
|
||||
func (ds *DocumentService) ServiceStartup(ctx context.Context, options application.ServiceOptions) error {
|
||||
ds.ctx = ctx
|
||||
// Ensure default document exists
|
||||
|
||||
// 确保默认文档存在
|
||||
if err := ds.ensureDefaultDocument(); err != nil {
|
||||
return fmt.Errorf("failed to ensure default document: %w", err)
|
||||
}
|
||||
@@ -170,6 +183,10 @@ func (ds *DocumentService) GetDocumentByID(id int64) (*models.Document, error) {
|
||||
doc.IsDeleted = isDeletedInt == 1
|
||||
}
|
||||
|
||||
if isLockedInt, ok := row["is_locked"].(int64); ok {
|
||||
doc.IsLocked = isLockedInt == 1
|
||||
}
|
||||
|
||||
return doc, nil
|
||||
}
|
||||
|
||||
@@ -186,6 +203,7 @@ func (ds *DocumentService) CreateDocument(title string) (*models.Document, error
|
||||
CreatedAt: now,
|
||||
UpdatedAt: now,
|
||||
IsDeleted: false,
|
||||
IsLocked: false,
|
||||
}
|
||||
|
||||
// 执行插入操作
|
||||
@@ -215,6 +233,61 @@ func (ds *DocumentService) CreateDocument(title string) (*models.Document, error
|
||||
return doc, nil
|
||||
}
|
||||
|
||||
// LockDocument 锁定文档,防止删除
|
||||
func (ds *DocumentService) LockDocument(id int64) error {
|
||||
ds.mu.Lock()
|
||||
defer ds.mu.Unlock()
|
||||
|
||||
// 检查文档是否存在且未删除
|
||||
doc, err := ds.GetDocumentByID(id)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get document: %w", err)
|
||||
}
|
||||
if doc == nil {
|
||||
return fmt.Errorf("document not found: %d", id)
|
||||
}
|
||||
if doc.IsDeleted {
|
||||
return fmt.Errorf("cannot lock deleted document: %d", id)
|
||||
}
|
||||
|
||||
// 如果已经锁定,无需操作
|
||||
if doc.IsLocked {
|
||||
return nil
|
||||
}
|
||||
|
||||
err = ds.databaseService.SQLite.Execute(sqlSetDocumentLocked, time.Now(), id)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to lock document: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// UnlockDocument 解锁文档
|
||||
func (ds *DocumentService) UnlockDocument(id int64) error {
|
||||
ds.mu.Lock()
|
||||
defer ds.mu.Unlock()
|
||||
|
||||
// 检查文档是否存在
|
||||
doc, err := ds.GetDocumentByID(id)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get document: %w", err)
|
||||
}
|
||||
if doc == nil {
|
||||
return fmt.Errorf("document not found: %d", id)
|
||||
}
|
||||
|
||||
// 如果未锁定,无需操作
|
||||
if !doc.IsLocked {
|
||||
return nil
|
||||
}
|
||||
|
||||
err = ds.databaseService.SQLite.Execute(sqlSetDocumentUnlocked, time.Now(), id)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to unlock document: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// UpdateDocumentContent updates the content of a document
|
||||
func (ds *DocumentService) UpdateDocumentContent(id int64, content string) error {
|
||||
ds.mu.Lock()
|
||||
@@ -249,7 +322,19 @@ func (ds *DocumentService) DeleteDocument(id int64) error {
|
||||
return fmt.Errorf("cannot delete the default document")
|
||||
}
|
||||
|
||||
err := ds.databaseService.SQLite.Execute(sqlMarkDocumentAsDeleted, time.Now(), id)
|
||||
// 检查文档是否锁定
|
||||
doc, err := ds.GetDocumentByID(id)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get document: %w", err)
|
||||
}
|
||||
if doc == nil {
|
||||
return fmt.Errorf("document not found: %d", id)
|
||||
}
|
||||
if doc.IsLocked {
|
||||
return fmt.Errorf("cannot delete locked document: %d", id)
|
||||
}
|
||||
|
||||
err = ds.databaseService.SQLite.Execute(sqlMarkDocumentAsDeleted, time.Now(), id)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to mark document as deleted: %w", err)
|
||||
}
|
||||
@@ -304,6 +389,10 @@ func (ds *DocumentService) ListAllDocumentsMeta() ([]*models.Document, error) {
|
||||
}
|
||||
}
|
||||
|
||||
if isLockedInt, ok := row["is_locked"].(int64); ok {
|
||||
doc.IsLocked = isLockedInt == 1
|
||||
}
|
||||
|
||||
documents = append(documents, doc)
|
||||
}
|
||||
|
||||
@@ -346,6 +435,10 @@ func (ds *DocumentService) ListDeletedDocumentsMeta() ([]*models.Document, error
|
||||
}
|
||||
}
|
||||
|
||||
if isLockedInt, ok := row["is_locked"].(int64); ok {
|
||||
doc.IsLocked = isLockedInt == 1
|
||||
}
|
||||
|
||||
documents = append(documents, doc)
|
||||
}
|
||||
|
||||
|
Reference in New Issue
Block a user