CREATE TABLE IF NOT EXISTS miningcheck_projects ( project_key VARCHAR(64) PRIMARY KEY, project_name VARCHAR(160) NOT NULL, created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ); CREATE TABLE IF NOT EXISTS miningcheck_currencies ( code VARCHAR(10) PRIMARY KEY, name VARCHAR(64) NOT NULL, symbol VARCHAR(8), is_active BOOLEAN NOT NULL DEFAULT TRUE, is_crypto BOOLEAN NOT NULL DEFAULT FALSE, sort_order INTEGER NOT NULL DEFAULT 0 ); CREATE TABLE IF NOT EXISTS miningcheck_currency_aliases ( alias_code VARCHAR(10) PRIMARY KEY, currency_code VARCHAR(10) NOT NULL, created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, CONSTRAINT fk_mining_currency_aliases_currency FOREIGN KEY (currency_code) REFERENCES miningcheck_currencies(code) ON DELETE CASCADE ); CREATE TABLE IF NOT EXISTS miningcheck_settings ( id BIGSERIAL PRIMARY KEY, project_key VARCHAR(64) NOT NULL, owner_sub VARCHAR(128) NOT NULL, baseline_measured_at TIMESTAMP NOT NULL, baseline_coins_total NUMERIC(20,6) NOT NULL, daily_cost_amount NUMERIC(20,10) NOT NULL, daily_cost_currency VARCHAR(10) NOT NULL, report_currency VARCHAR(10), crypto_currency VARCHAR(10), display_timezone VARCHAR(64), fx_max_age_hours NUMERIC(10,2), module_theme_mode VARCHAR(16), module_theme_accent VARCHAR(16), preferred_currencies JSONB, created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, CONSTRAINT fk_mining_settings_project FOREIGN KEY (project_key) REFERENCES miningcheck_projects(project_key) ON DELETE CASCADE, CONSTRAINT fk_mining_settings_daily_cost_currency_currency FOREIGN KEY (daily_cost_currency) REFERENCES miningcheck_currencies(code), CONSTRAINT fk_mining_settings_report_currency_currency FOREIGN KEY (report_currency) REFERENCES miningcheck_currencies(code), CONSTRAINT fk_mining_settings_crypto_currency_currency FOREIGN KEY (crypto_currency) REFERENCES miningcheck_currencies(code), CONSTRAINT uq_mining_settings_project_owner UNIQUE (project_key, owner_sub) ); CREATE TABLE IF NOT EXISTS miningcheck_cost_plans ( id BIGSERIAL PRIMARY KEY, project_key VARCHAR(64) NOT NULL, owner_sub VARCHAR(128) NOT NULL, label VARCHAR(120) NOT NULL, starts_at TIMESTAMP NOT NULL, runtime_months INTEGER NOT NULL, mining_speed_value NUMERIC(20,4), mining_speed_unit VARCHAR(8), bonus_speed_value NUMERIC(20,4), bonus_speed_unit VARCHAR(8), auto_renew BOOLEAN NOT NULL DEFAULT FALSE, base_price_amount NUMERIC(20,8), payment_type VARCHAR(10) NOT NULL DEFAULT 'fiat', total_cost_amount NUMERIC(20,8) NOT NULL, currency VARCHAR(10) NOT NULL, note TEXT, is_active BOOLEAN NOT NULL DEFAULT TRUE, created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, CONSTRAINT fk_mining_cost_plans_project FOREIGN KEY (project_key) REFERENCES miningcheck_projects(project_key) ON DELETE CASCADE, CONSTRAINT fk_mining_cost_plans_currency_currency FOREIGN KEY (currency) REFERENCES miningcheck_currencies(code) ); CREATE INDEX IF NOT EXISTS idx_miningcheck_cost_plans_project_start ON miningcheck_cost_plans(project_key, owner_sub, starts_at); CREATE TABLE IF NOT EXISTS miningcheck_measurements ( id BIGSERIAL PRIMARY KEY, project_key VARCHAR(64) NOT NULL, owner_sub VARCHAR(128) NOT NULL, measured_at TIMESTAMP NOT NULL, coins_total NUMERIC(20,6) NOT NULL, price_per_coin NUMERIC(20,8), price_currency VARCHAR(10), note TEXT, source VARCHAR(16) NOT NULL CHECK (source IN ('manual', 'image_ocr', 'seed_import')), image_path VARCHAR(255), ocr_raw_text TEXT, ocr_confidence NUMERIC(6,4), ocr_flags JSONB, created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, CONSTRAINT fk_mining_measurements_project FOREIGN KEY (project_key) REFERENCES miningcheck_projects(project_key) ON DELETE CASCADE, CONSTRAINT fk_mining_measurements_price_currency_currency FOREIGN KEY (price_currency) REFERENCES miningcheck_currencies(code), CONSTRAINT uq_mining_measurements_unique UNIQUE (project_key, owner_sub, measured_at, coins_total) ); CREATE INDEX IF NOT EXISTS idx_miningcheck_measurements_project_measured_at ON miningcheck_measurements(project_key, owner_sub, measured_at); CREATE TABLE IF NOT EXISTS miningcheck_measurement_rates ( id BIGSERIAL PRIMARY KEY, measurement_id BIGINT NOT NULL, project_key VARCHAR(64) NOT NULL, owner_sub VARCHAR(128) NOT NULL, base_currency VARCHAR(10) NOT NULL, quote_currency VARCHAR(10) NOT NULL, rate NUMERIC(20,10) NOT NULL, provider VARCHAR(32) NOT NULL DEFAULT 'derived', created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, CONSTRAINT fk_mining_measurement_rates_measurement FOREIGN KEY (measurement_id) REFERENCES miningcheck_measurements(id) ON DELETE CASCADE, CONSTRAINT fk_mining_measurement_rates_base_currency_currency FOREIGN KEY (base_currency) REFERENCES miningcheck_currencies(code), CONSTRAINT fk_mining_measurement_rates_quote_currency_currency FOREIGN KEY (quote_currency) REFERENCES miningcheck_currencies(code), CONSTRAINT uq_mining_measurement_rate_pair UNIQUE (measurement_id, base_currency, quote_currency) ); CREATE INDEX IF NOT EXISTS idx_miningcheck_measurement_rates_project_measurement ON miningcheck_measurement_rates(project_key, owner_sub, measurement_id); CREATE TABLE IF NOT EXISTS miningcheck_payouts ( id BIGSERIAL PRIMARY KEY, project_key VARCHAR(64) NOT NULL, owner_sub VARCHAR(128) NOT NULL, payout_at TIMESTAMP NOT NULL, coins_amount NUMERIC(20,6) NOT NULL, payout_currency VARCHAR(10) NOT NULL DEFAULT 'DOGE', note TEXT, created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, CONSTRAINT fk_mining_payouts_project FOREIGN KEY (project_key) REFERENCES miningcheck_projects(project_key) ON DELETE CASCADE, CONSTRAINT fk_mining_payouts_payout_currency_currency FOREIGN KEY (payout_currency) REFERENCES miningcheck_currencies(code) ); CREATE INDEX IF NOT EXISTS idx_miningcheck_payouts_project_payout_at ON miningcheck_payouts(project_key, owner_sub, payout_at); CREATE TABLE IF NOT EXISTS miningcheck_miner_offers ( id BIGSERIAL PRIMARY KEY, project_key VARCHAR(64) NOT NULL, label VARCHAR(120) NOT NULL, runtime_months INTEGER, mining_speed_value NUMERIC(20,4), mining_speed_unit VARCHAR(8), bonus_speed_value NUMERIC(20,4), bonus_speed_unit VARCHAR(8), base_price_amount NUMERIC(20,8) NOT NULL, base_price_currency VARCHAR(10) NOT NULL, payment_type VARCHAR(10) NOT NULL DEFAULT 'fiat', auto_renew BOOLEAN NOT NULL DEFAULT FALSE, note TEXT, is_active BOOLEAN NOT NULL DEFAULT TRUE, created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, CONSTRAINT fk_mining_miner_offers_project FOREIGN KEY (project_key) REFERENCES miningcheck_projects(project_key) ON DELETE CASCADE, CONSTRAINT fk_mining_miner_offers_base_price_currency_currency FOREIGN KEY (base_price_currency) REFERENCES miningcheck_currencies(code) ); CREATE TABLE IF NOT EXISTS miningcheck_targets ( id BIGSERIAL PRIMARY KEY, project_key VARCHAR(64) NOT NULL, owner_sub VARCHAR(128) NOT NULL, label VARCHAR(120) NOT NULL, target_amount_fiat NUMERIC(20,2) NOT NULL, currency VARCHAR(10) NOT NULL, miner_offer_id BIGINT, is_active BOOLEAN NOT NULL DEFAULT TRUE, sort_order INTEGER NOT NULL DEFAULT 0, created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, CONSTRAINT fk_mining_targets_project FOREIGN KEY (project_key) REFERENCES miningcheck_projects(project_key) ON DELETE CASCADE, CONSTRAINT fk_mining_targets_currency_currency FOREIGN KEY (currency) REFERENCES miningcheck_currencies(code), CONSTRAINT fk_mining_targets_offer FOREIGN KEY (miner_offer_id) REFERENCES miningcheck_miner_offers(id) ON DELETE SET NULL, CONSTRAINT uq_mining_targets_project_label UNIQUE (project_key, owner_sub, label) ); CREATE TABLE IF NOT EXISTS miningcheck_dashboard_definitions ( id BIGSERIAL PRIMARY KEY, project_key VARCHAR(64) NOT NULL, owner_sub VARCHAR(128) NOT NULL, name VARCHAR(160) NOT NULL, chart_type VARCHAR(16) NOT NULL CHECK (chart_type IN ('line', 'bar', 'area', 'table')), x_field VARCHAR(64) NOT NULL, y_field VARCHAR(64) NOT NULL, aggregation VARCHAR(32) NOT NULL DEFAULT 'none', filters_json JSONB, is_active BOOLEAN NOT NULL DEFAULT TRUE, created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, CONSTRAINT fk_mining_dashboards_project FOREIGN KEY (project_key) REFERENCES miningcheck_projects(project_key) ON DELETE CASCADE, CONSTRAINT uq_mining_dashboards_project_name UNIQUE (project_key, owner_sub, name) ); CREATE TABLE IF NOT EXISTS miningcheck_purchased_miners ( id BIGSERIAL PRIMARY KEY, project_key VARCHAR(64) NOT NULL, owner_sub VARCHAR(128) NOT NULL, miner_offer_id BIGINT, purchased_at TIMESTAMP NOT NULL, label VARCHAR(120) NOT NULL, runtime_months INTEGER, mining_speed_value NUMERIC(20,4), mining_speed_unit VARCHAR(8), bonus_speed_value NUMERIC(20,4), bonus_speed_unit VARCHAR(8), total_cost_amount NUMERIC(20,8) NOT NULL, currency VARCHAR(10) NOT NULL, usd_reference_amount NUMERIC(20,8), reference_price_amount NUMERIC(20,8), reference_price_currency VARCHAR(10), auto_renew BOOLEAN NOT NULL DEFAULT FALSE, note TEXT, is_active BOOLEAN NOT NULL DEFAULT TRUE, created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, CONSTRAINT fk_mining_purchased_miners_project FOREIGN KEY (project_key) REFERENCES miningcheck_projects(project_key) ON DELETE CASCADE, CONSTRAINT fk_mining_purchased_miners_offer FOREIGN KEY (miner_offer_id) REFERENCES miningcheck_miner_offers(id) ON DELETE SET NULL, CONSTRAINT fk_mining_purchased_miners_currency_currency FOREIGN KEY (currency) REFERENCES miningcheck_currencies(code), CONSTRAINT fk_mining_purchased_miners_reference_price_currency_currency FOREIGN KEY (reference_price_currency) REFERENCES miningcheck_currencies(code) ); CREATE TABLE IF NOT EXISTS miningcheck_fx_fetches ( id BIGSERIAL PRIMARY KEY, provider VARCHAR(32) NOT NULL DEFAULT 'currencyapi', base_currency VARCHAR(10) NOT NULL, rate_date DATE NOT NULL, fetched_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, CONSTRAINT fk_mining_fx_fetches_base_currency_currency FOREIGN KEY (base_currency) REFERENCES miningcheck_currencies(code) ); CREATE INDEX IF NOT EXISTS idx_miningcheck_fx_fetches_base_fetched ON miningcheck_fx_fetches(base_currency, fetched_at); CREATE TABLE IF NOT EXISTS miningcheck_fx_rates ( id BIGSERIAL PRIMARY KEY, fetch_id BIGINT NOT NULL, currency_code VARCHAR(10) NOT NULL, current_value NUMERIC(20,10) NOT NULL, CONSTRAINT fk_mining_fx_rates_fetch FOREIGN KEY (fetch_id) REFERENCES miningcheck_fx_fetches(id) ON DELETE CASCADE, CONSTRAINT fk_mining_fx_rates_currency_code_currency FOREIGN KEY (currency_code) REFERENCES miningcheck_currencies(code) ); CREATE INDEX IF NOT EXISTS idx_miningcheck_fx_rates_fetch ON miningcheck_fx_rates(fetch_id); CREATE INDEX IF NOT EXISTS idx_miningcheck_fx_rates_currency ON miningcheck_fx_rates(currency_code);