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 FROM documents WHERE id = ?` sqlInsertDocument = ` INSERT INTO documents (title, content, created_at, updated_at, is_deleted) VALUES (?, ?, ?, ?, 0)` sqlUpdateDocumentContent = ` UPDATE documents SET content = ?, updated_at = ? WHERE id = ?` sqlUpdateDocumentTitle = ` UPDATE documents SET title = ?, updated_at = ? WHERE id = ?` sqlMarkDocumentAsDeleted = ` UPDATE documents SET is_deleted = 1, updated_at = ? WHERE id = ?` sqlRestoreDocument = ` UPDATE documents SET is_deleted = 0, updated_at = ? WHERE id = ?` sqlListAllDocumentsMeta = ` SELECT id, title, created_at, updated_at FROM documents WHERE is_deleted = 0 ORDER BY updated_at DESC` sqlListDeletedDocumentsMeta = ` SELECT id, title, created_at, updated_at 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` 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() } return &DocumentService{ databaseService: databaseService, logger: logger, } } // 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) } return nil } // ensureDefaultDocument ensures a default document exists func (ds *DocumentService) ensureDefaultDocument() error { // Check if any document exists rows, err := ds.databaseService.SQLite.Query(sqlCountDocuments) if err != nil { return err } if len(rows) == 0 { return fmt.Errorf("failed to query document count") } count, ok := rows[0]["COUNT(*)"].(int64) if !ok { return fmt.Errorf("failed to convert count to int64") } // 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() rows, err := ds.databaseService.SQLite.Query(sqlGetDocumentByID, id) if err != nil { return nil, fmt.Errorf("failed to get document by ID: %w", err) } if len(rows) == 0 { return nil, nil } row := rows[0] doc := &models.Document{} // 从Row中提取数据 if idVal, ok := row["id"].(int64); ok { doc.ID = idVal } if title, ok := row["title"].(string); ok { doc.Title = title } if content, ok := row["content"].(string); ok { doc.Content = content } if createdAt, ok := row["created_at"].(string); ok { t, err := time.Parse("2006-01-02 15:04:05", createdAt) if err == nil { doc.CreatedAt = t } } if updatedAt, ok := row["updated_at"].(string); ok { t, err := time.Parse("2006-01-02 15:04:05", updatedAt) if err == nil { doc.UpdatedAt = t } } if isDeletedInt, ok := row["is_deleted"].(int64); ok { doc.IsDeleted = isDeletedInt == 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() // Create document with default content now := time.Now() doc := &models.Document{ Title: title, Content: "∞∞∞text-a\n", CreatedAt: now, UpdatedAt: now, IsDeleted: false, } // 执行插入操作 if err := ds.databaseService.SQLite.Execute(sqlInsertDocument, doc.Title, doc.Content, doc.CreatedAt, doc.UpdatedAt); err != nil { return nil, fmt.Errorf("failed to create document: %w", err) } // 获取自增ID lastIDRows, err := ds.databaseService.SQLite.Query("SELECT last_insert_rowid()") if err != nil { return nil, fmt.Errorf("failed to get last insert ID: %w", err) } if len(lastIDRows) == 0 { return nil, fmt.Errorf("no rows returned for last insert ID query") } // 从结果中提取ID lastID, ok := lastIDRows[0]["last_insert_rowid()"].(int64) if !ok { return nil, fmt.Errorf("failed to convert last insert ID to int64") } // 返回带ID的文档 doc.ID = lastID return doc, nil } // UpdateDocumentContent updates the content of a document func (ds *DocumentService) UpdateDocumentContent(id int64, content string) error { ds.mu.Lock() defer ds.mu.Unlock() err := ds.databaseService.SQLite.Execute(sqlUpdateDocumentContent, content, time.Now(), 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() err := ds.databaseService.SQLite.Execute(sqlUpdateDocumentTitle, title, time.Now(), 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 id == sqlDefaultDocumentID { return fmt.Errorf("cannot delete the default document") } err := ds.databaseService.SQLite.Execute(sqlMarkDocumentAsDeleted, time.Now(), 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() err := ds.databaseService.SQLite.Execute(sqlRestoreDocument, time.Now(), 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() rows, err := ds.databaseService.SQLite.Query(sqlListAllDocumentsMeta) if err != nil { return nil, fmt.Errorf("failed to list document meta: %w", err) } var documents []*models.Document for _, row := range rows { doc := &models.Document{IsDeleted: false} if id, ok := row["id"].(int64); ok { doc.ID = id } if title, ok := row["title"].(string); ok { doc.Title = title } if createdAt, ok := row["created_at"].(string); ok { t, err := time.Parse("2006-01-02 15:04:05", createdAt) if err == nil { doc.CreatedAt = t } } if updatedAt, ok := row["updated_at"].(string); ok { t, err := time.Parse("2006-01-02 15:04:05", updatedAt) if err == nil { doc.UpdatedAt = t } } documents = append(documents, doc) } return documents, nil } // ListDeletedDocumentsMeta lists all deleted document metadata func (ds *DocumentService) ListDeletedDocumentsMeta() ([]*models.Document, error) { ds.mu.RLock() defer ds.mu.RUnlock() rows, err := ds.databaseService.SQLite.Query(sqlListDeletedDocumentsMeta) if err != nil { return nil, fmt.Errorf("failed to list deleted document meta: %w", err) } var documents []*models.Document for _, row := range rows { doc := &models.Document{IsDeleted: true} if id, ok := row["id"].(int64); ok { doc.ID = id } if title, ok := row["title"].(string); ok { doc.Title = title } if createdAt, ok := row["created_at"].(string); ok { t, err := time.Parse("2006-01-02 15:04:05", createdAt) if err == nil { doc.CreatedAt = t } } if updatedAt, ok := row["updated_at"].(string); ok { t, err := time.Parse("2006-01-02 15:04:05", updatedAt) if err == nil { doc.UpdatedAt = t } } documents = append(documents, doc) } 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() rows, err := ds.databaseService.SQLite.Query(sqlGetFirstDocumentID) 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) } if len(rows) == 0 { return 0, nil } id, ok := rows[0]["id"].(int64) if !ok { return 0, fmt.Errorf("failed to convert ID to int64") } return id, nil }