180 lines
4.2 KiB
Go
180 lines
4.2 KiB
Go
package lib
|
|
|
|
// ============================================================================
|
|
// Auth Database (auth.db) - OAuth tokens and sessions
|
|
// ============================================================================
|
|
// Separate from medical data (inou.db). Volatile/ephemeral data.
|
|
// Schema documented in docs/schema-auth.sql
|
|
// ============================================================================
|
|
|
|
import (
|
|
"database/sql"
|
|
"fmt"
|
|
"reflect"
|
|
"strings"
|
|
|
|
_ "github.com/mattn/go-sqlite3"
|
|
)
|
|
|
|
var authDB *sql.DB
|
|
|
|
// AuthDBInit opens the auth database connection
|
|
func AuthDBInit(dbPath string) error {
|
|
var err error
|
|
authDB, err = sql.Open("sqlite3", dbPath)
|
|
return err
|
|
}
|
|
|
|
// AuthDBClose closes the auth database connection
|
|
func AuthDBClose() {
|
|
if authDB != nil {
|
|
authDB.Close()
|
|
}
|
|
}
|
|
|
|
// authSave inserts or updates a record in auth.db (simplified, no encryption)
|
|
func authSave(table string, v interface{}) error {
|
|
val := reflect.ValueOf(v)
|
|
if val.Kind() == reflect.Ptr {
|
|
val = val.Elem()
|
|
}
|
|
typ := val.Type()
|
|
|
|
var cols []string
|
|
var placeholders []string
|
|
var vals []interface{}
|
|
|
|
for i := 0; i < typ.NumField(); i++ {
|
|
field := typ.Field(i)
|
|
tag := field.Tag.Get("db")
|
|
if tag == "" || tag == "-" {
|
|
continue
|
|
}
|
|
parts := strings.Split(tag, ",")
|
|
colName := parts[0]
|
|
cols = append(cols, colName)
|
|
placeholders = append(placeholders, "?")
|
|
vals = append(vals, val.Field(i).Interface())
|
|
}
|
|
|
|
query := fmt.Sprintf(
|
|
"INSERT OR REPLACE INTO %s (%s) VALUES (%s)",
|
|
table,
|
|
strings.Join(cols, ", "),
|
|
strings.Join(placeholders, ", "),
|
|
)
|
|
|
|
_, err := authDB.Exec(query, vals...)
|
|
return err
|
|
}
|
|
|
|
// authLoad retrieves a single record by primary key from auth.db
|
|
func authLoad(table string, pk interface{}, dest interface{}) error {
|
|
val := reflect.ValueOf(dest)
|
|
if val.Kind() != reflect.Ptr {
|
|
return fmt.Errorf("dest must be a pointer")
|
|
}
|
|
val = val.Elem()
|
|
typ := val.Type()
|
|
|
|
var pkCol string
|
|
var cols []string
|
|
for i := 0; i < typ.NumField(); i++ {
|
|
field := typ.Field(i)
|
|
tag := field.Tag.Get("db")
|
|
if tag == "" || tag == "-" {
|
|
continue
|
|
}
|
|
parts := strings.Split(tag, ",")
|
|
colName := parts[0]
|
|
isPK := len(parts) > 1 && parts[1] == "pk"
|
|
if isPK {
|
|
pkCol = colName
|
|
}
|
|
cols = append(cols, colName)
|
|
}
|
|
|
|
query := fmt.Sprintf("SELECT %s FROM %s WHERE %s = ?", strings.Join(cols, ", "), table, pkCol)
|
|
row := authDB.QueryRow(query, pk)
|
|
|
|
// Build scan destinations
|
|
ptrs := make([]interface{}, len(cols))
|
|
colIdx := 0
|
|
for i := 0; i < typ.NumField(); i++ {
|
|
field := typ.Field(i)
|
|
tag := field.Tag.Get("db")
|
|
if tag == "" || tag == "-" {
|
|
continue
|
|
}
|
|
ptrs[colIdx] = val.Field(i).Addr().Interface()
|
|
colIdx++
|
|
}
|
|
|
|
return row.Scan(ptrs...)
|
|
}
|
|
|
|
// authQuery executes a SELECT and scans into a slice (for auth.db)
|
|
func authQuery(query string, args []interface{}, dest interface{}) error {
|
|
rows, err := authDB.Query(query, args...)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer rows.Close()
|
|
|
|
sliceVal := reflect.ValueOf(dest)
|
|
if sliceVal.Kind() != reflect.Ptr || sliceVal.Elem().Kind() != reflect.Slice {
|
|
return fmt.Errorf("dest must be pointer to slice")
|
|
}
|
|
sliceVal = sliceVal.Elem()
|
|
elemType := sliceVal.Type().Elem()
|
|
isPtr := elemType.Kind() == reflect.Ptr
|
|
if isPtr {
|
|
elemType = elemType.Elem()
|
|
}
|
|
|
|
cols, _ := rows.Columns()
|
|
|
|
for rows.Next() {
|
|
elem := reflect.New(elemType).Elem()
|
|
ptrs := make([]interface{}, len(cols))
|
|
|
|
for i, col := range cols {
|
|
for j := 0; j < elemType.NumField(); j++ {
|
|
field := elemType.Field(j)
|
|
tag := field.Tag.Get("db")
|
|
if tag == "" {
|
|
continue
|
|
}
|
|
parts := strings.Split(tag, ",")
|
|
if parts[0] == col {
|
|
ptrs[i] = elem.Field(j).Addr().Interface()
|
|
break
|
|
}
|
|
}
|
|
if ptrs[i] == nil {
|
|
var dummy interface{}
|
|
ptrs[i] = &dummy
|
|
}
|
|
}
|
|
|
|
if err := rows.Scan(ptrs...); err != nil {
|
|
return err
|
|
}
|
|
|
|
if isPtr {
|
|
sliceVal.Set(reflect.Append(sliceVal, elem.Addr()))
|
|
} else {
|
|
sliceVal.Set(reflect.Append(sliceVal, elem))
|
|
}
|
|
}
|
|
|
|
return rows.Err()
|
|
}
|
|
|
|
// authDelete removes a record by primary key from auth.db
|
|
func authDelete(table, pkCol string, pkVal interface{}) error {
|
|
query := fmt.Sprintf("DELETE FROM %s WHERE %s = ?", table, pkCol)
|
|
_, err := authDB.Exec(query, pkVal)
|
|
return err
|
|
}
|