inou/lib/data.go.bak

354 lines
11 KiB
Go

package lib
import (
"database/sql"
"fmt"
"time"
)
// ============================================================================
// DOSSIER HELPERS (auth code management)
// ============================================================================
// DossierSetAuthCode updates auth code and expiry (internal/auth operation)
func DossierSetAuthCode(dossierID string, code int, expiresAt int64) error {
d, err := DossierGet(nil, dossierID) // nil ctx = internal operation
if err != nil {
return err
}
d.AuthCode = code
d.AuthCodeExpiresAt = expiresAt
return DossierWrite(nil, d)
}
// DossierClearAuthCode clears auth code and sets last login (internal/auth operation)
func DossierClearAuthCode(dossierID string) error {
d, err := DossierGet(nil, dossierID) // nil ctx = internal operation
if err != nil {
return err
}
d.AuthCode = 0
d.AuthCodeExpiresAt = 0
d.LastLogin = time.Now().Unix()
return DossierWrite(nil, d)
}
// ============================================================================
// DOSSIER ACCESS
// ============================================================================
// AccessAdd inserts a new access record
func AccessAdd(a *Access) error {
if a.CreatedAt == 0 {
a.CreatedAt = time.Now().Unix()
}
return AccessWrite(a)
}
// AccessDelete removes an access record
func AccessDelete(granteeID, dossierID string) error {
return AccessRemove(granteeID, dossierID)
}
// AccessModify updates an access record
func AccessModify(a *Access) error {
// Lookup access_id if not provided
if a.AccessID == "" {
existing, err := AccessGet(a.GranteeID, a.DossierID)
if err != nil {
return err
}
a.AccessID = existing.AccessID
}
return AccessWrite(a)
}
// AccessListByAccessor lists all dossiers a user can access
func AccessListByAccessor(granteeID string) ([]*Access, error) {
return AccessList(&AccessFilter{AccessorID: granteeID})
}
// AccessListByTarget lists all users who can access a dossier
func AccessListByTarget(dossierID string) ([]*Access, error) {
return AccessList(&AccessFilter{TargetID: dossierID})
}
// AccessUpdateTimestamp updates the accessed_at timestamp
func AccessUpdateTimestamp(granteeID, dossierID string) error {
access, err := AccessGet(granteeID, dossierID)
if err != nil {
return err
}
// Note: Access struct doesn't have AccessedAt field anymore
return AccessWrite(access)
}
// ============================================================================
// ENTRY
// ============================================================================
// EntryAdd inserts a new entry. Generates EntryID if empty. (internal operation)
func EntryAdd(e *Entry) error {
return EntryWrite(nil, e) // nil ctx = internal operation
}
// EntryDelete removes a single entry (internal operation)
func EntryDelete(entryID string) error {
return EntryRemove(nil, entryID) // nil ctx = internal operation
}
// EntryDeleteTree removes an entry and all its children. Requires delete permission.
func EntryDeleteTree(ctx *AccessContext, dossierID, entryID string) error {
if err := checkAccess(accessorIDFromContext(ctx), dossierID, entryID, 0, 'd'); err != nil {
return err
}
// Delete children first
var children []*Entry
if err := dbQuery("SELECT entry_id FROM entries WHERE dossier_id = ? AND parent_id = ?", []any{dossierID, entryID}, &children); err != nil {
return err
}
for _, c := range children {
if err := dbDelete("entries", "entry_id", c.EntryID); err != nil {
return err
}
}
return dbDelete("entries", "entry_id", entryID)
}
// EntryModify updates an entry (internal operation)
func EntryModify(e *Entry) error {
return EntryWrite(nil, e) // nil ctx = internal operation
}
// EntryQueryOld finds entries by dossier and optional category/type (DEPRECATED - use EntryQuery with RBAC)
// Use category=-1 to skip category filter, typ="" to skip type filter
func EntryQueryOld(dossierID string, category int, typ string) ([]*Entry, error) {
q := "SELECT * FROM entries WHERE dossier_id = ?"
args := []any{dossierID}
if category >= 0 {
q += " AND category = ?"
args = append(args, category)
}
if typ != "" {
q += " AND type = ?"
args = append(args, CryptoEncrypt(typ))
}
q += " ORDER BY timestamp DESC"
var result []*Entry
return result, dbQuery(q, args, &result)
}
// EntryQueryByDate retrieves entries within a timestamp range
func EntryQueryByDate(dossierID string, from, to int64) ([]*Entry, error) {
var result []*Entry
return result, dbQuery("SELECT * FROM entries WHERE dossier_id = ? AND timestamp >= ? AND timestamp < ? ORDER BY timestamp DESC",
[]any{dossierID, from, to}, &result)
}
// EntryChildren retrieves child entries ordered by ordinal
func EntryChildren(dossierID, parentID string) ([]*Entry, error) {
var result []*Entry
return result, dbQuery("SELECT * FROM entries WHERE dossier_id = ? AND parent_id = ? ORDER BY ordinal",
[]any{dossierID, parentID}, &result)
}
// EntryChildrenByCategory retrieves child entries filtered by category, ordered by ordinal
func EntryChildrenByCategory(dossierID, parentID string, category int) ([]*Entry, error) {
var result []*Entry
return result, dbQuery("SELECT * FROM entries WHERE dossier_id = ? AND parent_id = ? AND category = ? ORDER BY ordinal",
[]any{dossierID, parentID, category}, &result)
}
// EntryChildrenByType retrieves child entries filtered by type string, ordered by ordinal
func EntryChildrenByType(dossierID, parentID string, typ string) ([]*Entry, error) {
var result []*Entry
return result, dbQuery("SELECT * FROM entries WHERE dossier_id = ? AND parent_id = ? AND type = ? ORDER BY ordinal",
[]any{dossierID, parentID, CryptoEncrypt(typ)}, &result)
}
// EntryRootByType finds the root entry (parent_id = 0 or NULL) for a given type
func EntryRootByType(dossierID string, typ string) (*Entry, error) {
var result []*Entry
err := dbQuery("SELECT * FROM entries WHERE dossier_id = ? AND type = ? AND (parent_id IS NULL OR parent_id = '' OR parent_id = '0') LIMIT 1",
[]any{dossierID, CryptoEncrypt(typ)}, &result)
if err != nil {
return nil, err
}
if len(result) == 0 {
return nil, fmt.Errorf("not found")
}
return result[0], nil
}
// EntryRootsByType finds all root entries (parent_id = '' or NULL) for a given type
func EntryRootsByType(dossierID string, typ string) ([]*Entry, error) {
var result []*Entry
return result, dbQuery("SELECT * FROM entries WHERE dossier_id = ? AND type = ? AND (parent_id IS NULL OR parent_id = '' OR parent_id = '0') ORDER BY timestamp DESC",
[]any{dossierID, CryptoEncrypt(typ)}, &result)
}
// EntryRootByCategory finds the root entry (parent_id IS NULL) for a category
func EntryRootByCategory(dossierID string, category int) (*Entry, error) {
var result []*Entry
err := dbQuery("SELECT * FROM entries WHERE dossier_id = ? AND category = ? AND (parent_id IS NULL OR parent_id = '') LIMIT 1",
[]any{dossierID, category}, &result)
if err != nil {
return nil, err
}
if len(result) == 0 {
return nil, fmt.Errorf("not found")
}
return result[0], nil
}
// EntryTypes returns distinct types for a dossier+category
func EntryTypes(dossierID string, category int) ([]string, error) {
var entries []*Entry
if err := dbQuery("SELECT DISTINCT type FROM entries WHERE dossier_id = ? AND category = ?",
[]any{dossierID, category}, &entries); err != nil {
return nil, err
}
var types []string
for _, e := range entries {
if e.Type != "" {
types = append(types, e.Type)
}
}
return types, nil
}
// ============================================================================
// AUDIT
// ============================================================================
// AuditAdd inserts an audit entry. Generates AuditID if empty.
func AuditAdd(a *AuditEntry) error {
if a.Timestamp == 0 {
a.Timestamp = time.Now().Unix()
}
return AuditWrite(a)
}
// AuditLog is a convenience function for quick audit logging
func AuditLog(actor1ID string, action string, targetID string, details string) {
AuditAdd(&AuditEntry{
Actor1ID: actor1ID,
Action: action,
TargetID: targetID,
Details: details,
})
}
// AuditLogFull logs an audit entry with all fields
func AuditLogFull(actor1ID, actor2ID, targetID string, action, details string, relationID int) {
AuditAdd(&AuditEntry{
Actor1ID: actor1ID,
Actor2ID: actor2ID,
TargetID: targetID,
Action: action,
Details: details,
RelationID: relationID,
})
}
// AuditQueryByDossier retrieves audit entries by actor
func AuditQueryByActor(actor1ID string, from, to int64) ([]*AuditEntry, error) {
return AuditList(&AuditFilter{ActorID: actor1ID, FromDate: from, ToDate: to})
}
// AuditQueryByTarget retrieves audit entries by target dossier
func AuditQueryByTarget(targetID string, from, to int64) ([]*AuditEntry, error) {
return AuditList(&AuditFilter{TargetID: targetID, FromDate: from, ToDate: to})
}
// ============================================================================
// HELPERS
// ============================================================================
func nilIfZero(v int64) any {
if v == 0 {
return nil
}
return v
}
func nilIfEmpty(s string) any {
if s == "" || s == "0000000000000000" {
return nil
}
return s
}
func nullStr(ns sql.NullString) string {
if ns.Valid {
return ns.String
}
return ""
}
func nullInt(ni sql.NullInt64) int64 {
if ni.Valid {
return ni.Int64
}
return 0
}
func nullIntToHex(ni sql.NullInt64) string {
if ni.Valid {
return fmt.Sprintf("%016x", ni.Int64)
}
return ""
}
func boolToInt(b bool) int {
if b {
return 1
}
return 0
}
// EntryDeleteByCategory removes all entries with a given category for a dossier. Requires delete permission.
func EntryDeleteByCategory(ctx *AccessContext, dossierID string, category int) error {
if err := checkAccess(accessorIDFromContext(ctx), dossierID, "", category, 'd'); err != nil {
return err
}
// Query all entries with this category, then delete each
var entries []*Entry
if err := dbQuery("SELECT entry_id FROM entries WHERE dossier_id = ? AND category = ?",
[]any{dossierID, category}, &entries); err != nil {
return err
}
for _, e := range entries {
if err := dbDelete("entries", "entry_id", e.EntryID); err != nil {
return err
}
}
return nil
}
// OpenReadOnly opens a SQLite database in read-only mode
func OpenReadOnly(path string) (*sql.DB, error) {
return sql.Open("sqlite3", path+"?mode=ro")
}
// EntryAddBatch inserts multiple entries (internal operation)
func EntryAddBatch(entries []*Entry) error {
return EntryWrite(nil, entries...) // nil ctx = internal operation
}
// EntryAddBatchValues inserts multiple entries from a value slice (internal operation)
func EntryAddBatchValues(entries []Entry) error {
return dbSave("entries", entries)
}
// DossierSetSessionToken sets the mobile session token (internal/auth operation)
func DossierSetSessionToken(dossierID string, token string) error {
d, err := DossierGet(nil, dossierID) // nil ctx = internal operation
if err != nil {
return err
}
d.SessionToken = token
return DossierWrite(nil, d)
}