diff --git a/internal/storage/face_gallery_repo.go b/internal/storage/face_gallery_repo.go index 85faf43..ef525cc 100644 --- a/internal/storage/face_gallery_repo.go +++ b/internal/storage/face_gallery_repo.go @@ -9,7 +9,7 @@ import ( type PersonRecord struct { ID int `json:"id"` Name string `json:"name"` - PhotoURL string `json:"photo_url,omitempty"` + PhotoCount int `json:"photo_count"` CreatedAt string `json:"created_at"` } @@ -24,27 +24,25 @@ func NewFaceGalleryRepo(dbPath string) *FaceGalleryRepo { func (r *FaceGalleryRepo) open() (*sql.DB, error) { db, err := sql.Open("sqlite", r.dbPath) if err != nil { - return nil, fmt.Errorf("open face gallery: %w", err) + return nil, fmt.Errorf("open app db: %w", err) } - // Ensure schema _, err = db.Exec(` -CREATE TABLE IF NOT EXISTS person ( +CREATE TABLE IF NOT EXISTS face_persons ( id INTEGER PRIMARY KEY, name TEXT NOT NULL, created_at TEXT NOT NULL DEFAULT '', - extra TEXT NOT NULL DEFAULT '' + updated_at TEXT NOT NULL DEFAULT '' ); -CREATE TABLE IF NOT EXISTS embedding ( +CREATE TABLE IF NOT EXISTS face_photos ( id INTEGER PRIMARY KEY, person_id INTEGER NOT NULL, - embedding BLOB NOT NULL, - created_at TEXT NOT NULL DEFAULT '', - FOREIGN KEY (person_id) REFERENCES person(id) + photo_path TEXT NOT NULL DEFAULT '', + created_at TEXT NOT NULL DEFAULT '' ); `) if err != nil { db.Close() - return nil, fmt.Errorf("ensure schema: %w", err) + return nil, fmt.Errorf("ensure face tables: %w", err) } return db, nil } @@ -56,8 +54,8 @@ func (r *FaceGalleryRepo) ListPersons() ([]PersonRecord, error) { } defer db.Close() - rows, err := db.Query(`SELECT p.id, p.name, p.created_at, COUNT(e.id) AS photo_count -FROM person p LEFT JOIN embedding e ON e.person_id = p.id + rows, err := db.Query(`SELECT p.id, p.name, p.created_at, COUNT(ph.id) AS photo_count +FROM face_persons p LEFT JOIN face_photos ph ON ph.person_id = p.id GROUP BY p.id ORDER BY p.name`) if err != nil { return nil, err @@ -67,8 +65,7 @@ GROUP BY p.id ORDER BY p.name`) var out []PersonRecord for rows.Next() { var p PersonRecord - var photoCount int - if err := rows.Scan(&p.ID, &p.Name, &p.CreatedAt, &photoCount); err != nil { + if err := rows.Scan(&p.ID, &p.Name, &p.CreatedAt, &p.PhotoCount); err != nil { continue } out = append(out, p) @@ -86,30 +83,51 @@ func (r *FaceGalleryRepo) AddPerson(name string) error { } defer db.Close() now := time.Now().Format(time.RFC3339) - _, err = db.Exec(`INSERT INTO person(name, created_at) VALUES(?, ?)`, name, now) + _, err = db.Exec(`INSERT INTO face_persons(name, created_at, updated_at) VALUES(?, ?, ?)`, name, now, now) return err } +func (r *FaceGalleryRepo) AddPhoto(personID int, photoPath string) error { + db, err := r.open() + if err != nil { + return err + } + defer db.Close() + now := time.Now().Format(time.RFC3339) + _, err = db.Exec(`INSERT INTO face_photos(person_id, photo_path, created_at) VALUES(?, ?, ?)`, personID, photoPath, now) + return err +} + +func (r *FaceGalleryRepo) FindOrCreatePerson(name string) (int, error) { + db, err := r.open() + if err != nil { + return 0, err + } + defer db.Close() + var id int + err = db.QueryRow(`SELECT id FROM face_persons WHERE name = ?`, name).Scan(&id) + if err == nil { + return id, nil + } + now := time.Now().Format(time.RFC3339) + res, err := db.Exec(`INSERT INTO face_persons(name, created_at, updated_at) VALUES(?, ?, ?)`, name, now, now) + if err != nil { + return 0, err + } + lid, _ := res.LastInsertId() + return int(lid), nil +} + func (r *FaceGalleryRepo) DeletePerson(id int) error { db, err := r.open() if err != nil { return err } defer db.Close() - _, err = db.Exec(`DELETE FROM embedding WHERE person_id = ?`, id) + _, err = db.Exec(`DELETE FROM face_photos WHERE person_id = ?`, id) if err != nil { return err } - _, err = db.Exec(`DELETE FROM person WHERE id = ?`, id) - return err -} - -func (r *FaceGalleryRepo) RenamePerson(id int, name string) error { - db, err := r.open() - if err != nil { - return err - } - defer db.Close() - _, err = db.Exec(`UPDATE person SET name = ? WHERE id = ?`, name, id) + _, err = db.Exec(`DELETE FROM face_persons WHERE id = ?`, id) return err } diff --git a/internal/web/ui.go b/internal/web/ui.go index b584158..aa3ca09 100644 --- a/internal/web/ui.go +++ b/internal/web/ui.go @@ -667,7 +667,6 @@ func (u *UI) Routes() (chi.Router, error) { r.Post("/face-gallery/build", u.actionFaceGalleryBuild) r.Post("/face-gallery/add", u.actionFaceGalleryAdd) r.Post("/face-gallery/delete", u.actionFaceGalleryDelete) - r.Post("/face-gallery/rename", u.actionFaceGalleryRename) r.Get("/monitor", u.pageMonitor) r.Get("/hls/*", u.proxyHLS) r.Get("/api/monitor/channels", u.apiMonitorChannels) @@ -4009,10 +4008,11 @@ func (u *UI) actionFaceGalleryBuild(w http.ResponseWriter, r *http.Request) { func (u *UI) pageFaceGallery(w http.ResponseWriter, r *http.Request) { data := PageData{Title: "人脸库管理"} data.Message = r.URL.Query().Get("msg") - dbPath := filepath.Join("resources", "standard_resources", "face_gallery", "face_gallery.db") - if repo := storage.NewFaceGalleryRepo(dbPath); repo != nil { - if persons, err := repo.ListPersons(); err == nil { - data.FaceGalleryPersons = persons + if strings.TrimSpace(u.dbPath) != "" { + if repo := storage.NewFaceGalleryRepo(u.dbPath); repo != nil { + if persons, err := repo.ListPersons(); err == nil { + data.FaceGalleryPersons = persons + } } } u.render(w, r, "face_gallery", data) @@ -4025,22 +4025,13 @@ func (u *UI) actionFaceGalleryDelete(w http.ResponseWriter, r *http.Request) { return } u.rebuildFaceGallery() - repo := storage.NewFaceGalleryRepo(filepath.Join("resources", "standard_resources", "face_gallery", "face_gallery.db")) + if strings.TrimSpace(u.dbPath) != "" { repo := storage.NewFaceGalleryRepo(u.dbPath) repo.DeletePerson(id) + u.rebuildFaceGallery() + } http.Redirect(w, r, "/ui/face-gallery", http.StatusFound) } -func (u *UI) actionFaceGalleryRename(w http.ResponseWriter, r *http.Request) { - id, _ := strconv.Atoi(r.FormValue("id")) - name := strings.TrimSpace(r.FormValue("name")) - if id <= 0 || name == "" { - http.Error(w, "invalid", http.StatusBadRequest) - return - } - repo := storage.NewFaceGalleryRepo(filepath.Join("resources", "standard_resources", "face_gallery", "face_gallery.db")) - repo.RenamePerson(id, name) - http.Redirect(w, r, "/ui/face-gallery", http.StatusFound) -} func (u *UI) actionFaceGalleryImport(w http.ResponseWriter, r *http.Request) { if err := r.ParseMultipartForm(100 << 20); err != nil {