feat: registry_api + fn sync — sincronización de registry.db entre PCs
Nuevo sistema para mantener datos no regenerables (proposals, apps, projects, analysis, vaults, pc_locations) sincronizados entre múltiples máquinas via una API HTTP central desplegada en organic-machine.com. - Migración 011: tabla pc_locations (mapa de ubicaciones por PC) - registry/models.go: struct PcLocation - registry/store.go: CRUD PcLocation + helpers de sync - cmd/fn/sync.go: subcomando fn sync (push+pull, status, locations) - bash/functions/infra/setup_registry_api: pipeline de deploy Docker+Traefik - CLAUDE.md: documentación de sync y pc_locations Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1094,3 +1094,116 @@ func scanProposals(rows interface{ Next() bool; Scan(...any) error }) ([]Proposa
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// --- PcLocation CRUD ---
|
||||
|
||||
// InsertPcLocation inserts or replaces a pc_location entry.
|
||||
func (db *DB) InsertPcLocation(loc *PcLocation) error {
|
||||
now := time.Now().UTC()
|
||||
if loc.CreatedAt.IsZero() {
|
||||
loc.CreatedAt = now
|
||||
}
|
||||
if loc.UpdatedAt.IsZero() {
|
||||
loc.UpdatedAt = now
|
||||
}
|
||||
if loc.ID == "" {
|
||||
loc.ID = loc.EntityType + "_" + loc.EntityID + "_" + loc.PcID
|
||||
}
|
||||
if loc.Status == "" {
|
||||
loc.Status = "active"
|
||||
}
|
||||
|
||||
_, err := db.conn.Exec(`
|
||||
INSERT OR REPLACE INTO pc_locations (
|
||||
id, entity_type, entity_id, pc_id, dir_path, status, notes, created_at, updated_at
|
||||
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
||||
loc.ID, loc.EntityType, loc.EntityID, loc.PcID, loc.DirPath, loc.Status, loc.Notes,
|
||||
loc.CreatedAt.Format(time.RFC3339), loc.UpdatedAt.Format(time.RFC3339),
|
||||
)
|
||||
return err
|
||||
}
|
||||
|
||||
// GetPcLocationsByPC returns all locations for a given PC.
|
||||
func (db *DB) GetPcLocationsByPC(pcID string) ([]PcLocation, error) {
|
||||
rows, err := db.conn.Query(
|
||||
"SELECT id, entity_type, entity_id, pc_id, dir_path, status, notes, created_at, updated_at FROM pc_locations WHERE pc_id = ? ORDER BY entity_type, entity_id",
|
||||
pcID,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
return scanPcLocations(rows)
|
||||
}
|
||||
|
||||
// GetPcLocationsByEntity returns all PC locations for a given entity.
|
||||
func (db *DB) GetPcLocationsByEntity(entityType, entityID string) ([]PcLocation, error) {
|
||||
rows, err := db.conn.Query(
|
||||
"SELECT id, entity_type, entity_id, pc_id, dir_path, status, notes, created_at, updated_at FROM pc_locations WHERE entity_type = ? AND entity_id = ? ORDER BY pc_id",
|
||||
entityType, entityID,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
return scanPcLocations(rows)
|
||||
}
|
||||
|
||||
// ListAllPcLocations returns all pc_location entries.
|
||||
func (db *DB) ListAllPcLocations() ([]PcLocation, error) {
|
||||
rows, err := db.conn.Query(
|
||||
"SELECT id, entity_type, entity_id, pc_id, dir_path, status, notes, created_at, updated_at FROM pc_locations ORDER BY pc_id, entity_type, entity_id",
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
return scanPcLocations(rows)
|
||||
}
|
||||
|
||||
// DeletePcLocationsByPC removes all locations for a given PC.
|
||||
func (db *DB) DeletePcLocationsByPC(pcID string) error {
|
||||
_, err := db.conn.Exec("DELETE FROM pc_locations WHERE pc_id = ?", pcID)
|
||||
return err
|
||||
}
|
||||
|
||||
func scanPcLocations(rows interface{ Next() bool; Scan(...any) error }) ([]PcLocation, error) {
|
||||
var result []PcLocation
|
||||
for rows.Next() {
|
||||
var loc PcLocation
|
||||
var createdAt, updatedAt string
|
||||
err := rows.Scan(
|
||||
&loc.ID, &loc.EntityType, &loc.EntityID, &loc.PcID,
|
||||
&loc.DirPath, &loc.Status, &loc.Notes, &createdAt, &updatedAt,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("scanning pc_location: %w", err)
|
||||
}
|
||||
loc.CreatedAt, _ = time.Parse(time.RFC3339, createdAt)
|
||||
loc.UpdatedAt, _ = time.Parse(time.RFC3339, updatedAt)
|
||||
result = append(result, loc)
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// --- Sync helpers ---
|
||||
|
||||
// AllApps returns all apps (for sync export).
|
||||
func (db *DB) AllApps() ([]App, error) {
|
||||
return db.SearchApps("", "", "")
|
||||
}
|
||||
|
||||
// AllAnalysis returns all analysis entries (for sync export).
|
||||
func (db *DB) AllAnalysis() ([]Analysis, error) {
|
||||
return db.SearchAnalysis("", "", "")
|
||||
}
|
||||
|
||||
// AllProposals returns all proposals (for sync export).
|
||||
func (db *DB) AllProposals() ([]Proposal, error) {
|
||||
return db.ListProposals("", "")
|
||||
}
|
||||
|
||||
// AllVaults returns all vaults (for sync export).
|
||||
func (db *DB) AllVaults() ([]Vault, error) {
|
||||
return db.SearchVaults("", "")
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user