68eaee0b9f
Security: - Encrypt Gitea tokens at rest (AES-256-GCM with MH_SECRET) - Secure cookie flag when behind HTTPS (X-Forwarded-Proto) - Password complexity (min 8 chars) - TOTP: defer persist until verified (totp_pending column) - Audit log table + logging on login/rename/password change Features: - Rename files/folders (double-click in tree, /api/files/rename) - beforeunload warning for unsaved changes - Mobile hamburger menu - PWA icons (192px, 512px) - Max file size enforcement (10MB) - Shared file read access (cross-user with permission check) Polish: - Toast notifications replace all alert() calls - Keyboard shortcut help overlay (Ctrl+/) - File rename via double-click in FileTree
153 lines
3.9 KiB
Go
153 lines
3.9 KiB
Go
package db
|
|
|
|
import (
|
|
"database/sql"
|
|
"os"
|
|
"path/filepath"
|
|
|
|
_ "modernc.org/sqlite"
|
|
)
|
|
|
|
type DB = sql.DB
|
|
|
|
func Open(path string) (*DB, error) {
|
|
dir := filepath.Dir(path)
|
|
if err := os.MkdirAll(dir, 0755); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
database, err := sql.Open("sqlite", path+"?_journal_mode=WAL&_busy_timeout=5000")
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if err := database.Ping(); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return database, nil
|
|
}
|
|
|
|
func Migrate(database *DB) error {
|
|
_, err := database.Exec(schema)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
// Add columns that may not exist yet (idempotent)
|
|
database.Exec("ALTER TABLE users ADD COLUMN totp_pending TEXT")
|
|
database.Exec("ALTER TABLE users ADD COLUMN audit_log_enabled INTEGER DEFAULT 0")
|
|
database.Exec(`CREATE TABLE IF NOT EXISTS audit_log (
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
user_id TEXT NOT NULL,
|
|
action TEXT NOT NULL,
|
|
detail TEXT,
|
|
created_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
)`)
|
|
return nil
|
|
}
|
|
|
|
var schema = `
|
|
CREATE TABLE IF NOT EXISTS users (
|
|
id TEXT PRIMARY KEY,
|
|
username TEXT UNIQUE NOT NULL,
|
|
email TEXT UNIQUE NOT NULL,
|
|
password_hash TEXT NOT NULL,
|
|
totp_secret TEXT,
|
|
encryption_enabled INTEGER DEFAULT 0,
|
|
encryption_salt BLOB,
|
|
recovery_key_hash TEXT,
|
|
is_admin INTEGER DEFAULT 0,
|
|
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
updated_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
);
|
|
|
|
CREATE TABLE IF NOT EXISTS files (
|
|
id TEXT PRIMARY KEY,
|
|
owner_id TEXT NOT NULL REFERENCES users(id),
|
|
path TEXT NOT NULL,
|
|
title TEXT,
|
|
encrypted INTEGER DEFAULT 0,
|
|
encrypted_content BLOB,
|
|
encrypted_file_key BLOB,
|
|
sync_flagged INTEGER DEFAULT 0,
|
|
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
updated_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
UNIQUE(owner_id, path)
|
|
);
|
|
|
|
CREATE TABLE IF NOT EXISTS virtual_tree (
|
|
id TEXT PRIMARY KEY,
|
|
user_id TEXT NOT NULL REFERENCES users(id),
|
|
parent_id TEXT REFERENCES virtual_tree(id),
|
|
name TEXT NOT NULL,
|
|
type TEXT NOT NULL,
|
|
target_file_id TEXT REFERENCES files(id),
|
|
sort_order INTEGER DEFAULT 0,
|
|
created_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
);
|
|
|
|
CREATE TABLE IF NOT EXISTS permissions (
|
|
id TEXT PRIMARY KEY,
|
|
file_id TEXT NOT NULL REFERENCES files(id),
|
|
user_id TEXT NOT NULL REFERENCES users(id),
|
|
level TEXT NOT NULL,
|
|
granted_by TEXT NOT NULL REFERENCES users(id),
|
|
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
UNIQUE(file_id, user_id)
|
|
);
|
|
|
|
CREATE TABLE IF NOT EXISTS git_remotes (
|
|
id TEXT PRIMARY KEY,
|
|
user_id TEXT NOT NULL REFERENCES users(id),
|
|
name TEXT NOT NULL,
|
|
url TEXT NOT NULL,
|
|
auth_token TEXT,
|
|
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
UNIQUE(user_id, name)
|
|
);
|
|
|
|
CREATE TABLE IF NOT EXISTS collab_state (
|
|
file_id TEXT PRIMARY KEY REFERENCES files(id),
|
|
yjs_state BLOB,
|
|
updated_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
);
|
|
|
|
CREATE TABLE IF NOT EXISTS sessions (
|
|
id TEXT PRIMARY KEY,
|
|
user_id TEXT NOT NULL REFERENCES users(id),
|
|
token_hash TEXT NOT NULL,
|
|
expires_at TEXT NOT NULL,
|
|
created_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
);
|
|
|
|
CREATE TABLE IF NOT EXISTS api_tokens (
|
|
id TEXT PRIMARY KEY,
|
|
user_id TEXT NOT NULL REFERENCES users(id),
|
|
name TEXT NOT NULL,
|
|
token_hash TEXT NOT NULL,
|
|
last_used_at TEXT,
|
|
created_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
);
|
|
|
|
CREATE TABLE IF NOT EXISTS build_jobs (
|
|
id TEXT PRIMARY KEY,
|
|
user_id TEXT NOT NULL REFERENCES users(id),
|
|
status TEXT NOT NULL DEFAULT 'pending',
|
|
spec_content TEXT NOT NULL,
|
|
gitea_url TEXT,
|
|
gitea_token TEXT,
|
|
gitea_org TEXT,
|
|
repo_name TEXT NOT NULL,
|
|
model TEXT,
|
|
log TEXT DEFAULT '',
|
|
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
updated_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
);
|
|
|
|
CREATE TABLE IF NOT EXISTS daemon_state (
|
|
id TEXT PRIMARY KEY DEFAULT 'singleton',
|
|
last_heartbeat TEXT,
|
|
status TEXT DEFAULT 'offline'
|
|
);
|
|
`
|