init: initial commit

This commit is contained in:
Blizzard
2026-04-07 17:35:09 +08:00
commit 680ecc320f
129 changed files with 10562 additions and 0 deletions
+158
View File
@@ -0,0 +1,158 @@
package project
import (
"fmt"
"os"
"path/filepath"
"sync"
"time"
"engimind/internal/models"
"gorm.io/driver/sqlite"
"gorm.io/gorm"
"gorm.io/gorm/logger"
)
// ProjectService manages multi-project lifecycle with isolated SQLite databases.
type ProjectService struct {
globalDB *gorm.DB
projectDB *gorm.DB
currentID string
mu sync.Mutex
baseDir string
}
// NewProjectService creates a project service. Call SetGlobalDB before use.
func NewProjectService() *ProjectService {
homeDir, _ := os.UserHomeDir()
return &ProjectService{
baseDir: filepath.Join(homeDir, ".engimind", "projects"),
}
}
// SetGlobalDB injects the global database reference.
func (s *ProjectService) SetGlobalDB(db *gorm.DB) {
s.globalDB = db
}
// ListProjects returns all registered projects.
func (s *ProjectService) ListProjects() ([]models.Project, error) {
var projects []models.Project
err := s.globalDB.Order("created_at DESC").Find(&projects).Error
return projects, err
}
// CreateProject creates a new project with its own SQLite database.
func (s *ProjectService) CreateProject(name string) (*models.Project, error) {
id := fmt.Sprintf("p-%d", time.Now().UnixMilli())
projDir := filepath.Join(s.baseDir, id)
if err := os.MkdirAll(projDir, 0755); err != nil {
return nil, fmt.Errorf("create project dir: %w", err)
}
dbPath := filepath.Join(projDir, "project.db")
proj := models.Project{
ID: id,
Name: name,
Path: dbPath,
}
if err := s.globalDB.Create(&proj).Error; err != nil {
return nil, err
}
// Initialize project DB
if err := s.openProjectDB(dbPath); err != nil {
return nil, err
}
s.currentID = id
return &proj, nil
}
// SwitchProject closes current project DB and opens a new one.
func (s *ProjectService) SwitchProject(id string) error {
s.mu.Lock()
defer s.mu.Unlock()
if s.currentID == id && s.projectDB != nil {
return nil
}
// Close current
s.closeCurrentDB()
// Find project
var proj models.Project
if err := s.globalDB.First(&proj, "id = ?", id).Error; err != nil {
return fmt.Errorf("project not found: %w", err)
}
if err := s.openProjectDB(proj.Path); err != nil {
return err
}
s.currentID = id
return nil
}
// DeleteProject removes the project and its database files.
func (s *ProjectService) DeleteProject(id string) error {
s.mu.Lock()
defer s.mu.Unlock()
if s.currentID == id {
s.closeCurrentDB()
}
var proj models.Project
if err := s.globalDB.First(&proj, "id = ?", id).Error; err != nil {
return err
}
// Remove DB files
projDir := filepath.Dir(proj.Path)
os.RemoveAll(projDir)
return s.globalDB.Delete(&proj).Error
}
// GetCurrentProjectID returns the active project ID.
func (s *ProjectService) GetCurrentProjectID() string {
return s.currentID
}
// GetProjectDB returns the current project's database.
func (s *ProjectService) GetProjectDB() *gorm.DB {
return s.projectDB
}
func (s *ProjectService) openProjectDB(dbPath string) error {
db, err := gorm.Open(sqlite.Open(dbPath), &gorm.Config{
Logger: logger.Default.LogMode(logger.Silent),
})
if err != nil {
return fmt.Errorf("open project db: %w", err)
}
if err := db.AutoMigrate(
&models.SourceFile{},
&models.ChatMessage{},
&models.TemplateChapter{},
&models.TextChunk{},
); err != nil {
return fmt.Errorf("auto migrate project db: %w", err)
}
s.projectDB = db
return nil
}
func (s *ProjectService) closeCurrentDB() {
if s.projectDB != nil {
sqlDB, err := s.projectDB.DB()
if err == nil {
sqlDB.Close()
}
s.projectDB = nil
s.currentID = ""
}
}