add admin panel
This commit is contained in:
654
internal/server/database.go
Normal file
654
internal/server/database.go
Normal file
@@ -0,0 +1,654 @@
|
||||
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)
|
||||
}
|
||||
|
||||
// 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)
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
Reference in New Issue
Block a user