更新若干功能

This commit is contained in:
李琦
2026-08-15 17:18:00 +08:00
parent 4954295961
commit 6a81e479ef
77 changed files with 8165 additions and 2372 deletions

View File

@@ -55,6 +55,8 @@ func OpenStore(path string) (*Store, error) {
db.Close()
return nil, err
}
// 每次打开库:把上次异常退出留下的 running 任务标为失败(表不存在时忽略)。
s.markInterruptedPackTasks()
return s, nil
}
@@ -66,7 +68,12 @@ func OpenStore(path string) (*Store, error) {
// v6新增 day_briefs工作台 AI 今日规划 / 下班日报)。
// v7新增 launch_apps启动台保存的应用与启停命令
// v8todos/tickets 新增 team_id 团队共享列。
const schemaVersion = 8
// v9新增 launch_logs启动台进程输出仅本地不同步
// v10launch_apps 新增 icon本地抓取的 favicon/logo dataURL
// v11launch_apps 新增 category我的应用分类筛选
// v12launch_categories 一级分类launch_apps.category_idprojects.iconkind_icons。
// v13pack_tasks / pack_task_logs打包任务与控制台输出仅本地不同步
const schemaVersion = 13
func (s *Store) migrate() error {
// PRAGMA 是连接级/文件级配置,不属于迁移,每次打开都需执行。
@@ -111,6 +118,14 @@ func (s *Store) migrate() error {
`CREATE TABLE IF NOT EXISTS ai_summaries(project_id INTEGER NOT NULL, kind TEXT NOT NULL, content TEXT NOT NULL DEFAULT '', provider TEXT NOT NULL DEFAULT '', generated_at TEXT NOT NULL, PRIMARY KEY(project_id,kind), FOREIGN KEY(project_id) REFERENCES projects(id) ON DELETE CASCADE)`,
`CREATE TABLE IF NOT EXISTS day_briefs(kind TEXT PRIMARY KEY, content TEXT NOT NULL DEFAULT '', provider TEXT NOT NULL DEFAULT '', generated_at TEXT NOT NULL)`,
`CREATE TABLE IF NOT EXISTS launch_apps(id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT NOT NULL, kind TEXT NOT NULL DEFAULT 'other', port INTEGER NOT NULL DEFAULT 0, dir TEXT NOT NULL DEFAULT '', start_cmd TEXT NOT NULL DEFAULT '', stop_cmd TEXT NOT NULL DEFAULT '', last_pid INTEGER NOT NULL DEFAULT 0, created_at TEXT NOT NULL, updated_at TEXT NOT NULL)`,
`CREATE TABLE IF NOT EXISTS launch_logs(id INTEGER PRIMARY KEY AUTOINCREMENT, app_id INTEGER NOT NULL, level TEXT NOT NULL DEFAULT 'info', line TEXT NOT NULL, created_at TEXT NOT NULL)`,
`CREATE TABLE IF NOT EXISTS launch_categories(id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT NOT NULL UNIQUE, sort INTEGER NOT NULL DEFAULT 0, created_at TEXT NOT NULL)`,
`CREATE TABLE IF NOT EXISTS kind_icons(kind TEXT PRIMARY KEY, value TEXT NOT NULL DEFAULT '', updated_at TEXT NOT NULL DEFAULT '')`,
`CREATE TABLE IF NOT EXISTS pack_tasks(id TEXT PRIMARY KEY, title TEXT NOT NULL DEFAULT '', cmd TEXT NOT NULL DEFAULT '', dir TEXT NOT NULL DEFAULT '', status TEXT NOT NULL DEFAULT 'running', pid INTEGER NOT NULL DEFAULT 0, started_at TEXT NOT NULL DEFAULT '', ended_at TEXT NOT NULL DEFAULT '', error TEXT NOT NULL DEFAULT '')`,
`CREATE TABLE IF NOT EXISTS pack_task_logs(id INTEGER PRIMARY KEY AUTOINCREMENT, task_id TEXT NOT NULL, line TEXT NOT NULL, created_at TEXT NOT NULL)`,
`CREATE INDEX IF NOT EXISTS idx_launch_logs_app ON launch_logs(app_id, id DESC)`,
`CREATE INDEX IF NOT EXISTS idx_pack_tasks_started ON pack_tasks(started_at DESC)`,
`CREATE INDEX IF NOT EXISTS idx_pack_task_logs_task ON pack_task_logs(task_id, id)`,
`CREATE INDEX IF NOT EXISTS idx_ai_messages_conv ON ai_messages(conversation_id)`,
`CREATE INDEX IF NOT EXISTS idx_messages_read ON messages(is_read)`,
`CREATE INDEX IF NOT EXISTS idx_todos_status ON todos(deleted,status)`,
@@ -150,6 +165,34 @@ func (s *Store) migrate() error {
}
}
}
if ok, err := s.columnExists("launch_apps", "icon"); err != nil {
return err
} else if !ok {
if _, err = s.db.Exec(`ALTER TABLE launch_apps ADD COLUMN icon TEXT NOT NULL DEFAULT ''`); err != nil {
return fmt.Errorf("database migration: %w", err)
}
}
if ok, err := s.columnExists("launch_apps", "category"); err != nil {
return err
} else if !ok {
if _, err = s.db.Exec(`ALTER TABLE launch_apps ADD COLUMN category TEXT NOT NULL DEFAULT ''`); err != nil {
return fmt.Errorf("database migration: %w", err)
}
}
if ok, err := s.columnExists("launch_apps", "category_id"); err != nil {
return err
} else if !ok {
if _, err = s.db.Exec(`ALTER TABLE launch_apps ADD COLUMN category_id INTEGER NOT NULL DEFAULT 0`); err != nil {
return fmt.Errorf("database migration: %w", err)
}
}
if ok, err := s.columnExists("projects", "icon"); err != nil {
return err
} else if !ok {
if _, err = s.db.Exec(`ALTER TABLE projects ADD COLUMN icon TEXT NOT NULL DEFAULT ''`); err != nil {
return fmt.Errorf("database migration: %w", err)
}
}
_, _ = s.db.Exec(`INSERT OR IGNORE INTO project_groups(id,name,created_at,updated_at) VALUES(1,'My Projects',?,?)`, time.Now().Format(time.RFC3339), time.Now().Format(time.RFC3339))
_, _ = s.db.Exec(`UPDATE projects SET group_id=1 WHERE group_id IS NULL OR group_id=0`)
defaults := map[string][]string{"general": {".git", ".idea", ".vscode", "node_modules", "dist", "coverage", "*.min.js", "*.min.css", ".DS_Store"}, "php": {"vendor", "storage/framework", "bootstrap/cache", ".phpunit.cache"}, "go": {"go.sum"}, "vue": {".nuxt", ".next", ".output", "unpackage"}}
@@ -291,7 +334,7 @@ func (s *Store) DeleteProjectGroup(id int64) error {
return tx.Commit()
}
func (s *Store) ListProjects(groupID int64) ([]Project, error) {
q := `SELECT p.id,p.name,p.path,p.description,p.group_id,COALESCE(g.name,''),p.created_at,p.updated_at FROM projects p LEFT JOIN project_groups g ON g.id=p.group_id`
q := `SELECT p.id,p.name,p.path,p.description,p.group_id,COALESCE(g.name,''),COALESCE(p.icon,''),p.created_at,p.updated_at FROM projects p LEFT JOIN project_groups g ON g.id=p.group_id`
args := []any{}
if groupID > 0 {
q += ` WHERE p.group_id=?`
@@ -305,7 +348,7 @@ func (s *Store) ListProjects(groupID int64) ([]Project, error) {
out := []Project{}
for rows.Next() {
var p Project
if e = rows.Scan(&p.ID, &p.Name, &p.Path, &p.Description, &p.GroupID, &p.GroupName, &p.CreatedAt, &p.UpdatedAt); e != nil {
if e = rows.Scan(&p.ID, &p.Name, &p.Path, &p.Description, &p.GroupID, &p.GroupName, &p.Icon, &p.CreatedAt, &p.UpdatedAt); e != nil {
rows.Close()
return nil, e
}
@@ -326,7 +369,7 @@ func (s *Store) ListProjects(groupID int64) ([]Project, error) {
}
func (s *Store) GetProject(id int64) (Project, error) {
var p Project
e := s.db.QueryRow(`SELECT p.id,p.name,p.path,p.description,p.group_id,COALESCE(g.name,''),p.created_at,p.updated_at FROM projects p LEFT JOIN project_groups g ON g.id=p.group_id WHERE p.id=?`, id).Scan(&p.ID, &p.Name, &p.Path, &p.Description, &p.GroupID, &p.GroupName, &p.CreatedAt, &p.UpdatedAt)
e := s.db.QueryRow(`SELECT p.id,p.name,p.path,p.description,p.group_id,COALESCE(g.name,''),COALESCE(p.icon,''),p.created_at,p.updated_at FROM projects p LEFT JOIN project_groups g ON g.id=p.group_id WHERE p.id=?`, id).Scan(&p.ID, &p.Name, &p.Path, &p.Description, &p.GroupID, &p.GroupName, &p.Icon, &p.CreatedAt, &p.UpdatedAt)
if e != nil {
return p, e
}