308 lines
12 KiB
Go
308 lines
12 KiB
Go
package db
|
|
|
|
import (
|
|
"database/sql"
|
|
"fmt"
|
|
)
|
|
|
|
const CurrentSchemaVersion = "2026-06-compat-baseline"
|
|
|
|
func (s *Store) migrate(conn *sql.DB, d dialect) error {
|
|
statements := []string{}
|
|
if d.name == "sqlite" {
|
|
statements = append(statements,
|
|
"PRAGMA busy_timeout = 5000",
|
|
"PRAGMA journal_mode = WAL",
|
|
"PRAGMA foreign_keys = ON",
|
|
)
|
|
}
|
|
statements = append(statements, schemaStatements(d)...)
|
|
for _, statement := range statements {
|
|
if _, err := conn.Exec(d.rebind(statement)); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return s.recordSchemaVersion(conn, d)
|
|
}
|
|
|
|
func schemaStatements(d dialect) []string {
|
|
keyText := d.keyTextType()
|
|
shortText := d.shortTextType()
|
|
mediumText := d.mediumTextType()
|
|
longText := d.longTextType()
|
|
return []string{
|
|
fmt.Sprintf(`CREATE TABLE IF NOT EXISTS schema_migrations (
|
|
version VARCHAR(64) NOT NULL PRIMARY KEY,
|
|
applied_at %s NOT NULL,
|
|
description VARCHAR(255) NOT NULL DEFAULT ''
|
|
)`, shortText),
|
|
fmt.Sprintf(`CREATE TABLE IF NOT EXISTS admin_users (
|
|
id %s,
|
|
username %s NOT NULL UNIQUE,
|
|
password_hash %s NOT NULL,
|
|
password_changed INTEGER NOT NULL DEFAULT 0,
|
|
created_at %s NOT NULL,
|
|
updated_at %s NOT NULL
|
|
)`, d.idType(), keyText, shortText, shortText, shortText),
|
|
fmt.Sprintf(`CREATE TABLE IF NOT EXISTS sessions (
|
|
id %s,
|
|
session_id %s NOT NULL UNIQUE,
|
|
username %s NOT NULL,
|
|
csrf %s NOT NULL,
|
|
expires_at %s NOT NULL,
|
|
created_at %s NOT NULL
|
|
)`, d.idType(), keyText, keyText, shortText, shortText, shortText),
|
|
fmt.Sprintf(`CREATE TABLE IF NOT EXISTS release_packages (
|
|
id %s,
|
|
product %s NOT NULL,
|
|
version %s NOT NULL,
|
|
platform %s NOT NULL,
|
|
arch %s NOT NULL,
|
|
file_name %s NOT NULL UNIQUE,
|
|
url %s NOT NULL,
|
|
sha256 %s NOT NULL,
|
|
size_bytes BIGINT NOT NULL DEFAULT 0,
|
|
enabled INTEGER NOT NULL DEFAULT 1,
|
|
created_at %s NOT NULL,
|
|
updated_at %s NOT NULL
|
|
)`, d.idType(), keyText, keyText, keyText, keyText, keyText, mediumText, shortText, shortText, shortText),
|
|
fmt.Sprintf(`CREATE TABLE IF NOT EXISTS release_notices (
|
|
id %s,
|
|
version %s NOT NULL UNIQUE,
|
|
build %s NOT NULL DEFAULT '',
|
|
channel %s NOT NULL DEFAULT 'stable',
|
|
title %s NOT NULL DEFAULT '',
|
|
message %s NOT NULL,
|
|
release_notes %s NOT NULL,
|
|
message_md %s NOT NULL,
|
|
release_notes_md %s NOT NULL,
|
|
download_url %s NOT NULL DEFAULT '',
|
|
notice_file %s NOT NULL DEFAULT '',
|
|
raw_json %s NOT NULL,
|
|
published_at %s NOT NULL DEFAULT '',
|
|
created_at %s NOT NULL,
|
|
updated_at %s NOT NULL
|
|
)`, d.idType(), keyText, shortText, shortText, mediumText, longText, longText, longText, longText, mediumText, keyText, longText, shortText, shortText, shortText),
|
|
fmt.Sprintf(`CREATE TABLE IF NOT EXISTS release_notice_revisions (
|
|
id %s,
|
|
version %s NOT NULL,
|
|
raw_json %s NOT NULL,
|
|
note %s NOT NULL DEFAULT '',
|
|
created_by %s NOT NULL DEFAULT '',
|
|
created_at %s NOT NULL
|
|
)`, d.idType(), keyText, longText, mediumText, keyText, shortText),
|
|
fmt.Sprintf(`CREATE TABLE IF NOT EXISTS feedback_tickets (
|
|
code %s PRIMARY KEY,
|
|
title %s NOT NULL,
|
|
type %s NOT NULL,
|
|
severity %s NOT NULL,
|
|
category %s NOT NULL DEFAULT '',
|
|
priority %s NOT NULL DEFAULT '',
|
|
contact %s NOT NULL DEFAULT '',
|
|
body %s NOT NULL,
|
|
status %s NOT NULL,
|
|
status_detail %s NOT NULL DEFAULT '',
|
|
public_reply %s NOT NULL,
|
|
note %s NOT NULL,
|
|
assignee %s NOT NULL DEFAULT '',
|
|
handled_by %s NOT NULL DEFAULT '',
|
|
due_at %s NOT NULL DEFAULT '',
|
|
resolved_at %s NOT NULL DEFAULT '',
|
|
archived_at %s NOT NULL DEFAULT '',
|
|
sla_level %s NOT NULL DEFAULT '',
|
|
source_channel %s NOT NULL DEFAULT '',
|
|
risk_score INTEGER NOT NULL DEFAULT 0,
|
|
resolution %s NOT NULL,
|
|
attachment %s NOT NULL DEFAULT '',
|
|
package_path %s NOT NULL DEFAULT '',
|
|
encrypted_package_path %s NOT NULL DEFAULT '',
|
|
package_sha256 %s NOT NULL DEFAULT '',
|
|
plain_package_sha256 %s NOT NULL DEFAULT '',
|
|
summary_text %s NOT NULL,
|
|
included_files %s NOT NULL,
|
|
mail_sent INTEGER NOT NULL DEFAULT 0,
|
|
remote_addr %s NOT NULL DEFAULT '',
|
|
tags %s NOT NULL,
|
|
created_at %s NOT NULL,
|
|
updated_at %s NOT NULL,
|
|
last_activity_at %s NOT NULL
|
|
)`, keyText, mediumText, keyText, keyText, keyText, keyText, mediumText, longText, keyText, mediumText, longText, longText, keyText, keyText, shortText, shortText, shortText, keyText, keyText, longText, mediumText, mediumText, mediumText, shortText, shortText, longText, longText, shortText, longText, shortText, shortText, shortText),
|
|
fmt.Sprintf(`CREATE TABLE IF NOT EXISTS feedback_comments (
|
|
id %s,
|
|
feedback_code %s NOT NULL,
|
|
author %s NOT NULL DEFAULT '',
|
|
body %s NOT NULL,
|
|
internal INTEGER NOT NULL DEFAULT 1,
|
|
created_at %s NOT NULL
|
|
)`, d.idType(), keyText, keyText, longText, shortText),
|
|
fmt.Sprintf(`CREATE TABLE IF NOT EXISTS feedback_attachments (
|
|
id %s,
|
|
feedback_code %s NOT NULL,
|
|
kind %s NOT NULL,
|
|
path %s NOT NULL,
|
|
file_name %s NOT NULL,
|
|
sha256 %s NOT NULL DEFAULT '',
|
|
size_bytes BIGINT NOT NULL DEFAULT 0,
|
|
created_at %s NOT NULL
|
|
)`, d.idType(), keyText, keyText, mediumText, mediumText, shortText, shortText),
|
|
fmt.Sprintf(`CREATE TABLE IF NOT EXISTS feedback_events (
|
|
id %s,
|
|
feedback_code %s NOT NULL,
|
|
event_type %s NOT NULL,
|
|
actor %s NOT NULL DEFAULT '',
|
|
from_value %s NOT NULL DEFAULT '',
|
|
to_value %s NOT NULL DEFAULT '',
|
|
message %s NOT NULL DEFAULT '',
|
|
created_at %s NOT NULL
|
|
)`, d.idType(), keyText, keyText, keyText, mediumText, mediumText, mediumText, shortText),
|
|
fmt.Sprintf(`CREATE TABLE IF NOT EXISTS feedback_tags (
|
|
feedback_code %s NOT NULL,
|
|
tag %s NOT NULL,
|
|
created_at %s NOT NULL,
|
|
PRIMARY KEY (feedback_code, tag)
|
|
)`, keyText, keyText, shortText),
|
|
fmt.Sprintf(`CREATE TABLE IF NOT EXISTS mail_records (
|
|
id %s,
|
|
feedback_code %s NOT NULL DEFAULT '',
|
|
kind %s NOT NULL DEFAULT '',
|
|
status %s NOT NULL DEFAULT '',
|
|
to_address %s NOT NULL DEFAULT '',
|
|
subject %s NOT NULL DEFAULT '',
|
|
plain_body %s NOT NULL,
|
|
html_body %s NOT NULL,
|
|
attachment_path %s NOT NULL DEFAULT '',
|
|
attachment_name %s NOT NULL DEFAULT '',
|
|
error_message %s NOT NULL,
|
|
created_at %s NOT NULL,
|
|
sent_at %s NOT NULL DEFAULT ''
|
|
)`, d.idType(), keyText, keyText, keyText, mediumText, mediumText, longText, longText, mediumText, mediumText, longText, shortText, shortText),
|
|
fmt.Sprintf(`CREATE TABLE IF NOT EXISTS source_categories (
|
|
id %s,
|
|
category_id %s NOT NULL UNIQUE,
|
|
name %s NOT NULL,
|
|
enabled INTEGER NOT NULL DEFAULT 1,
|
|
ui_config %s NOT NULL,
|
|
created_at %s NOT NULL,
|
|
updated_at %s NOT NULL
|
|
)`, d.idType(), keyText, shortText, longText, shortText, shortText),
|
|
fmt.Sprintf(`CREATE TABLE IF NOT EXISTS source_endpoints (
|
|
id %s,
|
|
category_id %s NOT NULL,
|
|
category_name %s NOT NULL,
|
|
source_id %s NOT NULL UNIQUE,
|
|
name %s NOT NULL,
|
|
description %s NOT NULL DEFAULT '',
|
|
method %s NOT NULL DEFAULT 'GET',
|
|
api_url %s NOT NULL DEFAULT '',
|
|
url_template %s NOT NULL DEFAULT '',
|
|
thumbnail_url %s NOT NULL DEFAULT '',
|
|
proxy_mode %s NOT NULL DEFAULT 'client_direct',
|
|
timeout_ms INTEGER NOT NULL DEFAULT 8000,
|
|
retry_count INTEGER NOT NULL DEFAULT 1,
|
|
cache_seconds INTEGER NOT NULL DEFAULT 300,
|
|
check_interval_sec INTEGER NOT NULL DEFAULT 300,
|
|
enabled INTEGER NOT NULL DEFAULT 1,
|
|
client_visible INTEGER NOT NULL DEFAULT 1,
|
|
supported_formats %s NOT NULL,
|
|
last_status %s NOT NULL DEFAULT 'unknown',
|
|
last_latency_ms INTEGER NOT NULL DEFAULT 0,
|
|
last_checked_at %s NOT NULL DEFAULT '',
|
|
last_error %s NOT NULL,
|
|
consecutive_failure INTEGER NOT NULL DEFAULT 0,
|
|
created_at %s NOT NULL,
|
|
updated_at %s NOT NULL
|
|
)`, d.idType(), keyText, shortText, keyText, shortText, mediumText, keyText, mediumText, mediumText, mediumText, keyText, longText, keyText, shortText, longText, shortText, shortText),
|
|
fmt.Sprintf(`CREATE TABLE IF NOT EXISTS endpoint_health_checks (
|
|
id %s,
|
|
source_db_id BIGINT NOT NULL,
|
|
status %s NOT NULL,
|
|
latency_ms INTEGER NOT NULL DEFAULT 0,
|
|
error %s NOT NULL,
|
|
checked_at %s NOT NULL
|
|
)`, d.idType(), keyText, longText, shortText),
|
|
fmt.Sprintf(`CREATE TABLE IF NOT EXISTS endpoint_call_logs (
|
|
id %s,
|
|
source_id %s NOT NULL,
|
|
status %s NOT NULL,
|
|
latency_ms INTEGER NOT NULL DEFAULT 0,
|
|
error %s NOT NULL,
|
|
client %s NOT NULL DEFAULT '',
|
|
created_at %s NOT NULL
|
|
)`, d.idType(), keyText, keyText, longText, mediumText, shortText),
|
|
fmt.Sprintf(`CREATE TABLE IF NOT EXISTS database_sync_jobs (
|
|
id %s,
|
|
direction %s NOT NULL,
|
|
status %s NOT NULL,
|
|
message %s NOT NULL,
|
|
tables_json %s NOT NULL,
|
|
started_at %s NOT NULL,
|
|
finished_at %s NOT NULL DEFAULT ''
|
|
)`, d.idType(), keyText, keyText, longText, longText, shortText, shortText),
|
|
fmt.Sprintf(`CREATE TABLE IF NOT EXISTS system_settings (
|
|
%s %s NOT NULL PRIMARY KEY,
|
|
value %s NOT NULL,
|
|
updated_at %s NOT NULL
|
|
)`, d.quoteIdent("key"), keyText, longText, shortText),
|
|
fmt.Sprintf(`CREATE TABLE IF NOT EXISTS legacy_sync_jobs (
|
|
id %s,
|
|
status %s NOT NULL,
|
|
summary %s NOT NULL,
|
|
stats_json %s NOT NULL,
|
|
started_at %s NOT NULL,
|
|
finished_at %s NOT NULL DEFAULT ''
|
|
)`, d.idType(), keyText, longText, longText, shortText, shortText),
|
|
fmt.Sprintf(`CREATE TABLE IF NOT EXISTS audit_logs (
|
|
id %s,
|
|
actor %s NOT NULL DEFAULT '',
|
|
type %s NOT NULL,
|
|
target %s NOT NULL DEFAULT '',
|
|
message %s NOT NULL,
|
|
ip %s NOT NULL DEFAULT '',
|
|
user_agent %s NOT NULL DEFAULT '',
|
|
created_at %s NOT NULL
|
|
)`, d.idType(), keyText, keyText, keyText, longText, keyText, mediumText, shortText),
|
|
fmt.Sprintf(`CREATE TABLE IF NOT EXISTS legacy_json_revisions (
|
|
id %s,
|
|
name %s NOT NULL,
|
|
raw %s NOT NULL,
|
|
note %s NOT NULL DEFAULT '',
|
|
created_by %s NOT NULL DEFAULT '',
|
|
created_at %s NOT NULL
|
|
)`, d.idType(), keyText, longText, mediumText, keyText, shortText),
|
|
fmt.Sprintf(`CREATE TABLE IF NOT EXISTS webhook_deliveries (
|
|
id %s,
|
|
webhook_name %s NOT NULL DEFAULT '',
|
|
event %s NOT NULL DEFAULT '',
|
|
status %s NOT NULL DEFAULT '',
|
|
attempts INTEGER NOT NULL DEFAULT 0,
|
|
response_code INTEGER NOT NULL DEFAULT 0,
|
|
error_message %s NOT NULL,
|
|
payload_sha256 %s NOT NULL DEFAULT '',
|
|
created_at %s NOT NULL,
|
|
finished_at %s NOT NULL DEFAULT ''
|
|
)`, d.idType(), keyText, keyText, keyText, longText, shortText, shortText, shortText),
|
|
`CREATE INDEX IF NOT EXISTS idx_feedback_tickets_activity ON feedback_tickets(last_activity_at)`,
|
|
`CREATE INDEX IF NOT EXISTS idx_feedback_comments_code ON feedback_comments(feedback_code)`,
|
|
`CREATE INDEX IF NOT EXISTS idx_feedback_attachments_code ON feedback_attachments(feedback_code)`,
|
|
`CREATE INDEX IF NOT EXISTS idx_feedback_events_code ON feedback_events(feedback_code)`,
|
|
`CREATE INDEX IF NOT EXISTS idx_mail_records_code ON mail_records(feedback_code)`,
|
|
`CREATE INDEX IF NOT EXISTS idx_endpoint_call_logs_source ON endpoint_call_logs(source_id)`,
|
|
`CREATE INDEX IF NOT EXISTS idx_audit_logs_created ON audit_logs(created_at)`,
|
|
`CREATE INDEX IF NOT EXISTS idx_audit_logs_type ON audit_logs(type)`,
|
|
`CREATE INDEX IF NOT EXISTS idx_audit_logs_target ON audit_logs(target)`,
|
|
`CREATE INDEX IF NOT EXISTS idx_legacy_json_revisions_name ON legacy_json_revisions(name, id)`,
|
|
`CREATE INDEX IF NOT EXISTS idx_release_notices_version ON release_notices(version)`,
|
|
`CREATE INDEX IF NOT EXISTS idx_release_notice_revisions_version ON release_notice_revisions(version, id)`,
|
|
}
|
|
}
|
|
|
|
func (s *Store) recordSchemaVersion(conn *sql.DB, d dialect) error {
|
|
columns := []string{"version", "applied_at", "description"}
|
|
_, err := conn.Exec(d.rebind(d.upsert("schema_migrations", columns, []string{"version"})),
|
|
CurrentSchemaVersion,
|
|
Now(),
|
|
"unified-management layered monolith baseline",
|
|
)
|
|
return err
|
|
}
|