485 lines
12 KiB
Go
485 lines
12 KiB
Go
package services
|
|
|
|
import (
|
|
"context"
|
|
"database/sql"
|
|
"errors"
|
|
"fmt"
|
|
"sync"
|
|
"time"
|
|
"voidraft/internal/models"
|
|
|
|
"github.com/wailsapp/wails/v3/pkg/application"
|
|
"github.com/wailsapp/wails/v3/pkg/services/log"
|
|
)
|
|
|
|
// SQL constants for document operations
|
|
const (
|
|
|
|
// Document operations
|
|
sqlGetDocumentByID = `
|
|
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, is_locked)
|
|
VALUES (?, ?, ?, ?, 0, 0)`
|
|
|
|
sqlUpdateDocumentContent = `
|
|
UPDATE documents
|
|
SET content = ?, updated_at = ?
|
|
WHERE id = ? AND is_deleted = 0`
|
|
|
|
sqlUpdateDocumentTitle = `
|
|
UPDATE documents
|
|
SET title = ?, updated_at = ?
|
|
WHERE id = ? AND is_deleted = 0`
|
|
|
|
sqlMarkDocumentAsDeleted = `
|
|
UPDATE documents
|
|
SET is_deleted = 1, updated_at = ?
|
|
WHERE id = ? AND is_locked = 0`
|
|
|
|
sqlRestoreDocument = `
|
|
UPDATE documents
|
|
SET is_deleted = 0, updated_at = ?
|
|
WHERE id = ?`
|
|
|
|
sqlListAllDocumentsMeta = `
|
|
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, is_locked
|
|
FROM documents
|
|
WHERE is_deleted = 1
|
|
ORDER BY updated_at DESC`
|
|
|
|
sqlGetFirstDocumentID = `
|
|
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
|
|
)
|
|
|
|
// DocumentService provides document management functionality
|
|
type DocumentService struct {
|
|
databaseService *DatabaseService
|
|
logger *log.Service
|
|
mu sync.RWMutex
|
|
ctx context.Context
|
|
}
|
|
|
|
// NewDocumentService creates a new document service
|
|
func NewDocumentService(databaseService *DatabaseService, logger *log.Service) *DocumentService {
|
|
if logger == nil {
|
|
logger = log.New()
|
|
}
|
|
|
|
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
|
|
|
|
// 确保默认文档存在
|
|
if err := ds.ensureDefaultDocument(); err != nil {
|
|
return fmt.Errorf("failed to ensure default document: %w", err)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// ensureDefaultDocument ensures a default document exists
|
|
func (ds *DocumentService) ensureDefaultDocument() error {
|
|
if ds.databaseService == nil || ds.databaseService.db == nil {
|
|
return errors.New("database service not available")
|
|
}
|
|
|
|
// Check if any document exists
|
|
var count int64
|
|
err := ds.databaseService.db.QueryRow(sqlCountDocuments).Scan(&count)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to query document count: %w", err)
|
|
}
|
|
|
|
// If no documents exist, create default document
|
|
if count == 0 {
|
|
defaultDoc := models.NewDefaultDocument()
|
|
_, err := ds.CreateDocument(defaultDoc.Title)
|
|
return err
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// GetDocumentByID gets a document by ID
|
|
func (ds *DocumentService) GetDocumentByID(id int64) (*models.Document, error) {
|
|
ds.mu.RLock()
|
|
defer ds.mu.RUnlock()
|
|
|
|
if ds.databaseService == nil || ds.databaseService.db == nil {
|
|
return nil, errors.New("database service not available")
|
|
}
|
|
|
|
doc := &models.Document{}
|
|
var createdAt, updatedAt string
|
|
var isDeleted, isLocked int
|
|
|
|
err := ds.databaseService.db.QueryRow(sqlGetDocumentByID, id).Scan(
|
|
&doc.ID,
|
|
&doc.Title,
|
|
&doc.Content,
|
|
&createdAt,
|
|
&updatedAt,
|
|
&isDeleted,
|
|
&isLocked,
|
|
)
|
|
|
|
if err != nil {
|
|
if errors.Is(err, sql.ErrNoRows) {
|
|
return nil, nil
|
|
}
|
|
return nil, fmt.Errorf("failed to get document by ID: %w", err)
|
|
}
|
|
|
|
// 转换时间字段
|
|
if t, err := time.Parse("2006-01-02 15:04:05", createdAt); err == nil {
|
|
doc.CreatedAt = t
|
|
}
|
|
if t, err := time.Parse("2006-01-02 15:04:05", updatedAt); err == nil {
|
|
doc.UpdatedAt = t
|
|
}
|
|
|
|
// 转换布尔字段
|
|
doc.IsDeleted = isDeleted == 1
|
|
doc.IsLocked = isLocked == 1
|
|
|
|
return doc, nil
|
|
}
|
|
|
|
// CreateDocument creates a new document and returns the created document with ID
|
|
func (ds *DocumentService) CreateDocument(title string) (*models.Document, error) {
|
|
ds.mu.Lock()
|
|
defer ds.mu.Unlock()
|
|
|
|
if ds.databaseService == nil || ds.databaseService.db == nil {
|
|
return nil, errors.New("database service not available")
|
|
}
|
|
|
|
// Create document with default content
|
|
now := time.Now()
|
|
doc := &models.Document{
|
|
Title: title,
|
|
Content: "∞∞∞text-a\n",
|
|
CreatedAt: now,
|
|
UpdatedAt: now,
|
|
IsDeleted: false,
|
|
IsLocked: false,
|
|
}
|
|
|
|
// 执行插入操作
|
|
result, err := ds.databaseService.db.Exec(sqlInsertDocument,
|
|
doc.Title, doc.Content, doc.CreatedAt.Format("2006-01-02 15:04:05"), doc.UpdatedAt.Format("2006-01-02 15:04:05"))
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to create document: %w", err)
|
|
}
|
|
|
|
// 获取自增ID
|
|
lastID, err := result.LastInsertId()
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to get last insert ID: %w", err)
|
|
}
|
|
|
|
// 返回带ID的文档
|
|
doc.ID = lastID
|
|
return doc, nil
|
|
}
|
|
|
|
// LockDocument 锁定文档,防止删除
|
|
func (ds *DocumentService) LockDocument(id int64) error {
|
|
ds.mu.Lock()
|
|
defer ds.mu.Unlock()
|
|
|
|
if ds.databaseService == nil || ds.databaseService.db == nil {
|
|
return errors.New("database service not available")
|
|
}
|
|
|
|
// 检查文档是否存在且未删除
|
|
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.db.Exec(sqlSetDocumentLocked, time.Now().Format("2006-01-02 15:04:05"), 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()
|
|
|
|
if ds.databaseService == nil || ds.databaseService.db == nil {
|
|
return errors.New("database service not available")
|
|
}
|
|
|
|
// 检查文档是否存在
|
|
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.db.Exec(sqlSetDocumentUnlocked, time.Now().Format("2006-01-02 15:04:05"), 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()
|
|
defer ds.mu.Unlock()
|
|
|
|
if ds.databaseService == nil || ds.databaseService.db == nil {
|
|
return errors.New("database service not available")
|
|
}
|
|
|
|
_, err := ds.databaseService.db.Exec(sqlUpdateDocumentContent, content, time.Now().Format("2006-01-02 15:04:05"), id)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to update document content: %w", err)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// UpdateDocumentTitle updates the title of a document
|
|
func (ds *DocumentService) UpdateDocumentTitle(id int64, title string) error {
|
|
ds.mu.Lock()
|
|
defer ds.mu.Unlock()
|
|
|
|
if ds.databaseService == nil || ds.databaseService.db == nil {
|
|
return errors.New("database service not available")
|
|
}
|
|
|
|
_, err := ds.databaseService.db.Exec(sqlUpdateDocumentTitle, title, time.Now().Format("2006-01-02 15:04:05"), id)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to update document title: %w", err)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// DeleteDocument marks a document as deleted (default document with ID=1 cannot be deleted)
|
|
func (ds *DocumentService) DeleteDocument(id int64) error {
|
|
ds.mu.Lock()
|
|
defer ds.mu.Unlock()
|
|
|
|
if ds.databaseService == nil || ds.databaseService.db == nil {
|
|
return errors.New("database service not available")
|
|
}
|
|
|
|
// 不允许删除默认文档
|
|
if id == sqlDefaultDocumentID {
|
|
return fmt.Errorf("cannot delete the default document")
|
|
}
|
|
|
|
// 检查文档是否锁定
|
|
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.db.Exec(sqlMarkDocumentAsDeleted, time.Now().Format("2006-01-02 15:04:05"), id)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to mark document as deleted: %w", err)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// RestoreDocument restores a deleted document
|
|
func (ds *DocumentService) RestoreDocument(id int64) error {
|
|
ds.mu.Lock()
|
|
defer ds.mu.Unlock()
|
|
|
|
if ds.databaseService == nil || ds.databaseService.db == nil {
|
|
return errors.New("database service not available")
|
|
}
|
|
|
|
_, err := ds.databaseService.db.Exec(sqlRestoreDocument, time.Now().Format("2006-01-02 15:04:05"), id)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to restore document: %w", err)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// ListAllDocumentsMeta lists all active (non-deleted) document metadata
|
|
func (ds *DocumentService) ListAllDocumentsMeta() ([]*models.Document, error) {
|
|
ds.mu.RLock()
|
|
defer ds.mu.RUnlock()
|
|
|
|
if ds.databaseService == nil || ds.databaseService.db == nil {
|
|
return nil, errors.New("database service not available")
|
|
}
|
|
|
|
rows, err := ds.databaseService.db.Query(sqlListAllDocumentsMeta)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to list document meta: %w", err)
|
|
}
|
|
defer rows.Close()
|
|
|
|
var documents []*models.Document
|
|
for rows.Next() {
|
|
doc := &models.Document{IsDeleted: false}
|
|
var createdAt, updatedAt string
|
|
var isLocked int
|
|
|
|
err := rows.Scan(
|
|
&doc.ID,
|
|
&doc.Title,
|
|
&createdAt,
|
|
&updatedAt,
|
|
&isLocked,
|
|
)
|
|
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to scan document row: %w", err)
|
|
}
|
|
|
|
// 转换时间字段
|
|
if t, err := time.Parse("2006-01-02 15:04:05", createdAt); err == nil {
|
|
doc.CreatedAt = t
|
|
}
|
|
if t, err := time.Parse("2006-01-02 15:04:05", updatedAt); err == nil {
|
|
doc.UpdatedAt = t
|
|
}
|
|
|
|
doc.IsLocked = isLocked == 1
|
|
documents = append(documents, doc)
|
|
}
|
|
|
|
if err = rows.Err(); err != nil {
|
|
return nil, fmt.Errorf("error iterating document rows: %w", err)
|
|
}
|
|
|
|
return documents, nil
|
|
}
|
|
|
|
// ListDeletedDocumentsMeta lists all deleted document metadata
|
|
func (ds *DocumentService) ListDeletedDocumentsMeta() ([]*models.Document, error) {
|
|
ds.mu.RLock()
|
|
defer ds.mu.RUnlock()
|
|
|
|
if ds.databaseService == nil || ds.databaseService.db == nil {
|
|
return nil, errors.New("database service not available")
|
|
}
|
|
|
|
rows, err := ds.databaseService.db.Query(sqlListDeletedDocumentsMeta)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to list deleted document meta: %w", err)
|
|
}
|
|
defer rows.Close()
|
|
|
|
var documents []*models.Document
|
|
for rows.Next() {
|
|
doc := &models.Document{IsDeleted: true}
|
|
var createdAt, updatedAt string
|
|
var isLocked int
|
|
|
|
err := rows.Scan(
|
|
&doc.ID,
|
|
&doc.Title,
|
|
&createdAt,
|
|
&updatedAt,
|
|
&isLocked,
|
|
)
|
|
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to scan document row: %w", err)
|
|
}
|
|
|
|
// 转换时间字段
|
|
if t, err := time.Parse("2006-01-02 15:04:05", createdAt); err == nil {
|
|
doc.CreatedAt = t
|
|
}
|
|
if t, err := time.Parse("2006-01-02 15:04:05", updatedAt); err == nil {
|
|
doc.UpdatedAt = t
|
|
}
|
|
|
|
doc.IsLocked = isLocked == 1
|
|
documents = append(documents, doc)
|
|
}
|
|
|
|
if err = rows.Err(); err != nil {
|
|
return nil, fmt.Errorf("error iterating deleted document rows: %w", err)
|
|
}
|
|
|
|
return documents, nil
|
|
}
|
|
|
|
// GetFirstDocumentID gets the first active document's ID for frontend initialization
|
|
func (ds *DocumentService) GetFirstDocumentID() (int64, error) {
|
|
ds.mu.RLock()
|
|
defer ds.mu.RUnlock()
|
|
|
|
if ds.databaseService == nil || ds.databaseService.db == nil {
|
|
return 0, errors.New("database service not available")
|
|
}
|
|
|
|
var id int64
|
|
err := ds.databaseService.db.QueryRow(sqlGetFirstDocumentID).Scan(&id)
|
|
if err != nil {
|
|
if errors.Is(err, sql.ErrNoRows) {
|
|
return 0, nil // No documents exist
|
|
}
|
|
return 0, fmt.Errorf("failed to get first document ID: %w", err)
|
|
}
|
|
|
|
return id, nil
|
|
}
|