594 lines
15 KiB
Go
594 lines
15 KiB
Go
package lib
|
|
|
|
// =============================================================================
|
|
// LEGACY STUBS — temporary bridges while callers are migrated to dbcore.go
|
|
//
|
|
// Each stub logs its call so we can find and replace callers.
|
|
// Delete this file when all callers use the new core.
|
|
// =============================================================================
|
|
|
|
import (
|
|
"database/sql"
|
|
"encoding/json"
|
|
"fmt"
|
|
"log"
|
|
"os"
|
|
)
|
|
|
|
// --- Legacy types ---
|
|
|
|
type EntryFilter struct {
|
|
DossierID string
|
|
Type string
|
|
Value string
|
|
SearchKey string
|
|
FromDate int64
|
|
ToDate int64
|
|
Limit int
|
|
}
|
|
|
|
type DossierFilter struct {
|
|
DateOfBirth string
|
|
Limit int
|
|
}
|
|
|
|
type AccessFilter struct {
|
|
AccessorID string
|
|
TargetID string
|
|
Status *int
|
|
}
|
|
|
|
type DossierQueryRow struct {
|
|
Dossier
|
|
Category int `db:"category"`
|
|
EntryCount int `db:"entry_count"`
|
|
}
|
|
|
|
type DossierAccess struct {
|
|
Access
|
|
}
|
|
|
|
// --- Entry stubs ---
|
|
|
|
func EntryAdd(e *Entry) error {
|
|
log.Printf("[STUB] EntryAdd(entry=%s, dossier=%s, cat=%d)", e.EntryID, e.DossierID, e.Category)
|
|
if e.EntryID == "" {
|
|
e.EntryID = NewID()
|
|
}
|
|
return entrySave(e)
|
|
}
|
|
|
|
func EntryAddBatch(entries []*Entry) error {
|
|
log.Printf("[STUB] EntryAddBatch(n=%d)", len(entries))
|
|
for _, e := range entries {
|
|
if e.EntryID == "" {
|
|
e.EntryID = NewID()
|
|
}
|
|
if err := entrySave(e); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func EntryAddBatchValues(entries []Entry) error {
|
|
log.Printf("[STUB] EntryAddBatchValues(n=%d)", len(entries))
|
|
for i := range entries {
|
|
if entries[i].EntryID == "" {
|
|
entries[i].EntryID = NewID()
|
|
}
|
|
if err := entrySave(&entries[i]); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func EntryGet(ctx *AccessContext, id string) (*Entry, error) {
|
|
accessorID := ""
|
|
if ctx != nil {
|
|
accessorID = ctx.AccessorID
|
|
}
|
|
return entryGetByID(accessorID, id)
|
|
}
|
|
|
|
func EntryQuery(ctx *AccessContext, dossierID string, category int, typ, parent string) ([]*Entry, error) {
|
|
accessorID := ""
|
|
if ctx != nil {
|
|
accessorID = ctx.AccessorID
|
|
}
|
|
f := &Filter{Category: category}
|
|
if typ != "" {
|
|
f.Type = typ
|
|
}
|
|
if parent != "" && parent != "*" {
|
|
f.ParentID = parent
|
|
}
|
|
return EntryRead(accessorID, dossierID, f)
|
|
}
|
|
|
|
func EntryList(accessorID string, parent string, category int, f *EntryFilter) ([]*Entry, error) {
|
|
dossierID := ""
|
|
filter := &Filter{Category: category, ParentID: parent}
|
|
if f != nil {
|
|
dossierID = f.DossierID
|
|
filter.Type = f.Type
|
|
filter.SearchKey = f.SearchKey
|
|
filter.FromDate = f.FromDate
|
|
filter.ToDate = f.ToDate
|
|
filter.Limit = f.Limit
|
|
}
|
|
return EntryRead(accessorID, dossierID, filter)
|
|
}
|
|
|
|
func EntryQueryOld(dossierID string, category int, typ string) ([]*Entry, error) {
|
|
return EntryRead("", dossierID, &Filter{Category: category, Type: typ})
|
|
}
|
|
|
|
func EntryCount(ctx *AccessContext, dossierID string, category int, typ string) (int, error) {
|
|
entries, err := EntryQuery(ctx, dossierID, category, typ, "*")
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
return len(entries), nil
|
|
}
|
|
|
|
func EntryModify(e *Entry) error {
|
|
log.Printf("[STUB] EntryModify(entry=%s)", e.EntryID)
|
|
return entrySave(e)
|
|
}
|
|
|
|
func EntryDelete(entryID string) error {
|
|
log.Printf("[STUB] EntryDelete(id=%s)", entryID)
|
|
_, err := db.Exec("DELETE FROM entries WHERE EntryID = ?", entryID)
|
|
return err
|
|
}
|
|
|
|
func EntryDeleteTree(ctx *AccessContext, dossierID, entryID string) error {
|
|
log.Printf("[STUB] EntryDeleteTree(dossier=%s, entry=%s)", dossierID, entryID)
|
|
// Delete children first
|
|
children, _ := entryQuery(dossierID, &Filter{Category: -1, ParentID: entryID})
|
|
for _, c := range children {
|
|
db.Exec("DELETE FROM entries WHERE EntryID = ?", c.EntryID)
|
|
}
|
|
_, err := db.Exec("DELETE FROM entries WHERE EntryID = ?", entryID)
|
|
return err
|
|
}
|
|
|
|
func EntryDeleteByCategory(ctx *AccessContext, dossierID string, category int) error {
|
|
log.Printf("[STUB] EntryDeleteByCategory(dossier=%s, cat=%d)", dossierID, category)
|
|
_, err := db.Exec("DELETE FROM entries WHERE DossierID = ? AND Category = ?", dossierID, category)
|
|
return err
|
|
}
|
|
|
|
func EntryRemoveByDossier(ctx *AccessContext, dossierID string) error {
|
|
log.Printf("[STUB] EntryRemoveByDossier(dossier=%s)", dossierID)
|
|
_, err := db.Exec("DELETE FROM entries WHERE DossierID = ?", dossierID)
|
|
return err
|
|
}
|
|
|
|
func EntryChildren(dossierID, parentID string) ([]*Entry, error) {
|
|
return EntryRead("", dossierID, &Filter{Category: -1, ParentID: parentID})
|
|
}
|
|
|
|
func EntryChildrenByType(dossierID, parentID string, typ string) ([]*Entry, error) {
|
|
return EntryRead("", dossierID, &Filter{Category: -1, ParentID: parentID, Type: typ})
|
|
}
|
|
|
|
func EntryTypes(dossierID string, category int) ([]string, error) {
|
|
entries, err := EntryRead("", dossierID, &Filter{Category: category})
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
seen := map[string]bool{}
|
|
var types []string
|
|
for _, e := range entries {
|
|
if !seen[e.Type] {
|
|
seen[e.Type] = true
|
|
types = append(types, e.Type)
|
|
}
|
|
}
|
|
return types, nil
|
|
}
|
|
|
|
// --- Dossier stubs ---
|
|
|
|
func DossierGet(ctx *AccessContext, id string) (*Dossier, error) {
|
|
accessorID := ""
|
|
if ctx != nil {
|
|
accessorID = ctx.AccessorID
|
|
}
|
|
entries, err := EntryRead(accessorID, id, &Filter{Category: 0})
|
|
if err != nil || len(entries) == 0 {
|
|
return nil, fmt.Errorf("dossier not found: %s", id)
|
|
}
|
|
e := entries[0]
|
|
d := &Dossier{DossierID: e.DossierID, Name: e.Summary, Email: e.SearchKey}
|
|
if e.Data != "" {
|
|
var data struct {
|
|
DOB string `json:"dob"`
|
|
Sex int `json:"sex"`
|
|
Lang string `json:"lang"`
|
|
}
|
|
if json.Unmarshal([]byte(e.Data), &data) == nil {
|
|
d.DateOfBirth = data.DOB
|
|
d.Sex = data.Sex
|
|
if data.Lang != "" {
|
|
d.Preferences.Language = data.Lang
|
|
}
|
|
}
|
|
}
|
|
return d, nil
|
|
}
|
|
|
|
func DossierGetFirst(ctx *AccessContext) (*Dossier, error) {
|
|
log.Printf("[STUB] DossierGetFirst")
|
|
return &Dossier{}, nil
|
|
}
|
|
|
|
func DossierGetByEmail(ctx *AccessContext, email string) (*Dossier, error) {
|
|
log.Printf("[STUB] DossierGetByEmail(email=%s)", email)
|
|
return nil, fmt.Errorf("stub: not implemented")
|
|
}
|
|
|
|
func DossierGetBySessionToken(token string) *Dossier {
|
|
log.Printf("[STUB] DossierGetBySessionToken")
|
|
return nil
|
|
}
|
|
|
|
func DossierWrite(ctx *AccessContext, dossiers ...*Dossier) error {
|
|
log.Printf("[STUB] DossierWrite(n=%d)", len(dossiers))
|
|
return nil
|
|
}
|
|
|
|
func DossierList(ctx *AccessContext, f *DossierFilter) ([]*Dossier, error) {
|
|
log.Printf("[STUB] DossierList")
|
|
return nil, nil
|
|
}
|
|
|
|
func DossierQuery(accessorID string) ([]*DossierQueryRow, error) {
|
|
// Get all accessible dossier profiles via RBAC
|
|
dossierEntries, err := EntryRead(accessorID, "", &Filter{Category: 0})
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
var rows []*DossierQueryRow
|
|
for _, de := range dossierEntries {
|
|
d := Dossier{DossierID: de.DossierID, Name: de.Summary, Email: de.SearchKey}
|
|
if de.Data != "" {
|
|
var data struct {
|
|
DOB string `json:"dob"`
|
|
Sex int `json:"sex"`
|
|
Lang string `json:"lang"`
|
|
}
|
|
if json.Unmarshal([]byte(de.Data), &data) == nil {
|
|
d.DateOfBirth = data.DOB
|
|
d.Sex = data.Sex
|
|
if data.Lang != "" {
|
|
d.Preferences.Language = data.Lang
|
|
}
|
|
}
|
|
}
|
|
|
|
// Count entries by category for this dossier
|
|
allEntries, err := EntryRead(accessorID, de.DossierID, &Filter{Category: -1})
|
|
if err != nil {
|
|
continue
|
|
}
|
|
catCounts := map[int]int{}
|
|
for _, e := range allEntries {
|
|
if e.Category > 0 {
|
|
catCounts[e.Category]++
|
|
}
|
|
}
|
|
|
|
if len(catCounts) == 0 {
|
|
// Still include dossier even with no entries
|
|
rows = append(rows, &DossierQueryRow{Dossier: d})
|
|
} else {
|
|
for cat, count := range catCounts {
|
|
rows = append(rows, &DossierQueryRow{Dossier: d, Category: cat, EntryCount: count})
|
|
}
|
|
}
|
|
}
|
|
return rows, nil
|
|
}
|
|
|
|
func DossierSetAuthCode(dossierID string, code int, expiresAt int64) error {
|
|
log.Printf("[STUB] DossierSetAuthCode(dossier=%s)", dossierID)
|
|
return nil
|
|
}
|
|
|
|
func DossierClearAuthCode(dossierID string) error {
|
|
log.Printf("[STUB] DossierClearAuthCode(dossier=%s)", dossierID)
|
|
return nil
|
|
}
|
|
|
|
func DossierSetSessionToken(dossierID string, token string) error {
|
|
log.Printf("[STUB] DossierSetSessionToken(dossier=%s)", dossierID)
|
|
return nil
|
|
}
|
|
|
|
// --- Access stubs ---
|
|
|
|
func AccessGet(accessorID, targetID string) (*Access, error) {
|
|
log.Printf("[STUB] AccessGet(accessor=%s, target=%s)", accessorID, targetID)
|
|
return nil, nil
|
|
}
|
|
|
|
func AccessWrite(records ...*Access) error {
|
|
log.Printf("[STUB] AccessWrite(n=%d)", len(records))
|
|
return nil
|
|
}
|
|
|
|
func AccessRemove(accessorID, targetID string) error {
|
|
log.Printf("[STUB] AccessRemove(accessor=%s, target=%s)", accessorID, targetID)
|
|
return nil
|
|
}
|
|
|
|
func AccessList(f *AccessFilter) ([]*Access, error) {
|
|
if f == nil {
|
|
return nil, nil
|
|
}
|
|
var grants []*Access
|
|
if f.AccessorID != "" {
|
|
return grants, dbQuery(
|
|
"SELECT AccessID, DossierID, GranteeID, EntryID, Relation, Ops, CreatedAt FROM access WHERE GranteeID = ?",
|
|
[]any{f.AccessorID},
|
|
&grants,
|
|
)
|
|
}
|
|
if f.TargetID != "" {
|
|
return grants, dbQuery(
|
|
"SELECT AccessID, DossierID, GranteeID, EntryID, Relation, Ops, CreatedAt FROM access WHERE DossierID = ?",
|
|
[]any{f.TargetID},
|
|
&grants,
|
|
)
|
|
}
|
|
return nil, nil
|
|
}
|
|
|
|
func AccessListByAccessor(granteeID string) ([]*Access, error) {
|
|
log.Printf("[STUB] AccessListByAccessor(grantee=%s)", granteeID)
|
|
return nil, nil
|
|
}
|
|
|
|
func AccessListByTargetWithNames(targetID string) ([]map[string]interface{}, error) {
|
|
log.Printf("[STUB] AccessListByTargetWithNames(target=%s)", targetID)
|
|
return nil, nil
|
|
}
|
|
|
|
func AccessUpdateTimestamp(granteeID, dossierID string) error {
|
|
log.Printf("[STUB] AccessUpdateTimestamp(grantee=%s, dossier=%s)", granteeID, dossierID)
|
|
return nil
|
|
}
|
|
|
|
func AccessGrantRole(dossierID, granteeID, role string) error {
|
|
log.Printf("[STUB] AccessGrantRole(dossier=%s, grantee=%s, role=%s)", dossierID, granteeID, role)
|
|
return nil
|
|
}
|
|
|
|
// --- Audit stubs ---
|
|
|
|
func auditWrite(a *AuditEntry) {
|
|
if a.AuditID == "" {
|
|
a.AuditID = NewID()
|
|
}
|
|
if a.Timestamp == 0 {
|
|
a.Timestamp = nowUnix()
|
|
}
|
|
db.Exec(`INSERT INTO audit (AuditID, Actor1ID, Actor2ID, TargetID, Action, Details, RelationID, Timestamp)
|
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?)`,
|
|
a.AuditID, a.Actor1ID, a.Actor2ID, a.TargetID,
|
|
Pack([]byte(a.Action)), Pack([]byte(a.Details)),
|
|
a.RelationID, a.Timestamp,
|
|
)
|
|
}
|
|
|
|
func AuditAdd(a *AuditEntry) error {
|
|
auditWrite(a)
|
|
return nil
|
|
}
|
|
|
|
func AuditLog(actor1ID string, action string, targetID string, details string) {
|
|
auditWrite(&AuditEntry{Actor1ID: actor1ID, Action: action, TargetID: targetID, Details: details})
|
|
}
|
|
|
|
func AuditLogFull(actor1ID, actor2ID, targetID string, action, details string, relationID int) {
|
|
auditWrite(&AuditEntry{Actor1ID: actor1ID, Actor2ID: actor2ID, TargetID: targetID, Action: action, Details: details, RelationID: relationID})
|
|
}
|
|
|
|
func AuditQueryByActor(actor1ID string, from, to int64) ([]*AuditEntry, error) {
|
|
log.Printf("[STUB] AuditQueryByActor(actor=%s)", actor1ID)
|
|
return nil, nil
|
|
}
|
|
|
|
func AuditQueryByTarget(targetID string, from, to int64) ([]*AuditEntry, error) {
|
|
log.Printf("[STUB] AuditQueryByTarget(target=%s)", targetID)
|
|
return nil, nil
|
|
}
|
|
|
|
// --- Object stubs ---
|
|
|
|
func ObjectWrite(ctx *AccessContext, dossierID, entryID string, data []byte) error {
|
|
log.Printf("[STUB] ObjectWrite(dossier=%s, entry=%s, size=%d)", dossierID, entryID, len(data))
|
|
path := ObjectPath(dossierID, entryID)
|
|
if err := os.MkdirAll(path[:len(path)-len(entryID)-1], 0750); err != nil {
|
|
return err
|
|
}
|
|
return os.WriteFile(path, Pack(data), 0640)
|
|
}
|
|
|
|
func ObjectRead(ctx *AccessContext, dossierID, entryID string) ([]byte, error) {
|
|
log.Printf("[STUB] ObjectRead(dossier=%s, entry=%s)", dossierID, entryID)
|
|
packed, err := os.ReadFile(ObjectPath(dossierID, entryID))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return Unpack(packed), nil
|
|
}
|
|
|
|
func ObjectRemoveByDossier(ctx *AccessContext, dossierID string) error {
|
|
log.Printf("[STUB] ObjectRemoveByDossier(dossier=%s)", dossierID)
|
|
return os.RemoveAll(ObjectPath(dossierID, "")[:len(ObjectDir)+1+len(dossierID)])
|
|
}
|
|
|
|
// --- Lab stubs ---
|
|
|
|
func LabEntryListForIndex() ([]*Entry, error) {
|
|
log.Printf("[STUB] LabEntryListForIndex")
|
|
return nil, nil
|
|
}
|
|
|
|
func LabTestSave(t *LabTest) error {
|
|
log.Printf("[STUB] LabTestSave")
|
|
return nil
|
|
}
|
|
|
|
func LabRefSaveBatch(refs []LabReference) error {
|
|
log.Printf("[STUB] LabRefSaveBatch(n=%d)", len(refs))
|
|
return nil
|
|
}
|
|
|
|
func RefDelete(table, col, id string) error {
|
|
log.Printf("[STUB] RefDelete(table=%s, col=%s, id=%s)", table, col, id)
|
|
return nil
|
|
}
|
|
|
|
// --- Genome types and stubs ---
|
|
|
|
type GenomeMatch struct {
|
|
RSID string `json:"rsid"`
|
|
Genotype string `json:"genotype"`
|
|
Gene string `json:"gene,omitempty"`
|
|
Magnitude *float64 `json:"magnitude,omitempty"`
|
|
Repute string `json:"repute,omitempty"`
|
|
Summary string `json:"summary,omitempty"`
|
|
Categories []string `json:"categories,omitempty"`
|
|
}
|
|
|
|
type GenomeQueryResult struct {
|
|
Matches []GenomeMatch `json:"matches"`
|
|
Returned int `json:"returned"`
|
|
Total int `json:"total"`
|
|
}
|
|
|
|
type GenomeQueryOpts struct {
|
|
Category string
|
|
Search string
|
|
Gene string
|
|
RSIDs []string
|
|
MinMagnitude float64
|
|
Repute string
|
|
IncludeHidden bool
|
|
Sort string
|
|
Offset int
|
|
Limit int
|
|
AccessorID string
|
|
}
|
|
|
|
type ImageOpts struct {
|
|
WC, WW float64
|
|
Zoom float64
|
|
PanX, PanY float64
|
|
}
|
|
|
|
func GenomeQuery(ctx *AccessContext, dossierID string, opts GenomeQueryOpts) (*GenomeQueryResult, error) {
|
|
log.Printf("[STUB] GenomeQuery(dossier=%s)", dossierID)
|
|
return &GenomeQueryResult{}, nil
|
|
}
|
|
|
|
func ImageGet(ctx *AccessContext, id string, opts *ImageOpts) ([]byte, error) {
|
|
log.Printf("[STUB] ImageGet(id=%s)", id)
|
|
return ObjectRead(ctx, "", id)
|
|
}
|
|
|
|
func EntryCategoryCounts(ctx *AccessContext, dossierID string) (map[string]int, error) {
|
|
log.Printf("[STUB] EntryCategoryCounts(dossier=%s)", dossierID)
|
|
return map[string]int{}, nil
|
|
}
|
|
|
|
func EntryQueryByDate(dossierID string, from, to int64) ([]*Entry, error) {
|
|
return EntryRead("", dossierID, &Filter{Category: -1, FromDate: from, ToDate: to})
|
|
}
|
|
|
|
// --- Audit types and stubs ---
|
|
|
|
type AuditFilter struct {
|
|
ActorID string
|
|
TargetID string
|
|
Action string
|
|
FromDate int64
|
|
ToDate int64
|
|
Limit int
|
|
}
|
|
|
|
func AuditList(f *AuditFilter) ([]*AuditEntry, error) {
|
|
if f == nil {
|
|
return nil, nil
|
|
}
|
|
q := "SELECT AuditID, Actor1ID, Actor2ID, TargetID, Action, Details, RelationID, Timestamp FROM audit WHERE 1=1"
|
|
var args []any
|
|
if f.TargetID != "" {
|
|
q += " AND TargetID = ?"
|
|
args = append(args, f.TargetID)
|
|
}
|
|
if f.ActorID != "" {
|
|
q += " AND Actor1ID = ?"
|
|
args = append(args, f.ActorID)
|
|
}
|
|
if f.FromDate > 0 {
|
|
q += " AND Timestamp >= ?"
|
|
args = append(args, f.FromDate)
|
|
}
|
|
if f.ToDate > 0 {
|
|
q += " AND Timestamp < ?"
|
|
args = append(args, f.ToDate)
|
|
}
|
|
q += " ORDER BY Timestamp DESC"
|
|
if f.Limit > 0 {
|
|
q += fmt.Sprintf(" LIMIT %d", f.Limit)
|
|
}
|
|
var entries []*AuditEntry
|
|
err := dbQuery(q, args, &entries)
|
|
return entries, err
|
|
}
|
|
|
|
// --- Tracker types and stubs ---
|
|
|
|
type TrackerFilter struct {
|
|
DossierID string
|
|
Category string
|
|
Type string
|
|
ActiveOnly bool
|
|
Limit int
|
|
}
|
|
|
|
func TrackerList(f *TrackerFilter) ([]*Tracker, error) {
|
|
log.Printf("[STUB] TrackerList")
|
|
return nil, nil
|
|
}
|
|
|
|
// --- Misc stubs ---
|
|
|
|
func OpenReadOnly(path string) (*sql.DB, error) {
|
|
log.Printf("[STUB] OpenReadOnly(path=%s)", path)
|
|
return sql.Open("sqlite3", path+"?mode=ro")
|
|
}
|
|
|
|
func TrackerDistinctTypes(dossierID string) (map[string][]string, error) {
|
|
log.Printf("[STUB] TrackerDistinctTypes(dossier=%s)", dossierID)
|
|
return nil, nil
|
|
}
|
|
|
|
func MigrateCategory() error {
|
|
log.Printf("[STUB] MigrateCategory")
|
|
return nil
|
|
}
|
|
|
|
func MigrateDOB() error {
|
|
log.Printf("[STUB] MigrateDOB")
|
|
return nil
|
|
}
|