Files
zfs/internal/server/database.go
2026-02-16 03:02:10 +01:00

828 lines
22 KiB
Go

package server
import (
"database/sql"
"fmt"
"log"
"time"
_ "modernc.org/sqlite"
)
// Database handles SQLite operations for the server
type Database struct {
db *sql.DB
}
// NewDatabase creates a new database connection and initializes tables
func NewDatabase(dbPath string) (*Database, error) {
db, err := sql.Open("sqlite", dbPath)
if err != nil {
return nil, fmt.Errorf("failed to open database: %v", err)
}
// Enable foreign keys
if _, err := db.Exec("PRAGMA foreign_keys = ON"); err != nil {
db.Close()
return nil, fmt.Errorf("failed to enable foreign keys: %v", err)
}
database := &Database{db: db}
if err := database.initTables(); err != nil {
db.Close()
return nil, fmt.Errorf("failed to initialize tables: %v", err)
}
return database, nil
}
// initTables creates the database tables if they don't exist
func (d *Database) initTables() error {
// Admins table
_, err := d.db.Exec(`
CREATE TABLE IF NOT EXISTS admins (
id INTEGER PRIMARY KEY AUTOINCREMENT,
username TEXT UNIQUE NOT NULL,
password_hash TEXT NOT NULL,
role TEXT NOT NULL DEFAULT 'admin',
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP
)
`)
if err != nil {
return fmt.Errorf("failed to create admins table: %v", err)
}
// Admin sessions table
_, err = d.db.Exec(`
CREATE TABLE IF NOT EXISTS admin_sessions (
id INTEGER PRIMARY KEY AUTOINCREMENT,
admin_id INTEGER NOT NULL,
token TEXT UNIQUE NOT NULL,
expires_at DATETIME NOT NULL,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (admin_id) REFERENCES admins(id) ON DELETE CASCADE
)
`)
if err != nil {
return fmt.Errorf("failed to create admin_sessions table: %v", err)
}
// Clients table
_, err = d.db.Exec(`
CREATE TABLE IF NOT EXISTS clients (
client_id TEXT PRIMARY KEY,
api_key TEXT NOT NULL,
max_size_bytes INTEGER NOT NULL DEFAULT 0,
dataset TEXT NOT NULL,
enabled INTEGER NOT NULL DEFAULT 1,
storage_type TEXT NOT NULL DEFAULT 's3',
keep_hourly INTEGER DEFAULT 24,
keep_daily INTEGER DEFAULT 7,
keep_weekly INTEGER DEFAULT 4,
keep_monthly INTEGER DEFAULT 12,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP
)
`)
if err != nil {
return fmt.Errorf("failed to create clients table: %v", err)
}
// Datasets table - multiple datasets per client
_, err = d.db.Exec(`
CREATE TABLE IF NOT EXISTS datasets (
id INTEGER PRIMARY KEY AUTOINCREMENT,
client_id TEXT NOT NULL,
dataset_name TEXT NOT NULL,
storage_type TEXT NOT NULL DEFAULT 's3',
enabled INTEGER NOT NULL DEFAULT 1,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (client_id) REFERENCES clients(client_id) ON DELETE CASCADE,
UNIQUE(client_id, dataset_name)
)
`)
if err != nil {
return fmt.Errorf("failed to create datasets table: %v", err)
}
// Snapshots table
_, err = d.db.Exec(`
CREATE TABLE IF NOT EXISTS snapshots (
id INTEGER PRIMARY KEY AUTOINCREMENT,
client_id TEXT NOT NULL,
snapshot_id TEXT NOT NULL,
timestamp DATETIME NOT NULL,
size_bytes INTEGER NOT NULL DEFAULT 0,
dataset_name TEXT NOT NULL,
storage_key TEXT NOT NULL,
storage_type TEXT NOT NULL,
compressed INTEGER NOT NULL DEFAULT 0,
incremental INTEGER NOT NULL DEFAULT 0,
base_snapshot TEXT,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (client_id) REFERENCES clients(client_id) ON DELETE CASCADE,
UNIQUE(client_id, snapshot_id)
)
`)
if err != nil {
return fmt.Errorf("failed to create snapshots table: %v", err)
}
// Create indexes
_, err = d.db.Exec(`CREATE INDEX IF NOT EXISTS idx_snapshots_client_id ON snapshots(client_id)`)
if err != nil {
return fmt.Errorf("failed to create index: %v", err)
}
_, err = d.db.Exec(`CREATE INDEX IF NOT EXISTS idx_snapshots_timestamp ON snapshots(timestamp)`)
if err != nil {
return fmt.Errorf("failed to create timestamp index: %v", err)
}
_, err = d.db.Exec(`CREATE INDEX IF NOT EXISTS idx_admin_sessions_token ON admin_sessions(token)`)
if err != nil {
return fmt.Errorf("failed to create admin_sessions index: %v", err)
}
return nil
}
// Close closes the database connection
func (d *Database) Close() error {
return d.db.Close()
}
// Client operations
// GetClient retrieves a client by ID
func (d *Database) GetClient(clientID string) (*ClientConfig, error) {
client := &ClientConfig{}
var enabled int
var keepHourly, keepDaily, keepWeekly, keepMonthly sql.NullInt64
query := `SELECT client_id, api_key, max_size_bytes, dataset, enabled, storage_type,
keep_hourly, keep_daily, keep_weekly, keep_monthly
FROM clients WHERE client_id = ?`
err := d.db.QueryRow(query, clientID).Scan(
&client.ClientID, &client.APIKey, &client.MaxSizeBytes, &client.Dataset,
&enabled, &client.StorageType,
&keepHourly, &keepDaily, &keepWeekly, &keepMonthly,
)
if err == sql.ErrNoRows {
return nil, nil
}
if err != nil {
return nil, err
}
client.Enabled = enabled == 1
// Set rotation policy if any values are set
if keepHourly.Valid || keepDaily.Valid || keepWeekly.Valid || keepMonthly.Valid {
client.RotationPolicy = &RotationPolicy{
KeepHourly: int(keepHourly.Int64),
KeepDaily: int(keepDaily.Int64),
KeepWeekly: int(keepWeekly.Int64),
KeepMonthly: int(keepMonthly.Int64),
}
}
return client, nil
}
// GetAllClients retrieves all clients
func (d *Database) GetAllClients() ([]*ClientConfig, error) {
query := `SELECT client_id, api_key, max_size_bytes, dataset, enabled, storage_type,
keep_hourly, keep_daily, keep_weekly, keep_monthly
FROM clients`
rows, err := d.db.Query(query)
if err != nil {
return nil, err
}
defer rows.Close()
var clients []*ClientConfig
for rows.Next() {
client := &ClientConfig{}
var enabled int
var keepHourly, keepDaily, keepWeekly, keepMonthly sql.NullInt64
err := rows.Scan(
&client.ClientID, &client.APIKey, &client.MaxSizeBytes, &client.Dataset,
&enabled, &client.StorageType,
&keepHourly, &keepDaily, &keepWeekly, &keepMonthly,
)
if err != nil {
return nil, err
}
client.Enabled = enabled == 1
if keepHourly.Valid || keepDaily.Valid || keepWeekly.Valid || keepMonthly.Valid {
client.RotationPolicy = &RotationPolicy{
KeepHourly: int(keepHourly.Int64),
KeepDaily: int(keepDaily.Int64),
KeepWeekly: int(keepWeekly.Int64),
KeepMonthly: int(keepMonthly.Int64),
}
}
clients = append(clients, client)
}
return clients, nil
}
// SaveClient saves or updates a client
func (d *Database) SaveClient(client *ClientConfig) error {
var keepHourly, keepDaily, keepWeekly, keepMonthly interface{}
if client.RotationPolicy != nil {
keepHourly = client.RotationPolicy.KeepHourly
keepDaily = client.RotationPolicy.KeepDaily
keepWeekly = client.RotationPolicy.KeepWeekly
keepMonthly = client.RotationPolicy.KeepMonthly
}
enabled := 0
if client.Enabled {
enabled = 1
}
query := `INSERT OR REPLACE INTO clients
(client_id, api_key, max_size_bytes, dataset, enabled, storage_type,
keep_hourly, keep_daily, keep_weekly, keep_monthly, updated_at)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, CURRENT_TIMESTAMP)`
_, err := d.db.Exec(query,
client.ClientID, client.APIKey, client.MaxSizeBytes, client.Dataset,
enabled, client.StorageType,
keepHourly, keepDaily, keepWeekly, keepMonthly,
)
return err
}
// Snapshot operations
// SaveSnapshot saves a new snapshot record
func (d *Database) SaveSnapshot(metadata *SnapshotMetadata) error {
compressed := 0
if metadata.Compressed {
compressed = 1
}
incremental := 0
if metadata.Incremental {
incremental = 1
}
query := `INSERT INTO snapshots
(client_id, snapshot_id, timestamp, size_bytes, dataset_name,
storage_key, storage_type, compressed, incremental, base_snapshot)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`
_, err := d.db.Exec(query,
metadata.ClientID, metadata.SnapshotID, metadata.Timestamp, metadata.SizeBytes,
metadata.DatasetName, metadata.StorageKey, metadata.StorageType,
compressed, incremental, metadata.BaseSnapshot,
)
return err
}
// GetSnapshotsByClient retrieves all snapshots for a client
func (d *Database) GetSnapshotsByClient(clientID string) ([]*SnapshotMetadata, error) {
query := `SELECT client_id, snapshot_id, timestamp, size_bytes, dataset_name,
storage_key, storage_type, compressed, incremental, base_snapshot
FROM snapshots WHERE client_id = ? ORDER BY timestamp DESC`
rows, err := d.db.Query(query, clientID)
if err != nil {
return nil, err
}
defer rows.Close()
var snapshots []*SnapshotMetadata
for rows.Next() {
snap := &SnapshotMetadata{}
var compressed, incremental int
var baseSnapshot sql.NullString
err := rows.Scan(
&snap.ClientID, &snap.SnapshotID, &snap.Timestamp, &snap.SizeBytes,
&snap.DatasetName, &snap.StorageKey, &snap.StorageType,
&compressed, &incremental, &baseSnapshot,
)
if err != nil {
return nil, err
}
snap.Compressed = compressed == 1
snap.Incremental = incremental == 1
if baseSnapshot.Valid {
snap.BaseSnapshot = baseSnapshot.String
}
snapshots = append(snapshots, snap)
}
return snapshots, nil
}
// GetClientUsage calculates total storage used by a client
func (d *Database) GetClientUsage(clientID string) (int64, error) {
var total sql.NullInt64
err := d.db.QueryRow(`SELECT SUM(size_bytes) FROM snapshots WHERE client_id = ?`, clientID).Scan(&total)
if err != nil {
return 0, err
}
if !total.Valid {
return 0, nil
}
return total.Int64, nil
}
// DeleteSnapshot deletes a snapshot record
func (d *Database) DeleteSnapshot(clientID, snapshotID string) error {
_, err := d.db.Exec(`DELETE FROM snapshots WHERE client_id = ? AND snapshot_id = ?`, clientID, snapshotID)
return err
}
// GetOldestSnapshots gets the oldest snapshots for a client (for rotation)
func (d *Database) GetOldestSnapshots(clientID string, limit int) ([]*SnapshotMetadata, error) {
query := `SELECT client_id, snapshot_id, timestamp, size_bytes, dataset_name,
storage_key, storage_type, compressed, incremental, base_snapshot
FROM snapshots WHERE client_id = ? ORDER BY timestamp ASC LIMIT ?`
rows, err := d.db.Query(query, clientID, limit)
if err != nil {
return nil, err
}
defer rows.Close()
var snapshots []*SnapshotMetadata
for rows.Next() {
snap := &SnapshotMetadata{}
var compressed, incremental int
var baseSnapshot sql.NullString
err := rows.Scan(
&snap.ClientID, &snap.SnapshotID, &snap.Timestamp, &snap.SizeBytes,
&snap.DatasetName, &snap.StorageKey, &snap.StorageType,
&compressed, &incremental, &baseSnapshot,
)
if err != nil {
return nil, err
}
snap.Compressed = compressed == 1
snap.Incremental = incremental == 1
if baseSnapshot.Valid {
snap.BaseSnapshot = baseSnapshot.String
}
snapshots = append(snapshots, snap)
}
return snapshots, nil
}
// CreateDefaultClient creates a default client if none exists
func (d *Database) CreateDefaultClient() error {
clients, err := d.GetAllClients()
if err != nil {
return err
}
if len(clients) > 0 {
return nil
}
log.Println("No clients found, creating default client 'client1'")
defaultClient := &ClientConfig{
ClientID: "client1",
APIKey: hashAPIKey("secret123"),
MaxSizeBytes: 100 * 1024 * 1024 * 1024, // 100GB
Dataset: "backup/client1",
Enabled: true,
StorageType: "s3",
RotationPolicy: &RotationPolicy{
KeepHourly: 24,
KeepDaily: 7,
KeepWeekly: 4,
KeepMonthly: 12,
},
}
return d.SaveClient(defaultClient)
}
// CreateDefaultDataset creates a default dataset for a client if none exists
func (d *Database) CreateDefaultDataset(clientID, datasetName string) error {
datasets, err := d.GetDatasetsByClient(clientID)
if err != nil {
return err
}
if len(datasets) > 0 {
return nil
}
// Create default dataset
dataset := &DatasetConfig{
ClientID: clientID,
DatasetName: datasetName,
StorageType: "s3",
Enabled: true,
}
return d.SaveDataset(dataset)
}
// DatasetConfig represents a dataset configuration
type DatasetConfig struct {
ID int64 `json:"id"`
ClientID string `json:"client_id"`
DatasetName string `json:"dataset_name"`
StorageType string `json:"storage_type"`
Enabled bool `json:"enabled"`
}
// GetDatasetsByClient gets all datasets for a client
func (d *Database) GetDatasetsByClient(clientID string) ([]*DatasetConfig, error) {
query := `SELECT id, client_id, dataset_name, storage_type, enabled FROM datasets WHERE client_id = ?`
rows, err := d.db.Query(query, clientID)
if err != nil {
return nil, err
}
defer rows.Close()
var datasets []*DatasetConfig
for rows.Next() {
dataset := &DatasetConfig{}
var enabled int
err := rows.Scan(&dataset.ID, &dataset.ClientID, &dataset.DatasetName, &dataset.StorageType, &enabled)
if err != nil {
return nil, err
}
dataset.Enabled = enabled == 1
datasets = append(datasets, dataset)
}
return datasets, nil
}
// GetDatasetByName gets a dataset by client and dataset name
func (d *Database) GetDatasetByName(clientID, datasetName string) (*DatasetConfig, error) {
query := `SELECT id, client_id, dataset_name, storage_type, enabled FROM datasets WHERE client_id = ? AND dataset_name = ?`
row := d.db.QueryRow(query, clientID, datasetName)
dataset := &DatasetConfig{}
var enabled int
err := row.Scan(&dataset.ID, &dataset.ClientID, &dataset.DatasetName, &dataset.StorageType, &enabled)
if err == sql.ErrNoRows {
return nil, nil
}
if err != nil {
return nil, err
}
dataset.Enabled = enabled == 1
return dataset, nil
}
// SaveDataset saves or updates a dataset
func (d *Database) SaveDataset(dataset *DatasetConfig) error {
enabled := 0
if dataset.Enabled {
enabled = 1
}
if dataset.ID == 0 {
// Insert new
_, err := d.db.Exec(`INSERT INTO datasets (client_id, dataset_name, storage_type, enabled) VALUES (?, ?, ?, ?)`,
dataset.ClientID, dataset.DatasetName, dataset.StorageType, enabled)
return err
}
// Update existing
_, err := d.db.Exec(`UPDATE datasets SET storage_type = ?, enabled = ? WHERE id = ?`,
dataset.StorageType, enabled, dataset.ID)
return err
}
// DeleteDataset deletes a dataset
func (d *Database) DeleteDataset(id int64) error {
_, err := d.db.Exec(`DELETE FROM datasets WHERE id = ?`, id)
return err
}
// GetDatasetByID gets a dataset by ID
func (d *Database) GetDatasetByID(id int64) (*DatasetConfig, error) {
query := `SELECT id, client_id, dataset_name, storage_type, enabled FROM datasets WHERE id = ?`
row := d.db.QueryRow(query, id)
dataset := &DatasetConfig{}
var enabled int
err := row.Scan(&dataset.ID, &dataset.ClientID, &dataset.DatasetName, &dataset.StorageType, &enabled)
if err != nil {
return nil, err
}
dataset.Enabled = enabled == 1
return dataset, nil
}
// GetSnapshotCountByDataset gets snapshot count for a specific dataset
func (d *Database) GetSnapshotCountByDataset(clientID, datasetName string) (int, error) {
var count int
err := d.db.QueryRow(`SELECT COUNT(*) FROM snapshots WHERE client_id = ? AND dataset_name = ?`, clientID, datasetName).Scan(&count)
return count, err
}
// GetAllDatasets gets all datasets
func (d *Database) GetAllDatasets() ([]*DatasetConfig, error) {
query := `SELECT id, client_id, dataset_name, storage_type, enabled FROM datasets`
rows, err := d.db.Query(query)
if err != nil {
return nil, err
}
defer rows.Close()
var datasets []*DatasetConfig
for rows.Next() {
dataset := &DatasetConfig{}
var enabled int
err := rows.Scan(&dataset.ID, &dataset.ClientID, &dataset.DatasetName, &dataset.StorageType, &enabled)
if err != nil {
return nil, err
}
dataset.Enabled = enabled == 1
datasets = append(datasets, dataset)
}
return datasets, nil
}
// GetSnapshotByID retrieves a specific snapshot
func (d *Database) GetSnapshotByID(clientID, snapshotID string) (*SnapshotMetadata, error) {
snap := &SnapshotMetadata{}
var compressed, incremental int
var baseSnapshot sql.NullString
query := `SELECT client_id, snapshot_id, timestamp, size_bytes, dataset_name,
storage_key, storage_type, compressed, incremental, base_snapshot
FROM snapshots WHERE client_id = ? AND snapshot_id = ?`
err := d.db.QueryRow(query, clientID, snapshotID).Scan(
&snap.ClientID, &snap.SnapshotID, &snap.Timestamp, &snap.SizeBytes,
&snap.DatasetName, &snap.StorageKey, &snap.StorageType,
&compressed, &incremental, &baseSnapshot,
)
if err == sql.ErrNoRows {
return nil, nil
}
if err != nil {
return nil, err
}
snap.Compressed = compressed == 1
snap.Incremental = incremental == 1
if baseSnapshot.Valid {
snap.BaseSnapshot = baseSnapshot.String
}
return snap, nil
}
// GetAllSnapshots retrieves all snapshots (for admin purposes)
func (d *Database) GetAllSnapshots() ([]*SnapshotMetadata, error) {
query := `SELECT client_id, snapshot_id, timestamp, size_bytes, dataset_name,
storage_key, storage_type, compressed, incremental, base_snapshot
FROM snapshots ORDER BY timestamp DESC`
rows, err := d.db.Query(query)
if err != nil {
return nil, err
}
defer rows.Close()
var snapshots []*SnapshotMetadata
for rows.Next() {
snap := &SnapshotMetadata{}
var compressed, incremental int
var baseSnapshot sql.NullString
err := rows.Scan(
&snap.ClientID, &snap.SnapshotID, &snap.Timestamp, &snap.SizeBytes,
&snap.DatasetName, &snap.StorageKey, &snap.StorageType,
&compressed, &incremental, &baseSnapshot,
)
if err != nil {
return nil, err
}
snap.Compressed = compressed == 1
snap.Incremental = incremental == 1
if baseSnapshot.Valid {
snap.BaseSnapshot = baseSnapshot.String
}
snapshots = append(snapshots, snap)
}
return snapshots, nil
}
// GetTotalSnapshotCount returns the total number of snapshots
func (d *Database) GetTotalSnapshotCount() (int, error) {
var count int
err := d.db.QueryRow(`SELECT COUNT(*) FROM snapshots`).Scan(&count)
return count, err
}
// GetTotalStorageUsed returns the total storage used across all clients
func (d *Database) GetTotalStorageUsed() (int64, error) {
var total sql.NullInt64
err := d.db.QueryRow(`SELECT SUM(size_bytes) FROM snapshots`).Scan(&total)
if err != nil {
return 0, err
}
if !total.Valid {
return 0, nil
}
return total.Int64, nil
}
// UpdateSnapshotTimestamp updates the timestamp of a snapshot (for rotation tracking)
func (d *Database) UpdateSnapshotTimestamp(clientID, snapshotID string, timestamp time.Time) error {
_, err := d.db.Exec(`UPDATE snapshots SET timestamp = ? WHERE client_id = ? AND snapshot_id = ?`,
timestamp, clientID, snapshotID)
return err
}
// Admin operations
// Admin represents an admin user
type Admin struct {
ID int
Username string
PasswordHash string
Role string
CreatedAt time.Time
UpdatedAt time.Time
}
// AdminSession represents an admin session
type AdminSession struct {
ID int
AdminID int
Token string
ExpiresAt time.Time
CreatedAt time.Time
}
// CreateAdmin creates a new admin user
func (d *Database) CreateAdmin(username, passwordHash, role string) error {
_, err := d.db.Exec(`
INSERT INTO admins (username, password_hash, role) VALUES (?, ?, ?)
`, username, passwordHash, role)
return err
}
// GetAdminByUsername retrieves an admin by username
func (d *Database) GetAdminByUsername(username string) (*Admin, error) {
admin := &Admin{}
err := d.db.QueryRow(`
SELECT id, username, password_hash, role, created_at, updated_at
FROM admins WHERE username = ?
`, username).Scan(&admin.ID, &admin.Username, &admin.PasswordHash, &admin.Role, &admin.CreatedAt, &admin.UpdatedAt)
if err == sql.ErrNoRows {
return nil, nil
}
if err != nil {
return nil, err
}
return admin, nil
}
// GetAdminByID retrieves an admin by ID
func (d *Database) GetAdminByID(id int) (*Admin, error) {
admin := &Admin{}
err := d.db.QueryRow(`
SELECT id, username, password_hash, role, created_at, updated_at
FROM admins WHERE id = ?
`, id).Scan(&admin.ID, &admin.Username, &admin.PasswordHash, &admin.Role, &admin.CreatedAt, &admin.UpdatedAt)
if err == sql.ErrNoRows {
return nil, nil
}
if err != nil {
return nil, err
}
return admin, nil
}
// CreateSession creates a new admin session
func (d *Database) CreateSession(adminID int, token string, expiresAt time.Time) error {
_, err := d.db.Exec(`
INSERT INTO admin_sessions (admin_id, token, expires_at) VALUES (?, ?, ?)
`, adminID, token, expiresAt)
return err
}
// GetSessionByToken retrieves a session by token
func (d *Database) GetSessionByToken(token string) (*AdminSession, error) {
session := &AdminSession{}
err := d.db.QueryRow(`
SELECT id, admin_id, token, expires_at, created_at
FROM admin_sessions WHERE token = ?
`, token).Scan(&session.ID, &session.AdminID, &session.Token, &session.ExpiresAt, &session.CreatedAt)
if err == sql.ErrNoRows {
return nil, nil
}
if err != nil {
return nil, err
}
return session, nil
}
// DeleteSession deletes a session (logout)
func (d *Database) DeleteSession(token string) error {
_, err := d.db.Exec(`DELETE FROM admin_sessions WHERE token = ?`, token)
return err
}
// CleanExpiredSessions removes all expired sessions
func (d *Database) CleanExpiredSessions() error {
_, err := d.db.Exec(`DELETE FROM admin_sessions WHERE expires_at < CURRENT_TIMESTAMP`)
return err
}
// CreateDefaultAdmin creates a default admin if none exists
func (d *Database) CreateDefaultAdmin() error {
var count int
err := d.db.QueryRow(`SELECT COUNT(*) FROM admins`).Scan(&count)
if err != nil {
return err
}
if count > 0 {
return nil
}
log.Println("No admins found, creating default admin 'admin'")
// Default password: admin123
defaultPasswordHash := hashAPIKey("admin123")
return d.CreateAdmin("admin", defaultPasswordHash, "admin")
}
// DeleteClient deletes a client and all its snapshots
func (d *Database) DeleteClient(clientID string) error {
_, err := d.db.Exec(`DELETE FROM clients WHERE client_id = ?`, clientID)
return err
}
// GetAllAdmins retrieves all admins
func (d *Database) GetAllAdmins() ([]*Admin, error) {
rows, err := d.db.Query(`
SELECT id, username, password_hash, role, created_at, updated_at
FROM admins ORDER BY id
`)
if err != nil {
return nil, err
}
defer rows.Close()
var admins []*Admin
for rows.Next() {
admin := &Admin{}
err := rows.Scan(&admin.ID, &admin.Username, &admin.PasswordHash, &admin.Role, &admin.CreatedAt, &admin.UpdatedAt)
if err != nil {
return nil, err
}
admins = append(admins, admin)
}
return admins, nil
}
// DeleteAdmin deletes an admin by ID
func (d *Database) DeleteAdmin(id int) error {
_, err := d.db.Exec(`DELETE FROM admins WHERE id = ?`, id)
return err
}
// UpdateAdminPassword updates an admin's password
func (d *Database) UpdateAdminPassword(id int, passwordHash string) error {
_, err := d.db.Exec(`UPDATE admins SET password_hash = ?, updated_at = CURRENT_TIMESTAMP WHERE id = ?`, passwordHash, id)
return err
}