Add pedigree lookup and ancestry tree page
New 🌳 Pedigree view: enter a dog's ISO chip or SKK registration number
and see its ancestry rendered as a tree. SKK has no public API, so the
server drives SKK Hunddata like a browser: it resolves the input to an
internal hundid via the Hund_sok.aspx/HundData page-method, renders 7
generations per pedigree page, parses the rowspan grid into ahnentafel
positions, and follows each generation's leaves deeper by reading their
hundid out of the __doPostBack response viewstate.
A lookup returns the first 7 generations immediately and crawls deeper in
the background; the client polls and fills the tree in as ancestors
arrive. Finished trees are cached per dog in a new pedigree_cache table
(pedigrees don't change), so a dog is crawled once and repeats are instant.
The endpoints sit behind auth like the rest of /api/*, and the crawl is
kept polite (warmed session, delay between requests, one coalesced job per
dog, hard caps).
This commit is contained in:
@@ -311,6 +311,13 @@ func openDB(path string) (*sql.DB, error) {
|
||||
user_id TEXT NOT NULL,
|
||||
created INTEGER NOT NULL,
|
||||
expires INTEGER NOT NULL
|
||||
);
|
||||
CREATE TABLE IF NOT EXISTS pedigree_cache (
|
||||
hundid TEXT PRIMARY KEY,
|
||||
subject TEXT NOT NULL DEFAULT '',
|
||||
nodes TEXT NOT NULL DEFAULT '',
|
||||
generations INTEGER NOT NULL DEFAULT 0,
|
||||
fetched INTEGER NOT NULL DEFAULT 0
|
||||
);`
|
||||
if _, err := db.Exec(schema); err != nil {
|
||||
db.Close()
|
||||
@@ -600,6 +607,7 @@ func main() {
|
||||
store := newStore(db)
|
||||
configStore := newConfigStore(db)
|
||||
exerciseStore := newExerciseStore(db)
|
||||
pedigrees := newPedManager(db)
|
||||
|
||||
photosDir := filepath.Join(filepath.Dir(*dataPath), "photos")
|
||||
if err := os.MkdirAll(photosDir, 0o755); err != nil {
|
||||
@@ -711,6 +719,24 @@ func main() {
|
||||
}
|
||||
}))
|
||||
|
||||
// POST /api/pedigree — resolve a dog by chip / registration number / name and
|
||||
// return its ancestry tree (immediately for the first generations, then a
|
||||
// background crawl deepens it). GET /api/pedigree/status polls that crawl.
|
||||
mux.HandleFunc("/api/pedigree", auth.requireUser(func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != http.MethodPost {
|
||||
http.Error(w, "method not allowed", http.StatusMethodNotAllowed)
|
||||
return
|
||||
}
|
||||
pedigrees.handleLookup(w, r)
|
||||
}))
|
||||
mux.HandleFunc("/api/pedigree/status", auth.requireUser(func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != http.MethodGet {
|
||||
http.Error(w, "method not allowed", http.StatusMethodNotAllowed)
|
||||
return
|
||||
}
|
||||
pedigrees.handleStatus(w, r)
|
||||
}))
|
||||
|
||||
mux.HandleFunc("/healthz", func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Write([]byte("ok"))
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user