No One Can Stop Me Now
This commit is contained in:
46
services/setec-manager/internal/db/backups.go
Normal file
46
services/setec-manager/internal/db/backups.go
Normal file
@@ -0,0 +1,46 @@
|
||||
package db
|
||||
|
||||
import "time"
|
||||
|
||||
type Backup struct {
|
||||
ID int64 `json:"id"`
|
||||
SiteID *int64 `json:"site_id"`
|
||||
BackupType string `json:"backup_type"`
|
||||
FilePath string `json:"file_path"`
|
||||
SizeBytes int64 `json:"size_bytes"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
}
|
||||
|
||||
func (d *DB) CreateBackup(siteID *int64, backupType, filePath string, sizeBytes int64) (int64, error) {
|
||||
result, err := d.conn.Exec(`INSERT INTO backups (site_id, backup_type, file_path, size_bytes)
|
||||
VALUES (?, ?, ?, ?)`, siteID, backupType, filePath, sizeBytes)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return result.LastInsertId()
|
||||
}
|
||||
|
||||
func (d *DB) ListBackups() ([]Backup, error) {
|
||||
rows, err := d.conn.Query(`SELECT id, site_id, backup_type, file_path, size_bytes, created_at
|
||||
FROM backups ORDER BY id DESC`)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
var backups []Backup
|
||||
for rows.Next() {
|
||||
var b Backup
|
||||
if err := rows.Scan(&b.ID, &b.SiteID, &b.BackupType, &b.FilePath,
|
||||
&b.SizeBytes, &b.CreatedAt); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
backups = append(backups, b)
|
||||
}
|
||||
return backups, rows.Err()
|
||||
}
|
||||
|
||||
func (d *DB) DeleteBackup(id int64) error {
|
||||
_, err := d.conn.Exec(`DELETE FROM backups WHERE id=?`, id)
|
||||
return err
|
||||
}
|
||||
163
services/setec-manager/internal/db/db.go
Normal file
163
services/setec-manager/internal/db/db.go
Normal file
@@ -0,0 +1,163 @@
|
||||
package db
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
_ "modernc.org/sqlite"
|
||||
)
|
||||
|
||||
type DB struct {
|
||||
conn *sql.DB
|
||||
}
|
||||
|
||||
func Open(path string) (*DB, error) {
|
||||
// Ensure directory exists
|
||||
if err := os.MkdirAll(filepath.Dir(path), 0755); err != nil {
|
||||
return nil, fmt.Errorf("create db dir: %w", err)
|
||||
}
|
||||
|
||||
conn, err := sql.Open("sqlite", path+"?_journal_mode=WAL&_busy_timeout=5000")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("open database: %w", err)
|
||||
}
|
||||
|
||||
conn.SetMaxOpenConns(1) // SQLite single-writer
|
||||
|
||||
db := &DB{conn: conn}
|
||||
if err := db.migrate(); err != nil {
|
||||
conn.Close()
|
||||
return nil, fmt.Errorf("migrate: %w", err)
|
||||
}
|
||||
|
||||
return db, nil
|
||||
}
|
||||
|
||||
func (d *DB) Close() error {
|
||||
return d.conn.Close()
|
||||
}
|
||||
|
||||
func (d *DB) Conn() *sql.DB {
|
||||
return d.conn
|
||||
}
|
||||
|
||||
func (d *DB) migrate() error {
|
||||
migrations := []string{
|
||||
migrateSites,
|
||||
migrateSystemUsers,
|
||||
migrateManagerUsers,
|
||||
migrateDeployments,
|
||||
migrateCronJobs,
|
||||
migrateFirewallRules,
|
||||
migrateFloatSessions,
|
||||
migrateBackups,
|
||||
migrateAuditLog,
|
||||
}
|
||||
|
||||
for _, m := range migrations {
|
||||
if _, err := d.conn.Exec(m); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
const migrateSites = `CREATE TABLE IF NOT EXISTS sites (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
domain TEXT NOT NULL UNIQUE,
|
||||
aliases TEXT DEFAULT '',
|
||||
app_type TEXT NOT NULL DEFAULT 'static',
|
||||
app_root TEXT NOT NULL,
|
||||
app_port INTEGER DEFAULT 0,
|
||||
app_entry TEXT DEFAULT '',
|
||||
git_repo TEXT DEFAULT '',
|
||||
git_branch TEXT DEFAULT 'main',
|
||||
ssl_enabled BOOLEAN DEFAULT FALSE,
|
||||
ssl_cert_path TEXT DEFAULT '',
|
||||
ssl_key_path TEXT DEFAULT '',
|
||||
ssl_auto BOOLEAN DEFAULT TRUE,
|
||||
enabled BOOLEAN DEFAULT TRUE,
|
||||
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
||||
);`
|
||||
|
||||
const migrateSystemUsers = `CREATE TABLE IF NOT EXISTS system_users (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
username TEXT NOT NULL UNIQUE,
|
||||
uid INTEGER,
|
||||
home_dir TEXT,
|
||||
shell TEXT DEFAULT '/bin/bash',
|
||||
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
||||
);`
|
||||
|
||||
const migrateManagerUsers = `CREATE TABLE IF NOT EXISTS manager_users (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
username TEXT NOT NULL UNIQUE,
|
||||
password_hash TEXT NOT NULL,
|
||||
role TEXT DEFAULT 'admin',
|
||||
force_change BOOLEAN DEFAULT FALSE,
|
||||
last_login DATETIME,
|
||||
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
||||
);`
|
||||
|
||||
const migrateDeployments = `CREATE TABLE IF NOT EXISTS deployments (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
site_id INTEGER REFERENCES sites(id),
|
||||
action TEXT NOT NULL,
|
||||
status TEXT DEFAULT 'pending',
|
||||
output TEXT DEFAULT '',
|
||||
started_at DATETIME,
|
||||
finished_at DATETIME
|
||||
);`
|
||||
|
||||
const migrateCronJobs = `CREATE TABLE IF NOT EXISTS cron_jobs (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
site_id INTEGER REFERENCES sites(id),
|
||||
job_type TEXT NOT NULL,
|
||||
schedule TEXT NOT NULL,
|
||||
enabled BOOLEAN DEFAULT TRUE,
|
||||
last_run DATETIME,
|
||||
next_run DATETIME
|
||||
);`
|
||||
|
||||
const migrateFirewallRules = `CREATE TABLE IF NOT EXISTS firewall_rules (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
direction TEXT DEFAULT 'in',
|
||||
protocol TEXT DEFAULT 'tcp',
|
||||
port TEXT NOT NULL,
|
||||
source TEXT DEFAULT 'any',
|
||||
action TEXT DEFAULT 'allow',
|
||||
comment TEXT DEFAULT '',
|
||||
enabled BOOLEAN DEFAULT TRUE
|
||||
);`
|
||||
|
||||
const migrateFloatSessions = `CREATE TABLE IF NOT EXISTS float_sessions (
|
||||
id TEXT PRIMARY KEY,
|
||||
user_id INTEGER REFERENCES manager_users(id),
|
||||
client_ip TEXT,
|
||||
client_agent TEXT,
|
||||
usb_bridge BOOLEAN DEFAULT FALSE,
|
||||
connected_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||
last_ping DATETIME,
|
||||
expires_at DATETIME
|
||||
);`
|
||||
|
||||
const migrateAuditLog = `CREATE TABLE IF NOT EXISTS audit_log (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
username TEXT NOT NULL,
|
||||
ip TEXT,
|
||||
action TEXT NOT NULL,
|
||||
detail TEXT DEFAULT '',
|
||||
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
||||
);`
|
||||
|
||||
const migrateBackups = `CREATE TABLE IF NOT EXISTS backups (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
site_id INTEGER REFERENCES sites(id),
|
||||
backup_type TEXT DEFAULT 'site',
|
||||
file_path TEXT NOT NULL,
|
||||
size_bytes INTEGER DEFAULT 0,
|
||||
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
||||
);`
|
||||
60
services/setec-manager/internal/db/deployments.go
Normal file
60
services/setec-manager/internal/db/deployments.go
Normal file
@@ -0,0 +1,60 @@
|
||||
package db
|
||||
|
||||
import "time"
|
||||
|
||||
type Deployment struct {
|
||||
ID int64 `json:"id"`
|
||||
SiteID *int64 `json:"site_id"`
|
||||
Action string `json:"action"`
|
||||
Status string `json:"status"`
|
||||
Output string `json:"output"`
|
||||
StartedAt *time.Time `json:"started_at"`
|
||||
FinishedAt *time.Time `json:"finished_at"`
|
||||
}
|
||||
|
||||
func (d *DB) CreateDeployment(siteID *int64, action string) (int64, error) {
|
||||
result, err := d.conn.Exec(`INSERT INTO deployments (site_id, action, status, started_at)
|
||||
VALUES (?, ?, 'running', CURRENT_TIMESTAMP)`, siteID, action)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return result.LastInsertId()
|
||||
}
|
||||
|
||||
func (d *DB) FinishDeployment(id int64, status, output string) error {
|
||||
_, err := d.conn.Exec(`UPDATE deployments SET status=?, output=?, finished_at=CURRENT_TIMESTAMP
|
||||
WHERE id=?`, status, output, id)
|
||||
return err
|
||||
}
|
||||
|
||||
func (d *DB) ListDeployments(siteID *int64, limit int) ([]Deployment, error) {
|
||||
var rows_query string
|
||||
var args []interface{}
|
||||
|
||||
if siteID != nil {
|
||||
rows_query = `SELECT id, site_id, action, status, output, started_at, finished_at
|
||||
FROM deployments WHERE site_id=? ORDER BY id DESC LIMIT ?`
|
||||
args = []interface{}{*siteID, limit}
|
||||
} else {
|
||||
rows_query = `SELECT id, site_id, action, status, output, started_at, finished_at
|
||||
FROM deployments ORDER BY id DESC LIMIT ?`
|
||||
args = []interface{}{limit}
|
||||
}
|
||||
|
||||
rows, err := d.conn.Query(rows_query, args...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
var deps []Deployment
|
||||
for rows.Next() {
|
||||
var dep Deployment
|
||||
if err := rows.Scan(&dep.ID, &dep.SiteID, &dep.Action, &dep.Status,
|
||||
&dep.Output, &dep.StartedAt, &dep.FinishedAt); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
deps = append(deps, dep)
|
||||
}
|
||||
return deps, rows.Err()
|
||||
}
|
||||
70
services/setec-manager/internal/db/float.go
Normal file
70
services/setec-manager/internal/db/float.go
Normal file
@@ -0,0 +1,70 @@
|
||||
package db
|
||||
|
||||
import "time"
|
||||
|
||||
type FloatSession struct {
|
||||
ID string `json:"id"`
|
||||
UserID int64 `json:"user_id"`
|
||||
ClientIP string `json:"client_ip"`
|
||||
ClientAgent string `json:"client_agent"`
|
||||
USBBridge bool `json:"usb_bridge"`
|
||||
ConnectedAt time.Time `json:"connected_at"`
|
||||
LastPing *time.Time `json:"last_ping"`
|
||||
ExpiresAt time.Time `json:"expires_at"`
|
||||
}
|
||||
|
||||
func (d *DB) CreateFloatSession(id string, userID int64, clientIP, agent string, expiresAt time.Time) error {
|
||||
_, err := d.conn.Exec(`INSERT INTO float_sessions (id, user_id, client_ip, client_agent, expires_at)
|
||||
VALUES (?, ?, ?, ?, ?)`, id, userID, clientIP, agent, expiresAt)
|
||||
return err
|
||||
}
|
||||
|
||||
func (d *DB) GetFloatSession(id string) (*FloatSession, error) {
|
||||
var s FloatSession
|
||||
err := d.conn.QueryRow(`SELECT id, user_id, client_ip, client_agent, usb_bridge,
|
||||
connected_at, last_ping, expires_at FROM float_sessions WHERE id=?`, id).
|
||||
Scan(&s.ID, &s.UserID, &s.ClientIP, &s.ClientAgent, &s.USBBridge,
|
||||
&s.ConnectedAt, &s.LastPing, &s.ExpiresAt)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &s, nil
|
||||
}
|
||||
|
||||
func (d *DB) ListFloatSessions() ([]FloatSession, error) {
|
||||
rows, err := d.conn.Query(`SELECT id, user_id, client_ip, client_agent, usb_bridge,
|
||||
connected_at, last_ping, expires_at FROM float_sessions ORDER BY connected_at DESC`)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
var sessions []FloatSession
|
||||
for rows.Next() {
|
||||
var s FloatSession
|
||||
if err := rows.Scan(&s.ID, &s.UserID, &s.ClientIP, &s.ClientAgent, &s.USBBridge,
|
||||
&s.ConnectedAt, &s.LastPing, &s.ExpiresAt); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
sessions = append(sessions, s)
|
||||
}
|
||||
return sessions, rows.Err()
|
||||
}
|
||||
|
||||
func (d *DB) DeleteFloatSession(id string) error {
|
||||
_, err := d.conn.Exec(`DELETE FROM float_sessions WHERE id=?`, id)
|
||||
return err
|
||||
}
|
||||
|
||||
func (d *DB) PingFloatSession(id string) error {
|
||||
_, err := d.conn.Exec(`UPDATE float_sessions SET last_ping=CURRENT_TIMESTAMP WHERE id=?`, id)
|
||||
return err
|
||||
}
|
||||
|
||||
func (d *DB) CleanExpiredFloatSessions() (int64, error) {
|
||||
result, err := d.conn.Exec(`DELETE FROM float_sessions WHERE expires_at < CURRENT_TIMESTAMP`)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return result.RowsAffected()
|
||||
}
|
||||
107
services/setec-manager/internal/db/sites.go
Normal file
107
services/setec-manager/internal/db/sites.go
Normal file
@@ -0,0 +1,107 @@
|
||||
package db
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"time"
|
||||
)
|
||||
|
||||
type Site struct {
|
||||
ID int64 `json:"id"`
|
||||
Domain string `json:"domain"`
|
||||
Aliases string `json:"aliases"`
|
||||
AppType string `json:"app_type"`
|
||||
AppRoot string `json:"app_root"`
|
||||
AppPort int `json:"app_port"`
|
||||
AppEntry string `json:"app_entry"`
|
||||
GitRepo string `json:"git_repo"`
|
||||
GitBranch string `json:"git_branch"`
|
||||
SSLEnabled bool `json:"ssl_enabled"`
|
||||
SSLCertPath string `json:"ssl_cert_path"`
|
||||
SSLKeyPath string `json:"ssl_key_path"`
|
||||
SSLAuto bool `json:"ssl_auto"`
|
||||
Enabled bool `json:"enabled"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
}
|
||||
|
||||
func (d *DB) ListSites() ([]Site, error) {
|
||||
rows, err := d.conn.Query(`SELECT id, domain, aliases, app_type, app_root, app_port,
|
||||
app_entry, git_repo, git_branch, ssl_enabled, ssl_cert_path, ssl_key_path,
|
||||
ssl_auto, enabled, created_at, updated_at FROM sites ORDER BY domain`)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
var sites []Site
|
||||
for rows.Next() {
|
||||
var s Site
|
||||
if err := rows.Scan(&s.ID, &s.Domain, &s.Aliases, &s.AppType, &s.AppRoot,
|
||||
&s.AppPort, &s.AppEntry, &s.GitRepo, &s.GitBranch, &s.SSLEnabled,
|
||||
&s.SSLCertPath, &s.SSLKeyPath, &s.SSLAuto, &s.Enabled,
|
||||
&s.CreatedAt, &s.UpdatedAt); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
sites = append(sites, s)
|
||||
}
|
||||
return sites, rows.Err()
|
||||
}
|
||||
|
||||
func (d *DB) GetSite(id int64) (*Site, error) {
|
||||
var s Site
|
||||
err := d.conn.QueryRow(`SELECT id, domain, aliases, app_type, app_root, app_port,
|
||||
app_entry, git_repo, git_branch, ssl_enabled, ssl_cert_path, ssl_key_path,
|
||||
ssl_auto, enabled, created_at, updated_at FROM sites WHERE id = ?`, id).
|
||||
Scan(&s.ID, &s.Domain, &s.Aliases, &s.AppType, &s.AppRoot,
|
||||
&s.AppPort, &s.AppEntry, &s.GitRepo, &s.GitBranch, &s.SSLEnabled,
|
||||
&s.SSLCertPath, &s.SSLKeyPath, &s.SSLAuto, &s.Enabled,
|
||||
&s.CreatedAt, &s.UpdatedAt)
|
||||
if err == sql.ErrNoRows {
|
||||
return nil, nil
|
||||
}
|
||||
return &s, err
|
||||
}
|
||||
|
||||
func (d *DB) GetSiteByDomain(domain string) (*Site, error) {
|
||||
var s Site
|
||||
err := d.conn.QueryRow(`SELECT id, domain, aliases, app_type, app_root, app_port,
|
||||
app_entry, git_repo, git_branch, ssl_enabled, ssl_cert_path, ssl_key_path,
|
||||
ssl_auto, enabled, created_at, updated_at FROM sites WHERE domain = ?`, domain).
|
||||
Scan(&s.ID, &s.Domain, &s.Aliases, &s.AppType, &s.AppRoot,
|
||||
&s.AppPort, &s.AppEntry, &s.GitRepo, &s.GitBranch, &s.SSLEnabled,
|
||||
&s.SSLCertPath, &s.SSLKeyPath, &s.SSLAuto, &s.Enabled,
|
||||
&s.CreatedAt, &s.UpdatedAt)
|
||||
if err == sql.ErrNoRows {
|
||||
return nil, nil
|
||||
}
|
||||
return &s, err
|
||||
}
|
||||
|
||||
func (d *DB) CreateSite(s *Site) (int64, error) {
|
||||
result, err := d.conn.Exec(`INSERT INTO sites (domain, aliases, app_type, app_root, app_port,
|
||||
app_entry, git_repo, git_branch, ssl_enabled, ssl_cert_path, ssl_key_path, ssl_auto, enabled)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
||||
s.Domain, s.Aliases, s.AppType, s.AppRoot, s.AppPort,
|
||||
s.AppEntry, s.GitRepo, s.GitBranch, s.SSLEnabled,
|
||||
s.SSLCertPath, s.SSLKeyPath, s.SSLAuto, s.Enabled)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return result.LastInsertId()
|
||||
}
|
||||
|
||||
func (d *DB) UpdateSite(s *Site) error {
|
||||
_, err := d.conn.Exec(`UPDATE sites SET domain=?, aliases=?, app_type=?, app_root=?,
|
||||
app_port=?, app_entry=?, git_repo=?, git_branch=?, ssl_enabled=?,
|
||||
ssl_cert_path=?, ssl_key_path=?, ssl_auto=?, enabled=?, updated_at=CURRENT_TIMESTAMP
|
||||
WHERE id=?`,
|
||||
s.Domain, s.Aliases, s.AppType, s.AppRoot, s.AppPort,
|
||||
s.AppEntry, s.GitRepo, s.GitBranch, s.SSLEnabled,
|
||||
s.SSLCertPath, s.SSLKeyPath, s.SSLAuto, s.Enabled, s.ID)
|
||||
return err
|
||||
}
|
||||
|
||||
func (d *DB) DeleteSite(id int64) error {
|
||||
_, err := d.conn.Exec(`DELETE FROM sites WHERE id=?`, id)
|
||||
return err
|
||||
}
|
||||
124
services/setec-manager/internal/db/users.go
Normal file
124
services/setec-manager/internal/db/users.go
Normal file
@@ -0,0 +1,124 @@
|
||||
package db
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"time"
|
||||
|
||||
"golang.org/x/crypto/bcrypt"
|
||||
)
|
||||
|
||||
type ManagerUser struct {
|
||||
ID int64 `json:"id"`
|
||||
Username string `json:"username"`
|
||||
PasswordHash string `json:"-"`
|
||||
Role string `json:"role"`
|
||||
ForceChange bool `json:"force_change"`
|
||||
LastLogin *time.Time `json:"last_login"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
}
|
||||
|
||||
func (d *DB) ListManagerUsers() ([]ManagerUser, error) {
|
||||
rows, err := d.conn.Query(`SELECT id, username, password_hash, role, force_change,
|
||||
last_login, created_at FROM manager_users ORDER BY username`)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
var users []ManagerUser
|
||||
for rows.Next() {
|
||||
var u ManagerUser
|
||||
if err := rows.Scan(&u.ID, &u.Username, &u.PasswordHash, &u.Role,
|
||||
&u.ForceChange, &u.LastLogin, &u.CreatedAt); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
users = append(users, u)
|
||||
}
|
||||
return users, rows.Err()
|
||||
}
|
||||
|
||||
func (d *DB) GetManagerUser(username string) (*ManagerUser, error) {
|
||||
var u ManagerUser
|
||||
err := d.conn.QueryRow(`SELECT id, username, password_hash, role, force_change,
|
||||
last_login, created_at FROM manager_users WHERE username = ?`, username).
|
||||
Scan(&u.ID, &u.Username, &u.PasswordHash, &u.Role,
|
||||
&u.ForceChange, &u.LastLogin, &u.CreatedAt)
|
||||
if err == sql.ErrNoRows {
|
||||
return nil, nil
|
||||
}
|
||||
return &u, err
|
||||
}
|
||||
|
||||
func (d *DB) GetManagerUserByID(id int64) (*ManagerUser, error) {
|
||||
var u ManagerUser
|
||||
err := d.conn.QueryRow(`SELECT id, username, password_hash, role, force_change,
|
||||
last_login, created_at FROM manager_users WHERE id = ?`, id).
|
||||
Scan(&u.ID, &u.Username, &u.PasswordHash, &u.Role,
|
||||
&u.ForceChange, &u.LastLogin, &u.CreatedAt)
|
||||
if err == sql.ErrNoRows {
|
||||
return nil, nil
|
||||
}
|
||||
return &u, err
|
||||
}
|
||||
|
||||
func (d *DB) CreateManagerUser(username, password, role string) (int64, error) {
|
||||
hash, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
result, err := d.conn.Exec(`INSERT INTO manager_users (username, password_hash, role)
|
||||
VALUES (?, ?, ?)`, username, string(hash), role)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return result.LastInsertId()
|
||||
}
|
||||
|
||||
func (d *DB) UpdateManagerUserPassword(id int64, password string) error {
|
||||
hash, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = d.conn.Exec(`UPDATE manager_users SET password_hash=?, force_change=FALSE WHERE id=?`,
|
||||
string(hash), id)
|
||||
return err
|
||||
}
|
||||
|
||||
func (d *DB) UpdateManagerUserRole(id int64, role string) error {
|
||||
_, err := d.conn.Exec(`UPDATE manager_users SET role=? WHERE id=?`, role, id)
|
||||
return err
|
||||
}
|
||||
|
||||
func (d *DB) DeleteManagerUser(id int64) error {
|
||||
_, err := d.conn.Exec(`DELETE FROM manager_users WHERE id=?`, id)
|
||||
return err
|
||||
}
|
||||
|
||||
func (d *DB) UpdateLoginTimestamp(id int64) error {
|
||||
_, err := d.conn.Exec(`UPDATE manager_users SET last_login=CURRENT_TIMESTAMP WHERE id=?`, id)
|
||||
return err
|
||||
}
|
||||
|
||||
func (d *DB) ManagerUserCount() (int, error) {
|
||||
var count int
|
||||
err := d.conn.QueryRow(`SELECT COUNT(*) FROM manager_users`).Scan(&count)
|
||||
return count, err
|
||||
}
|
||||
|
||||
func (d *DB) AuthenticateUser(username, password string) (*ManagerUser, error) {
|
||||
u, err := d.GetManagerUser(username)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if u == nil {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
if err := bcrypt.CompareHashAndPassword([]byte(u.PasswordHash), []byte(password)); err != nil {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
d.UpdateLoginTimestamp(u.ID)
|
||||
return u, nil
|
||||
}
|
||||
Reference in New Issue
Block a user