safesight-control/internal/storage/face_gallery_repo.go

183 lines
5.0 KiB
Go

package storage
import (
"database/sql"
"fmt"
"os"
"path/filepath"
"time"
)
type PersonRecord struct {
ID int `json:"id"`
Name string `json:"name"`
PhotoCount int `json:"photo_count"`
Photos []string `json:"photos,omitempty"`
PhotoIDs []int `json:"photo_ids,omitempty"`
CreatedAt string `json:"created_at"`
}
type FaceGalleryRepo struct {
dbPath string
}
func NewFaceGalleryRepo(dbPath string) *FaceGalleryRepo {
return &FaceGalleryRepo{dbPath: dbPath}
}
func (r *FaceGalleryRepo) open() (*sql.DB, error) {
db, err := sql.Open("sqlite", r.dbPath)
if err != nil {
return nil, fmt.Errorf("open app db: %w", err)
}
_, err = db.Exec(`
CREATE TABLE IF NOT EXISTS face_persons (
id INTEGER PRIMARY KEY,
name TEXT NOT NULL,
created_at TEXT NOT NULL DEFAULT '',
updated_at TEXT NOT NULL DEFAULT ''
);
CREATE TABLE IF NOT EXISTS face_photos (
id INTEGER PRIMARY KEY,
person_id INTEGER NOT NULL,
photo_path TEXT NOT NULL DEFAULT '',
created_at TEXT NOT NULL DEFAULT ''
);
`)
if err != nil {
db.Close()
return nil, fmt.Errorf("ensure face tables: %w", err)
}
return db, nil
}
func (r *FaceGalleryRepo) ListPersons() ([]PersonRecord, error) {
db, err := r.open()
if err != nil {
return nil, err
}
defer db.Close()
rows, err := db.Query(`SELECT id, name, created_at FROM face_persons ORDER BY name`)
if err != nil {
return nil, err
}
defer rows.Close()
var out []PersonRecord
for rows.Next() {
var p PersonRecord
if err := rows.Scan(&p.ID, &p.Name, &p.CreatedAt); err != nil {
continue
}
out = append(out, p)
}
// Load photos for each person
for i := range out {
photoRows, err := db.Query(`SELECT photo_path, id FROM face_photos WHERE person_id = ? ORDER BY id`, out[i].ID)
if err != nil {
continue
}
for photoRows.Next() {
var path string
var pid int
if photoRows.Scan(&path, &pid) == nil {
out[i].Photos = append(out[i].Photos, path)
out[i].PhotoIDs = append(out[i].PhotoIDs, pid)
}
}
photoRows.Close()
out[i].PhotoCount = len(out[i].Photos)
}
if out == nil {
out = make([]PersonRecord, 0)
}
return out, rows.Err()
}
func (r *FaceGalleryRepo) AddPerson(name 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_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()
// Skip if already exists
var existing int
if db.QueryRow(`SELECT COUNT(*) FROM face_photos WHERE person_id = ? AND photo_path = ?`, personID, photoPath).Scan(&existing) == nil && existing > 0 {
return nil
}
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 face_photos WHERE person_id = ?`, id)
if err != nil {
return err
}
_, err = db.Exec(`DELETE FROM face_persons 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()
now := time.Now().Format(time.RFC3339)
_, err = db.Exec(`UPDATE face_persons SET name = ?, updated_at = ? WHERE id = ?`, name, now, id)
return err
}
func (r *FaceGalleryRepo) DeletePhoto(photoID int) error {
db, err := r.open()
if err != nil { return err }
defer db.Close()
// Get path before deleting
var path string
db.QueryRow(`SELECT photo_path FROM face_photos WHERE id = ?`, photoID).Scan(&path)
_, err = db.Exec(`DELETE FROM face_photos WHERE id = ?`, photoID)
if err != nil { return err }
// Remove file from disk
if path != "" { os.Remove(filepath.Join("dataset", path)) }
return nil
}