1
0
mirror of git://f0xx.org/ac/ac-deploy synced 2026-07-29 06:17:57 +03:00

cluster0: lab seeds, Gitea mirrors, compose mail, verify tooling

Freeze lab-seeds backend for COMPOSE_SKIP_MONOLITH; fix Gitea mirror scripts
for 1.25 API; add secrets.lab.env template, verify-mail-lab, LAB_PUBLIC_ORIGIN,
and credentials pointers for mirror token recovery.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Anton Afanasyeu
2026-06-23 17:07:46 +02:00
parent 98f30786b7
commit c554dc761d
209 changed files with 31249 additions and 6 deletions

View File

@@ -0,0 +1,46 @@
-- Phase 1: companies, memberships, devices, report scoping.
-- Idempotent on fresh DB; existing installs: prefer app auto-migration (Database::ensureRbacSchema).
-- Manual run: mysql -u root -p androidcast_crashes < sql/migrations/001_rbac_companies.sql
USE androidcast_crashes;
CREATE TABLE IF NOT EXISTS companies (
id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
slug VARCHAR(64) NOT NULL,
name VARCHAR(128) NOT NULL,
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
UNIQUE KEY uq_companies_slug (slug)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
CREATE TABLE IF NOT EXISTS company_memberships (
id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
user_id INT UNSIGNED NOT NULL,
company_id INT UNSIGNED NOT NULL,
role ENUM('owner', 'admin', 'member') NOT NULL DEFAULT 'member',
permissions_json LONGTEXT NULL,
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
UNIQUE KEY uq_membership_user_company (user_id, company_id),
KEY idx_membership_company (company_id),
CONSTRAINT fk_membership_user FOREIGN KEY (user_id) REFERENCES users (id) ON DELETE CASCADE,
CONSTRAINT fk_membership_company FOREIGN KEY (company_id) REFERENCES companies (id) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
CREATE TABLE IF NOT EXISTS devices (
id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
company_id INT UNSIGNED NOT NULL,
external_id VARCHAR(128) NOT NULL,
display_name VARCHAR(256) NULL,
manufacturer VARCHAR(128) NULL,
model VARCHAR(128) NULL,
source ENUM('auto', 'manual', 'barcode') NOT NULL DEFAULT 'auto',
registered_at_ms BIGINT NOT NULL,
last_seen_at_ms BIGINT NOT NULL,
meta_json LONGTEXT NULL,
UNIQUE KEY uq_devices_company_external (company_id, external_id),
KEY idx_devices_company (company_id),
CONSTRAINT fk_devices_company FOREIGN KEY (company_id) REFERENCES companies (id) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
-- reports.company_id / reports.device_id: run 002_reports_company_columns.sql as root (app user cannot ALTER)
INSERT IGNORE INTO companies (id, slug, name) VALUES (1, 'default', 'Default');

View File

@@ -0,0 +1,24 @@
-- Add RBAC columns to existing reports table (requires ALTER — run as MySQL/MariaDB root).
-- App user androidcast typically has SELECT,INSERT,UPDATE,DELETE only.
--
-- mysql -u root -p androidcast_crashes < sql/migrations/002_reports_company_columns.sql
--
-- If 001_rbac_companies.sql was not applied yet, run that first.
USE androidcast_crashes;
INSERT IGNORE INTO companies (id, slug, name) VALUES (1, 'default', 'Default');
ALTER TABLE reports
ADD COLUMN IF NOT EXISTS company_id INT UNSIGNED NOT NULL DEFAULT 1 AFTER id,
ADD COLUMN IF NOT EXISTS device_id BIGINT UNSIGNED NULL AFTER company_id;
UPDATE reports SET company_id = 1 WHERE company_id IS NULL OR company_id = 0;
ALTER TABLE reports ADD INDEX IF NOT EXISTS idx_reports_company (company_id);
ALTER TABLE reports ADD INDEX IF NOT EXISTS idx_reports_device (device_id);
INSERT IGNORE INTO company_memberships (user_id, company_id, role)
SELECT u.id, 1,
CASE WHEN LOWER(u.role) IN ('root', 'admin', 'platform_admin') THEN 'owner' ELSE 'member' END
FROM users u;

View File

@@ -0,0 +1,32 @@
-- Tickets (tasks / QA / user issues) — same console as crash reports.
-- Run: mysql -u root -p androidcast_crashes < sql/migrations/003_tickets.sql
CREATE TABLE IF NOT EXISTS tickets (
id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
company_id INT UNSIGNED NOT NULL DEFAULT 1,
device_id BIGINT UNSIGNED NULL,
ticket_id VARCHAR(128) NOT NULL,
title VARCHAR(256) NOT NULL,
brief TEXT NULL,
body LONGTEXT NOT NULL,
opened_at_ms BIGINT NOT NULL,
created_at_ms BIGINT NOT NULL,
updated_at_ms BIGINT NOT NULL,
owner_username VARCHAR(64) NOT NULL DEFAULT '',
verified_by_username VARCHAR(64) NULL,
device_model VARCHAR(128) NULL,
app_package VARCHAR(128) NULL,
app_version VARCHAR(64) NULL,
os_name VARCHAR(64) NULL,
os_version VARCHAR(128) NULL,
rating TINYINT UNSIGNED NOT NULL DEFAULT 0,
tags_json LONGTEXT NOT NULL,
payload_json LONGTEXT NOT NULL,
UNIQUE KEY uq_tickets_ticket_id (ticket_id),
KEY idx_tickets_company (company_id),
KEY idx_tickets_opened (opened_at_ms),
KEY idx_tickets_updated (updated_at_ms),
KEY idx_tickets_owner (owner_username),
CONSTRAINT fk_tickets_company FOREIGN KEY (company_id) REFERENCES companies (id),
CONSTRAINT fk_tickets_device FOREIGN KEY (device_id) REFERENCES devices (id) ON DELETE SET NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

View File

@@ -0,0 +1,27 @@
CREATE TABLE IF NOT EXISTS tickets (
id INTEGER PRIMARY KEY AUTOINCREMENT,
company_id INTEGER NOT NULL DEFAULT 1,
device_id INTEGER NULL,
ticket_id TEXT NOT NULL UNIQUE,
title TEXT NOT NULL,
brief TEXT,
body TEXT NOT NULL,
opened_at_ms INTEGER NOT NULL,
created_at_ms INTEGER NOT NULL,
updated_at_ms INTEGER NOT NULL,
owner_username TEXT NOT NULL DEFAULT '',
verified_by_username TEXT,
device_model TEXT,
app_package TEXT,
app_version TEXT,
os_name TEXT,
os_version TEXT,
rating INTEGER NOT NULL DEFAULT 0,
tags_json TEXT NOT NULL DEFAULT '[]',
payload_json TEXT NOT NULL,
FOREIGN KEY (company_id) REFERENCES companies(id),
FOREIGN KEY (device_id) REFERENCES devices(id) ON DELETE SET NULL
);
CREATE INDEX IF NOT EXISTS idx_tickets_company ON tickets(company_id);
CREATE INDEX IF NOT EXISTS idx_tickets_opened ON tickets(opened_at_ms DESC);

View File

@@ -0,0 +1,18 @@
-- Ticket workflow, assignees, comments. Run as MySQL root on existing DBs.
-- mysql -u root -p androidcast_crashes < sql/migrations/004_ticket_workflow.sql
ALTER TABLE tickets
ADD COLUMN workflow_state VARCHAR(32) NOT NULL DEFAULT 'triage' AFTER tags_json,
ADD COLUMN assignees_json LONGTEXT NOT NULL DEFAULT '[]' AFTER workflow_state;
CREATE TABLE IF NOT EXISTS ticket_comments (
id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
ticket_id BIGINT UNSIGNED NOT NULL,
author_username VARCHAR(64) NOT NULL,
body LONGTEXT NOT NULL,
created_at_ms BIGINT NOT NULL,
updated_at_ms BIGINT NOT NULL,
KEY idx_ticket_comments_ticket (ticket_id),
KEY idx_ticket_comments_created (created_at_ms),
CONSTRAINT fk_ticket_comments_ticket FOREIGN KEY (ticket_id) REFERENCES tickets (id) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

View File

@@ -0,0 +1,13 @@
ALTER TABLE tickets ADD COLUMN workflow_state TEXT NOT NULL DEFAULT 'triage';
ALTER TABLE tickets ADD COLUMN assignees_json TEXT NOT NULL DEFAULT '[]';
CREATE TABLE IF NOT EXISTS ticket_comments (
id INTEGER PRIMARY KEY AUTOINCREMENT,
ticket_id INTEGER NOT NULL,
author_username TEXT NOT NULL,
body TEXT NOT NULL,
created_at_ms INTEGER NOT NULL,
updated_at_ms INTEGER NOT NULL,
FOREIGN KEY (ticket_id) REFERENCES tickets (id) ON DELETE CASCADE
);
CREATE INDEX IF NOT EXISTS idx_ticket_comments_ticket ON ticket_comments (ticket_id);

View File

@@ -0,0 +1,20 @@
-- Ticket attachments: uploaded files + external links (incl. Google URLs).
-- mysql -u root -p androidcast_crashes < sql/migrations/005_ticket_attachments.sql
CREATE TABLE IF NOT EXISTS ticket_attachments (
id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
ticket_id BIGINT UNSIGNED NOT NULL,
kind ENUM('file', 'link') NOT NULL,
title VARCHAR(256) NOT NULL DEFAULT '',
external_url TEXT NULL,
provider VARCHAR(32) NOT NULL DEFAULT 'generic',
storage_name VARCHAR(128) NULL,
original_name VARCHAR(256) NULL,
mime_type VARCHAR(128) NULL,
size_bytes BIGINT UNSIGNED NULL,
created_by VARCHAR(64) NOT NULL,
created_at_ms BIGINT NOT NULL,
KEY idx_ticket_attachments_ticket (ticket_id),
KEY idx_ticket_attachments_created (created_at_ms),
CONSTRAINT fk_ticket_attachments_ticket FOREIGN KEY (ticket_id) REFERENCES tickets (id) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

View File

@@ -0,0 +1,16 @@
CREATE TABLE IF NOT EXISTS ticket_attachments (
id INTEGER PRIMARY KEY AUTOINCREMENT,
ticket_id INTEGER NOT NULL,
kind TEXT NOT NULL CHECK (kind IN ('file', 'link')),
title TEXT NOT NULL DEFAULT '',
external_url TEXT NULL,
provider TEXT NOT NULL DEFAULT 'generic',
storage_name TEXT NULL,
original_name TEXT NULL,
mime_type TEXT NULL,
size_bytes INTEGER NULL,
created_by TEXT NOT NULL,
created_at_ms INTEGER NOT NULL,
FOREIGN KEY (ticket_id) REFERENCES tickets (id) ON DELETE CASCADE
);
CREATE INDEX IF NOT EXISTS idx_ticket_attachments_ticket ON ticket_attachments (ticket_id);

View File

@@ -0,0 +1,30 @@
-- Graph session metrics (direct MariaDB ingest; no ETL).
-- Run as root on existing servers: mysql -u root -p androidcast_crashes < sql/migrations/006_graph_sessions.sql
CREATE TABLE IF NOT EXISTS graph_sessions (
id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
session_id VARCHAR(96) NOT NULL,
company_id INT UNSIGNED NOT NULL DEFAULT 1,
user_id INT UNSIGNED NULL,
device_id VARCHAR(128) NULL,
app_version VARCHAR(64) NULL,
sdk_int INT NULL,
started_at_ms BIGINT NOT NULL,
ended_at_ms BIGINT NOT NULL,
duration_s INT NOT NULL,
completed TINYINT(1) NOT NULL DEFAULT 1,
direction ENUM('sender','receiver') NOT NULL DEFAULT 'sender',
transport VARCHAR(24) NOT NULL DEFAULT 'wifi',
avg_kbps INT NOT NULL DEFAULT 0,
recv_kbps INT NOT NULL DEFAULT 0,
packet_loss_pct DECIMAL(6,3) NOT NULL DEFAULT 0.0,
ntp_source VARCHAR(32) NOT NULL DEFAULT 'device',
ntp_correction_s INT NULL,
install_source VARCHAR(32) NOT NULL DEFAULT 'direct',
meta_json LONGTEXT NULL,
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
UNIQUE KEY uq_graph_session_id (session_id),
KEY idx_graph_company_time (company_id, started_at_ms),
KEY idx_graph_device (device_id),
KEY idx_graph_app_version (app_version)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

View File

@@ -0,0 +1,6 @@
-- SQLite graph_sessions indexes (table may already exist via GraphRepository::ensureSchema).
CREATE INDEX IF NOT EXISTS idx_graph_company_time ON graph_sessions (company_id, started_at_ms);
CREATE INDEX IF NOT EXISTS idx_graph_user_time ON graph_sessions (user_id, started_at_ms);
CREATE INDEX IF NOT EXISTS idx_graph_started ON graph_sessions (started_at_ms);
CREATE INDEX IF NOT EXISTS idx_graph_device ON graph_sessions (device_id);

View File

@@ -0,0 +1,56 @@
-- Remote access control plane (WireGuard v1, reverse SSH reserved).
-- MariaDB / production: mysql -u root -p androidcast_crashes < sql/migrations/007_remote_access.sql
CREATE TABLE IF NOT EXISTS remote_access_devices (
id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
device_id VARCHAR(128) NOT NULL,
company_id INT UNSIGNED NOT NULL DEFAULT 1,
whitelisted TINYINT(1) NOT NULL DEFAULT 0,
opt_in_mode ENUM('none','wireguard','rssh') NOT NULL DEFAULT 'none',
last_random VARCHAR(96) NULL,
app_version VARCHAR(64) NULL,
last_seen_at TIMESTAMP NULL,
notes TEXT NULL,
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
UNIQUE KEY uq_ra_device (device_id),
KEY idx_ra_device_company (company_id),
KEY idx_ra_device_whitelist (whitelisted, opt_in_mode)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
CREATE TABLE IF NOT EXISTS remote_access_sessions (
id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
session_id VARCHAR(36) NOT NULL,
device_id VARCHAR(128) NOT NULL,
company_id INT UNSIGNED NOT NULL DEFAULT 1,
operator_user_id INT UNSIGNED NULL,
status ENUM('pending','active','inactive','closed','closed_timeout') NOT NULL DEFAULT 'pending',
tunnel ENUM('wireguard','ssh_reverse') NOT NULL DEFAULT 'wireguard',
endpoint VARCHAR(255) NULL,
wg_client_private_key VARCHAR(64) NULL,
wg_client_address VARCHAR(64) NULL,
wg_server_public_key VARCHAR(64) NULL,
wg_peer_allowed_ips VARCHAR(255) NULL,
expires_at INT UNSIGNED NULL,
last_activity_at TIMESTAMP NULL,
close_reason VARCHAR(255) NULL,
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
closed_at TIMESTAMP NULL,
UNIQUE KEY uq_ra_session (session_id),
KEY idx_ra_sess_device (device_id, status),
KEY idx_ra_sess_status (status, last_activity_at)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
CREATE TABLE IF NOT EXISTS remote_access_events (
id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
session_id VARCHAR(36) NULL,
device_id VARCHAR(128) NOT NULL,
actor_user_id INT UNSIGNED NULL,
action VARCHAR(64) NOT NULL,
reason VARCHAR(255) NULL,
meta_json LONGTEXT NULL,
client_ip VARCHAR(64) NULL,
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
KEY idx_ra_evt_device (device_id, created_at),
KEY idx_ra_evt_session (session_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

View File

@@ -0,0 +1,50 @@
-- SQLite dev mirror for remote access tables.
CREATE TABLE IF NOT EXISTS remote_access_devices (
id INTEGER PRIMARY KEY AUTOINCREMENT,
device_id TEXT NOT NULL UNIQUE,
company_id INTEGER NOT NULL DEFAULT 1,
whitelisted INTEGER NOT NULL DEFAULT 0,
opt_in_mode TEXT NOT NULL DEFAULT 'none',
last_random TEXT NULL,
app_version TEXT NULL,
last_seen_at TEXT NULL,
notes TEXT NULL,
created_at TEXT NOT NULL DEFAULT (datetime('now')),
updated_at TEXT NOT NULL DEFAULT (datetime('now'))
);
CREATE TABLE IF NOT EXISTS remote_access_sessions (
id INTEGER PRIMARY KEY AUTOINCREMENT,
session_id TEXT NOT NULL UNIQUE,
device_id TEXT NOT NULL,
company_id INTEGER NOT NULL DEFAULT 1,
operator_user_id INTEGER NULL,
status TEXT NOT NULL DEFAULT 'pending',
tunnel TEXT NOT NULL DEFAULT 'wireguard',
endpoint TEXT NULL,
wg_client_private_key TEXT NULL,
wg_client_address TEXT NULL,
wg_server_public_key TEXT NULL,
wg_peer_allowed_ips TEXT NULL,
expires_at INTEGER NULL,
last_activity_at TEXT NULL,
close_reason TEXT NULL,
created_at TEXT NOT NULL DEFAULT (datetime('now')),
closed_at TEXT NULL
);
CREATE TABLE IF NOT EXISTS remote_access_events (
id INTEGER PRIMARY KEY AUTOINCREMENT,
session_id TEXT NULL,
device_id TEXT NOT NULL,
actor_user_id INTEGER NULL,
action TEXT NOT NULL,
reason TEXT NULL,
meta_json TEXT NULL,
client_ip TEXT NULL,
created_at TEXT NOT NULL DEFAULT (datetime('now'))
);
CREATE INDEX IF NOT EXISTS idx_ra_evt_device ON remote_access_events(device_id, created_at);
CREATE INDEX IF NOT EXISTS idx_ra_sess_device ON remote_access_sessions(device_id, status);

View File

@@ -0,0 +1,41 @@
-- Email registration + 2FA tables (MariaDB). Run as DB root once; SQLite dev auto-migrates via AuthEmailSchema.
ALTER TABLE users
ADD COLUMN IF NOT EXISTS email_normalized VARCHAR(254) NULL,
ADD COLUMN IF NOT EXISTS email_verified_at TIMESTAMP NULL,
ADD COLUMN IF NOT EXISTS status ENUM('pending','active','locked','disabled') NOT NULL DEFAULT 'active',
ADD COLUMN IF NOT EXISTS recovery_email_normalized VARCHAR(254) NULL;
CREATE UNIQUE INDEX IF NOT EXISTS uq_users_email_normalized ON users (email_normalized);
CREATE TABLE IF NOT EXISTS auth_tokens (
id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
user_id INT UNSIGNED NULL,
purpose ENUM('verify_email','reset_password','login_magic') NOT NULL,
token_hash CHAR(64) NOT NULL,
email_normalized VARCHAR(254) NULL,
expires_at TIMESTAMP NOT NULL,
used_at TIMESTAMP NULL,
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
KEY idx_auth_tokens_hash (token_hash),
KEY idx_auth_tokens_email (email_normalized)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
CREATE TABLE IF NOT EXISTS auth_factors (
id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
user_id INT UNSIGNED NOT NULL,
type ENUM('totp','webauthn','backup_code') NOT NULL,
secret_encrypted TEXT NULL,
label VARCHAR(64) NULL,
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
KEY idx_auth_factors_user (user_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
CREATE TABLE IF NOT EXISTS auth_attempts (
id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
ip_hash CHAR(64) NOT NULL,
username_hash CHAR(64) NULL,
outcome VARCHAR(32) NOT NULL,
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
KEY idx_auth_attempts_ip (ip_hash, created_at)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

View File

@@ -0,0 +1,5 @@
-- Extra graph_sessions indexes for graphs dashboard (user scope + platform-wide time range).
-- Idempotent on MariaDB 10.5+: duplicate index names are ignored manually when re-run.
CREATE INDEX IF NOT EXISTS idx_graph_user_time ON graph_sessions (user_id, started_at_ms);
CREATE INDEX IF NOT EXISTS idx_graph_started ON graph_sessions (started_at_ms);

View File

@@ -0,0 +1,8 @@
-- RSSH session credentials (MariaDB). Idempotent ALTERs.
ALTER TABLE remote_access_sessions
ADD COLUMN IF NOT EXISTS rssh_bastion_host VARCHAR(255) NULL,
ADD COLUMN IF NOT EXISTS rssh_bastion_port INT UNSIGNED NULL,
ADD COLUMN IF NOT EXISTS rssh_username VARCHAR(64) NULL,
ADD COLUMN IF NOT EXISTS rssh_secret VARCHAR(128) NULL,
ADD COLUMN IF NOT EXISTS rssh_remote_port INT UNSIGNED NULL,
ADD COLUMN IF NOT EXISTS rssh_local_port INT UNSIGNED NULL DEFAULT 8022;

View File

@@ -0,0 +1 @@
-- RSSH columns for SQLite (applied via Database::ensureSchema).

View File

@@ -0,0 +1,127 @@
-- Fresh MariaDB install for Android Cast crash reporter.
-- Run as root: mysql -u root -p < sql/schema.mariadb.sql
-- App user needs: SELECT, INSERT, UPDATE, DELETE on androidcast_crashes.*
CREATE DATABASE IF NOT EXISTS androidcast_crashes
CHARACTER SET utf8mb4
COLLATE utf8mb4_unicode_ci;
USE androidcast_crashes;
CREATE TABLE IF NOT EXISTS companies (
id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
slug VARCHAR(64) NOT NULL,
name VARCHAR(128) NOT NULL,
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
UNIQUE KEY uq_companies_slug (slug)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
CREATE TABLE IF NOT EXISTS users (
id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
username VARCHAR(64) NOT NULL,
password_hash VARCHAR(255) NOT NULL,
role ENUM('root', 'platform_admin', 'admin', 'viewer') NOT NULL DEFAULT 'viewer',
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
UNIQUE KEY uq_users_username (username)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
CREATE TABLE IF NOT EXISTS company_memberships (
id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
user_id INT UNSIGNED NOT NULL,
company_id INT UNSIGNED NOT NULL,
role ENUM('owner', 'admin', 'member') NOT NULL DEFAULT 'member',
permissions_json LONGTEXT NULL,
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
UNIQUE KEY uq_membership_user_company (user_id, company_id),
KEY idx_membership_company (company_id),
CONSTRAINT fk_membership_user FOREIGN KEY (user_id) REFERENCES users (id) ON DELETE CASCADE,
CONSTRAINT fk_membership_company FOREIGN KEY (company_id) REFERENCES companies (id) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
CREATE TABLE IF NOT EXISTS devices (
id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
company_id INT UNSIGNED NOT NULL,
external_id VARCHAR(128) NOT NULL,
display_name VARCHAR(256) NULL,
manufacturer VARCHAR(128) NULL,
model VARCHAR(128) NULL,
source ENUM('auto', 'manual', 'barcode') NOT NULL DEFAULT 'auto',
registered_at_ms BIGINT NOT NULL,
last_seen_at_ms BIGINT NOT NULL,
meta_json LONGTEXT NULL,
UNIQUE KEY uq_devices_company_external (company_id, external_id),
KEY idx_devices_company (company_id),
CONSTRAINT fk_devices_company FOREIGN KEY (company_id) REFERENCES companies (id) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
CREATE TABLE IF NOT EXISTS reports (
id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
company_id INT UNSIGNED NOT NULL DEFAULT 1,
device_id BIGINT UNSIGNED NULL,
report_id VARCHAR(128) NOT NULL,
fingerprint VARCHAR(64) NOT NULL,
crash_type VARCHAR(32) NOT NULL,
generated_at_ms BIGINT NOT NULL,
received_at_ms BIGINT NOT NULL,
device_model VARCHAR(128) NULL,
app_version VARCHAR(64) NULL,
payload_json LONGTEXT NOT NULL,
tags_json LONGTEXT NOT NULL,
UNIQUE KEY uq_reports_report_id (report_id),
KEY idx_reports_company (company_id),
KEY idx_reports_device (device_id),
KEY idx_reports_generated (generated_at_ms),
KEY idx_reports_received (received_at_ms),
KEY idx_reports_fingerprint (fingerprint),
KEY idx_reports_fp_received (fingerprint, received_at_ms),
CONSTRAINT fk_reports_company FOREIGN KEY (company_id) REFERENCES companies (id),
CONSTRAINT fk_reports_device FOREIGN KEY (device_id) REFERENCES devices (id) ON DELETE SET NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
CREATE TABLE IF NOT EXISTS report_views (
user_id INT UNSIGNED NOT NULL,
report_id BIGINT UNSIGNED NOT NULL,
viewed_at_ms BIGINT NOT NULL,
PRIMARY KEY (user_id, report_id),
CONSTRAINT fk_report_views_report
FOREIGN KEY (report_id) REFERENCES reports (id) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
CREATE TABLE IF NOT EXISTS tickets (
id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
company_id INT UNSIGNED NOT NULL DEFAULT 1,
device_id BIGINT UNSIGNED NULL,
ticket_id VARCHAR(128) NOT NULL,
title VARCHAR(256) NOT NULL,
brief TEXT NULL,
body LONGTEXT NOT NULL,
opened_at_ms BIGINT NOT NULL,
created_at_ms BIGINT NOT NULL,
updated_at_ms BIGINT NOT NULL,
owner_username VARCHAR(64) NOT NULL DEFAULT '',
verified_by_username VARCHAR(64) NULL,
device_model VARCHAR(128) NULL,
app_package VARCHAR(128) NULL,
app_version VARCHAR(64) NULL,
os_name VARCHAR(64) NULL,
os_version VARCHAR(128) NULL,
rating TINYINT UNSIGNED NOT NULL DEFAULT 0,
tags_json LONGTEXT NOT NULL,
payload_json LONGTEXT NOT NULL,
UNIQUE KEY uq_tickets_ticket_id (ticket_id),
KEY idx_tickets_company (company_id),
KEY idx_tickets_opened (opened_at_ms),
KEY idx_tickets_updated (updated_at_ms),
KEY idx_tickets_owner (owner_username),
CONSTRAINT fk_tickets_company FOREIGN KEY (company_id) REFERENCES companies (id),
CONSTRAINT fk_tickets_device FOREIGN KEY (device_id) REFERENCES devices (id) ON DELETE SET NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
INSERT IGNORE INTO companies (id, slug, name) VALUES (1, 'default', 'Default');
INSERT IGNORE INTO users (id, username, password_hash, role)
VALUES (1, 'admin', '$2y$10$t2JyYCyjBwRxAHfmLwsnG.APoj/in0i6nxpKcQggdl.AkhFJR13o.', 'root');
-- default password: admin — change in production
INSERT IGNORE INTO company_memberships (user_id, company_id, role)
VALUES (1, 1, 'owner');

View File

@@ -0,0 +1,113 @@
PRAGMA foreign_keys = ON;
CREATE TABLE IF NOT EXISTS companies (
id INTEGER PRIMARY KEY AUTOINCREMENT,
slug TEXT NOT NULL UNIQUE,
name TEXT NOT NULL,
created_at TEXT NOT NULL DEFAULT (datetime('now'))
);
CREATE TABLE IF NOT EXISTS users (
id INTEGER PRIMARY KEY AUTOINCREMENT,
username TEXT NOT NULL UNIQUE,
password_hash TEXT NOT NULL,
role TEXT NOT NULL DEFAULT 'viewer',
created_at TEXT NOT NULL DEFAULT (datetime('now'))
);
CREATE TABLE IF NOT EXISTS company_memberships (
id INTEGER PRIMARY KEY AUTOINCREMENT,
user_id INTEGER NOT NULL,
company_id INTEGER NOT NULL,
role TEXT NOT NULL DEFAULT 'member',
permissions_json TEXT,
created_at TEXT NOT NULL DEFAULT (datetime('now')),
UNIQUE (user_id, company_id),
FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE,
FOREIGN KEY (company_id) REFERENCES companies(id) ON DELETE CASCADE
);
CREATE TABLE IF NOT EXISTS devices (
id INTEGER PRIMARY KEY AUTOINCREMENT,
company_id INTEGER NOT NULL,
external_id TEXT NOT NULL,
display_name TEXT,
manufacturer TEXT,
model TEXT,
source TEXT NOT NULL DEFAULT 'auto',
registered_at_ms INTEGER NOT NULL,
last_seen_at_ms INTEGER NOT NULL,
meta_json TEXT,
UNIQUE (company_id, external_id),
FOREIGN KEY (company_id) REFERENCES companies(id) ON DELETE CASCADE
);
CREATE TABLE IF NOT EXISTS reports (
id INTEGER PRIMARY KEY AUTOINCREMENT,
company_id INTEGER NOT NULL DEFAULT 1,
device_id INTEGER,
report_id TEXT NOT NULL UNIQUE,
fingerprint TEXT NOT NULL,
crash_type TEXT NOT NULL,
generated_at_ms INTEGER NOT NULL,
received_at_ms INTEGER NOT NULL,
device_model TEXT,
app_version TEXT,
payload_json TEXT NOT NULL,
tags_json TEXT NOT NULL DEFAULT '[]',
FOREIGN KEY (company_id) REFERENCES companies(id),
FOREIGN KEY (device_id) REFERENCES devices(id) ON DELETE SET NULL
);
CREATE TABLE IF NOT EXISTS report_views (
user_id INTEGER NOT NULL,
report_id INTEGER NOT NULL,
viewed_at_ms INTEGER NOT NULL,
PRIMARY KEY (user_id, report_id),
FOREIGN KEY (report_id) REFERENCES reports(id) ON DELETE CASCADE
);
CREATE TABLE IF NOT EXISTS tickets (
id INTEGER PRIMARY KEY AUTOINCREMENT,
company_id INTEGER NOT NULL DEFAULT 1,
device_id INTEGER NULL,
ticket_id TEXT NOT NULL UNIQUE,
title TEXT NOT NULL,
brief TEXT,
body TEXT NOT NULL,
opened_at_ms INTEGER NOT NULL,
created_at_ms INTEGER NOT NULL,
updated_at_ms INTEGER NOT NULL,
owner_username TEXT NOT NULL DEFAULT '',
verified_by_username TEXT,
device_model TEXT,
app_package TEXT,
app_version TEXT,
os_name TEXT,
os_version TEXT,
rating INTEGER NOT NULL DEFAULT 0,
tags_json TEXT NOT NULL DEFAULT '[]',
payload_json TEXT NOT NULL,
FOREIGN KEY (company_id) REFERENCES companies(id),
FOREIGN KEY (device_id) REFERENCES devices(id) ON DELETE SET NULL
);
CREATE INDEX IF NOT EXISTS idx_tickets_company ON tickets(company_id);
CREATE INDEX IF NOT EXISTS idx_tickets_opened ON tickets(opened_at_ms DESC);
CREATE INDEX IF NOT EXISTS idx_reports_company ON reports(company_id);
CREATE INDEX IF NOT EXISTS idx_reports_generated ON reports(generated_at_ms DESC);
CREATE INDEX IF NOT EXISTS idx_reports_received ON reports(received_at_ms DESC);
CREATE INDEX IF NOT EXISTS idx_reports_fingerprint ON reports(fingerprint);
CREATE INDEX IF NOT EXISTS idx_reports_company_fingerprint ON reports(company_id, fingerprint);
CREATE INDEX IF NOT EXISTS idx_reports_company_received ON reports(company_id, received_at_ms);
CREATE INDEX IF NOT EXISTS idx_reports_fp_received ON reports(fingerprint, received_at_ms);
INSERT OR IGNORE INTO companies (id, slug, name) VALUES (1, 'default', 'Default');
INSERT OR IGNORE INTO users (id, username, password_hash, role)
VALUES (1, 'admin', '$2y$10$t2JyYCyjBwRxAHfmLwsnG.APoj/in0i6nxpKcQggdl.AkhFJR13o.', 'root');
-- password: admin
INSERT OR IGNORE INTO company_memberships (user_id, company_id, role)
VALUES (1, 1, 'owner');