Miner-Upgrade
This commit is contained in:
@@ -229,6 +229,7 @@ final class MiningRepository
|
||||
'owner_sub' => $this->ownerSub,
|
||||
'measured_at' => $payload['measured_at'],
|
||||
'coins_total' => $payload['coins_total'],
|
||||
'coin_currency' => $payload['coin_currency'] ?? 'DOGE',
|
||||
'price_per_coin' => $payload['price_per_coin'],
|
||||
'price_currency' => $payload['price_currency'],
|
||||
'fx_fetch_id' => $payload['fx_fetch_id'] ?? null,
|
||||
@@ -243,10 +244,10 @@ final class MiningRepository
|
||||
if ($this->driver === 'pgsql') {
|
||||
$stmt = $this->pdo->prepare(
|
||||
'INSERT INTO ' . $this->table('measurements') . ' (
|
||||
project_key, owner_sub, measured_at, coins_total, price_per_coin, price_currency, fx_fetch_id, note,
|
||||
project_key, owner_sub, measured_at, coins_total, coin_currency, price_per_coin, price_currency, fx_fetch_id, note,
|
||||
source, image_path, ocr_raw_text, ocr_confidence, ocr_flags
|
||||
) VALUES (
|
||||
:project_key, :owner_sub, :measured_at, :coins_total, :price_per_coin, :price_currency, :fx_fetch_id, :note,
|
||||
:project_key, :owner_sub, :measured_at, :coins_total, :coin_currency, :price_per_coin, :price_currency, :fx_fetch_id, :note,
|
||||
:source, :image_path, :ocr_raw_text, :ocr_confidence, CAST(:ocr_flags AS jsonb)
|
||||
)
|
||||
RETURNING *'
|
||||
@@ -259,10 +260,10 @@ final class MiningRepository
|
||||
|
||||
$stmt = $this->pdo->prepare(
|
||||
'INSERT INTO ' . $this->table('measurements') . ' (
|
||||
project_key, owner_sub, measured_at, coins_total, price_per_coin, price_currency, fx_fetch_id, note,
|
||||
project_key, owner_sub, measured_at, coins_total, coin_currency, price_per_coin, price_currency, fx_fetch_id, note,
|
||||
source, image_path, ocr_raw_text, ocr_confidence, ocr_flags
|
||||
) VALUES (
|
||||
:project_key, :owner_sub, :measured_at, :coins_total, :price_per_coin, :price_currency, :fx_fetch_id, :note,
|
||||
:project_key, :owner_sub, :measured_at, :coins_total, :coin_currency, :price_per_coin, :price_currency, :fx_fetch_id, :note,
|
||||
:source, :image_path, :ocr_raw_text, :ocr_confidence, :ocr_flags
|
||||
)'
|
||||
);
|
||||
@@ -441,6 +442,71 @@ final class MiningRepository
|
||||
return $this->normalizeRow($fetch->fetch() ?: []);
|
||||
}
|
||||
|
||||
public function listWalletSnapshots(string $projectKey, int $limit = 100): array
|
||||
{
|
||||
$stmt = $this->pdo->prepare(
|
||||
'SELECT * FROM ' . $this->table('wallet_snapshots') . '
|
||||
WHERE project_key = :project_key AND owner_sub = :owner_sub
|
||||
ORDER BY measured_at DESC, id DESC
|
||||
LIMIT :limit'
|
||||
);
|
||||
$stmt->bindValue(':project_key', $projectKey, PDO::PARAM_STR);
|
||||
$stmt->bindValue(':owner_sub', $this->ownerSub, PDO::PARAM_STR);
|
||||
$stmt->bindValue(':limit', $limit, PDO::PARAM_INT);
|
||||
$stmt->execute();
|
||||
return $this->normalizeRows($stmt->fetchAll() ?: []);
|
||||
}
|
||||
|
||||
public function saveWalletSnapshot(string $projectKey, array $payload): array
|
||||
{
|
||||
$params = [
|
||||
'project_key' => $projectKey,
|
||||
'owner_sub' => $this->ownerSub,
|
||||
'measured_at' => $payload['measured_at'],
|
||||
'total_value_amount' => $payload['total_value_amount'] ?? null,
|
||||
'total_value_currency' => $payload['total_value_currency'] ?? null,
|
||||
'wallet_balance' => $payload['wallet_balance'] ?? null,
|
||||
'wallet_currency' => $payload['wallet_currency'],
|
||||
'balances_json' => json_encode($payload['balances_json'] ?? [], JSON_UNESCAPED_UNICODE),
|
||||
'note' => $payload['note'] ?? null,
|
||||
'source' => $payload['source'] ?? 'manual',
|
||||
'image_path' => $payload['image_path'] ?? null,
|
||||
'ocr_raw_text' => $payload['ocr_raw_text'] ?? null,
|
||||
'ocr_confidence' => $payload['ocr_confidence'] ?? null,
|
||||
'ocr_flags' => json_encode($payload['ocr_flags'] ?? [], JSON_UNESCAPED_UNICODE),
|
||||
];
|
||||
|
||||
if ($this->driver === 'pgsql') {
|
||||
$stmt = $this->pdo->prepare(
|
||||
'INSERT INTO ' . $this->table('wallet_snapshots') . ' (
|
||||
project_key, owner_sub, measured_at, total_value_amount, total_value_currency, wallet_balance,
|
||||
wallet_currency, balances_json, note, source, image_path, ocr_raw_text, ocr_confidence, ocr_flags
|
||||
) VALUES (
|
||||
:project_key, :owner_sub, :measured_at, :total_value_amount, :total_value_currency, :wallet_balance,
|
||||
:wallet_currency, CAST(:balances_json AS jsonb), :note, :source, :image_path, :ocr_raw_text, :ocr_confidence, CAST(:ocr_flags AS jsonb)
|
||||
)
|
||||
RETURNING *'
|
||||
);
|
||||
$stmt->execute($params);
|
||||
return $this->normalizeRow($stmt->fetch() ?: []);
|
||||
}
|
||||
|
||||
$stmt = $this->pdo->prepare(
|
||||
'INSERT INTO ' . $this->table('wallet_snapshots') . ' (
|
||||
project_key, owner_sub, measured_at, total_value_amount, total_value_currency, wallet_balance,
|
||||
wallet_currency, balances_json, note, source, image_path, ocr_raw_text, ocr_confidence, ocr_flags
|
||||
) VALUES (
|
||||
:project_key, :owner_sub, :measured_at, :total_value_amount, :total_value_currency, :wallet_balance,
|
||||
:wallet_currency, :balances_json, :note, :source, :image_path, :ocr_raw_text, :ocr_confidence, :ocr_flags
|
||||
)'
|
||||
);
|
||||
$stmt->execute($params);
|
||||
$id = (int) $this->pdo->lastInsertId();
|
||||
$fetch = $this->pdo->prepare('SELECT * FROM ' . $this->table('wallet_snapshots') . ' WHERE id = :id LIMIT 1');
|
||||
$fetch->execute(['id' => $id]);
|
||||
return $this->normalizeRow($fetch->fetch() ?: []);
|
||||
}
|
||||
|
||||
public function listTargets(string $projectKey): array
|
||||
{
|
||||
$stmt = $this->pdo->prepare(
|
||||
@@ -1195,6 +1261,7 @@ final class MiningRepository
|
||||
'fx_rates' => $this->prefix . 'fx_rates',
|
||||
'measurement_rates' => $this->prefix . 'measurement_rates',
|
||||
'payouts' => $this->prefix . 'payouts',
|
||||
'wallet_snapshots' => $this->prefix . 'wallet_snapshots',
|
||||
'miner_offers' => $this->prefix . 'miner_offers',
|
||||
'purchased_miners' => $this->prefix . 'purchased_miners',
|
||||
default => throw new \RuntimeException('Unknown mining table: ' . $logicalName),
|
||||
@@ -1273,7 +1340,7 @@ final class MiningRepository
|
||||
|
||||
private function normalizeRow(array $row): array
|
||||
{
|
||||
foreach (['ocr_flags', 'filters_json', 'preferred_currencies'] as $jsonField) {
|
||||
foreach (['ocr_flags', 'filters_json', 'preferred_currencies', 'balances_json'] as $jsonField) {
|
||||
if (array_key_exists($jsonField, $row) && is_string($row[$jsonField]) && trim($row[$jsonField]) !== '') {
|
||||
$decoded = json_decode($row[$jsonField], true);
|
||||
if (json_last_error() === JSON_ERROR_NONE) {
|
||||
|
||||
@@ -188,6 +188,10 @@ final class SchemaManager
|
||||
$this->ensureMeasurementFxReferenceColumn();
|
||||
$applied[] = 'measurement_fx_reference';
|
||||
}
|
||||
if ($this->tableExists($this->prefix . 'measurements') && !$this->columnExists($this->prefix . 'measurements', 'coin_currency')) {
|
||||
$this->ensureMeasurementCoinCurrencyColumn();
|
||||
$applied[] = 'measurement_coin_currency';
|
||||
}
|
||||
|
||||
if (!$this->tableExists($this->prefix . 'measurement_rates')) {
|
||||
$this->ensureMeasurementRatesTable();
|
||||
@@ -197,6 +201,10 @@ final class SchemaManager
|
||||
$this->ensurePayoutsTable();
|
||||
$applied[] = 'payouts_table';
|
||||
}
|
||||
if (!$this->tableExists($this->prefix . 'wallet_snapshots')) {
|
||||
$this->ensureWalletSnapshotsTable();
|
||||
$applied[] = 'wallet_snapshots_table';
|
||||
}
|
||||
if (!$this->tableExists($this->prefix . 'fx_fetches') || !$this->tableExists($this->prefix . 'fx_rates')) {
|
||||
$this->ensureFxRatesTable();
|
||||
$applied[] = 'fx_rates_table';
|
||||
@@ -293,6 +301,10 @@ final class SchemaManager
|
||||
$this->ensureMeasurementFxReferenceColumn();
|
||||
$applied[] = 'measurement_fx_reference';
|
||||
}
|
||||
if ($this->tableExists($this->prefix . 'measurements') && !$this->columnExists($this->prefix . 'measurements', 'coin_currency')) {
|
||||
$this->ensureMeasurementCoinCurrencyColumn();
|
||||
$applied[] = 'measurement_coin_currency';
|
||||
}
|
||||
|
||||
if (!$this->tableExists($this->prefix . 'fx_fetches') || !$this->tableExists($this->prefix . 'fx_rates')) {
|
||||
$this->ensureFxRatesTable();
|
||||
@@ -308,6 +320,10 @@ final class SchemaManager
|
||||
$this->ensurePayoutsTable();
|
||||
$applied[] = 'payouts_table';
|
||||
}
|
||||
if (!$this->tableExists($this->prefix . 'wallet_snapshots')) {
|
||||
$this->ensureWalletSnapshotsTable();
|
||||
$applied[] = 'wallet_snapshots_table';
|
||||
}
|
||||
|
||||
if (!$this->tableExists($this->prefix . 'miner_offers')) {
|
||||
$this->upgradeMinerOffersTable();
|
||||
@@ -421,6 +437,54 @@ final class SchemaManager
|
||||
}
|
||||
}
|
||||
|
||||
private function ensureMeasurementCoinCurrencyColumn(): void
|
||||
{
|
||||
$table = $this->prefix . 'measurements';
|
||||
$settingsTable = $this->prefix . 'settings';
|
||||
if (!$this->tableExists($table)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$statements = $this->driver === 'pgsql'
|
||||
? [
|
||||
'ALTER TABLE ' . $table . ' ADD COLUMN IF NOT EXISTS coin_currency VARCHAR(10)',
|
||||
'UPDATE ' . $table . ' AS m
|
||||
SET coin_currency = COALESCE(NULLIF(BTRIM(s.crypto_currency), \'\'), \'DOGE\')
|
||||
FROM ' . $settingsTable . ' AS s
|
||||
WHERE s.project_key = m.project_key
|
||||
AND (m.coin_currency IS NULL OR BTRIM(m.coin_currency) = \'\')',
|
||||
'UPDATE ' . $table . ' SET coin_currency = \'DOGE\' WHERE coin_currency IS NULL OR BTRIM(coin_currency) = \'\'',
|
||||
'ALTER TABLE ' . $table . ' ALTER COLUMN coin_currency SET DEFAULT \'DOGE\'',
|
||||
'ALTER TABLE ' . $table . ' ALTER COLUMN coin_currency SET NOT NULL',
|
||||
]
|
||||
: [
|
||||
'ALTER TABLE `' . $table . '` ADD COLUMN coin_currency VARCHAR(10) NOT NULL DEFAULT \'DOGE\' AFTER coins_total',
|
||||
'UPDATE `' . $table . '` m
|
||||
LEFT JOIN `' . $settingsTable . '` s ON s.project_key = m.project_key
|
||||
SET m.coin_currency = COALESCE(NULLIF(TRIM(s.crypto_currency), \'\'), \'DOGE\')
|
||||
WHERE m.coin_currency IS NULL OR TRIM(m.coin_currency) = \'\'',
|
||||
];
|
||||
|
||||
foreach ($statements as $index => $statement) {
|
||||
try {
|
||||
if ($this->driver === 'mysql' && $index === 0 && $this->columnExists($table, 'coin_currency')) {
|
||||
continue;
|
||||
}
|
||||
$this->executeUpgradeStatements([$statement], 'Messpunkt-Coin-Waehrung konnte nicht angelegt werden.');
|
||||
} catch (\Throwable $exception) {
|
||||
$message = strtolower($exception->getMessage());
|
||||
if (
|
||||
($this->driver === 'mysql' && $index === 0 && str_contains($message, 'duplicate column')) ||
|
||||
($this->driver === 'pgsql' && str_contains($message, 'already exists'))
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
|
||||
throw $exception;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private function ensureLegacyMinerOfferImportColumns(): void
|
||||
{
|
||||
$table = $this->prefix . 'miner_offers';
|
||||
@@ -599,6 +663,9 @@ final class SchemaManager
|
||||
if ($this->tableExists($this->prefix . 'measurements') && !$this->columnExists($this->prefix . 'measurements', 'fx_fetch_id')) {
|
||||
$upgrades[] = 'measurement_fx_reference';
|
||||
}
|
||||
if ($this->tableExists($this->prefix . 'measurements') && !$this->columnExists($this->prefix . 'measurements', 'coin_currency')) {
|
||||
$upgrades[] = 'measurement_coin_currency';
|
||||
}
|
||||
|
||||
if (!$this->tableExists($this->prefix . 'fx_fetches') || !$this->tableExists($this->prefix . 'fx_rates')) {
|
||||
$upgrades[] = 'fx_rates_table';
|
||||
@@ -609,6 +676,9 @@ final class SchemaManager
|
||||
if (!$this->tableExists($this->prefix . 'payouts')) {
|
||||
$upgrades[] = 'payouts_table';
|
||||
}
|
||||
if (!$this->tableExists($this->prefix . 'wallet_snapshots')) {
|
||||
$upgrades[] = 'wallet_snapshots_table';
|
||||
}
|
||||
if (!$this->tableExists($this->prefix . 'miner_offers')) {
|
||||
$upgrades[] = 'miner_offers_table';
|
||||
}
|
||||
@@ -849,6 +919,65 @@ final class SchemaManager
|
||||
$this->executeUpgradeStatements($statements, 'Schema-Upgrade fuer Auszahlungen fehlgeschlagen.');
|
||||
}
|
||||
|
||||
public function ensureWalletSnapshotsTable(): void
|
||||
{
|
||||
if ($this->tableExists($this->prefix . 'wallet_snapshots')) {
|
||||
return;
|
||||
}
|
||||
|
||||
$table = $this->prefix . 'wallet_snapshots';
|
||||
$projectTable = $this->prefix . 'projects';
|
||||
$statements = $this->driver === 'pgsql'
|
||||
? [
|
||||
'CREATE TABLE IF NOT EXISTS ' . $table . ' (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
project_key VARCHAR(64) NOT NULL,
|
||||
owner_sub VARCHAR(128) NOT NULL,
|
||||
measured_at TIMESTAMP NOT NULL,
|
||||
total_value_amount NUMERIC(20,8),
|
||||
total_value_currency VARCHAR(10),
|
||||
wallet_balance NUMERIC(28,10),
|
||||
wallet_currency VARCHAR(10) NOT NULL,
|
||||
balances_json JSONB,
|
||||
note TEXT,
|
||||
source VARCHAR(16) NOT NULL,
|
||||
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_wallet_snapshots_project FOREIGN KEY (project_key) REFERENCES ' . $projectTable . '(project_key) ON DELETE CASCADE
|
||||
)',
|
||||
'CREATE INDEX IF NOT EXISTS idx_miningcheck_wallet_snapshots_project_measured_at ON ' . $table . ' (project_key, owner_sub, measured_at)',
|
||||
]
|
||||
: [
|
||||
'CREATE TABLE IF NOT EXISTS `' . $table . '` (
|
||||
id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
|
||||
project_key VARCHAR(64) NOT NULL,
|
||||
owner_sub VARCHAR(128) NOT NULL,
|
||||
measured_at TIMESTAMP NOT NULL,
|
||||
total_value_amount DECIMAL(20,8) NULL,
|
||||
total_value_currency VARCHAR(10) NULL,
|
||||
wallet_balance DECIMAL(28,10) NULL,
|
||||
wallet_currency VARCHAR(10) NOT NULL,
|
||||
balances_json JSON NULL,
|
||||
note TEXT NULL,
|
||||
source ENUM(\'manual\', \'image_ocr\', \'seed_import\') NOT NULL,
|
||||
image_path VARCHAR(255) NULL,
|
||||
ocr_raw_text MEDIUMTEXT NULL,
|
||||
ocr_confidence DECIMAL(6,4) NULL,
|
||||
ocr_flags JSON NULL,
|
||||
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||
CONSTRAINT fk_mining_wallet_snapshots_project FOREIGN KEY (project_key) REFERENCES `' . $projectTable . '`(project_key) ON DELETE CASCADE,
|
||||
KEY idx_miningcheck_wallet_snapshots_project_measured_at (project_key, owner_sub, measured_at)
|
||||
)',
|
||||
];
|
||||
|
||||
$this->executeUpgradeStatements($statements, 'Schema-Upgrade fuer Wallet-Snapshots fehlgeschlagen.');
|
||||
}
|
||||
|
||||
public function ensureMinerTables(): void
|
||||
{
|
||||
if (!$this->tableExists($this->prefix . 'miner_offers')) {
|
||||
@@ -1321,6 +1450,7 @@ final class SchemaManager
|
||||
$this->prefix . 'fx_rates',
|
||||
$this->prefix . 'measurement_rates',
|
||||
$this->prefix . 'payouts',
|
||||
$this->prefix . 'wallet_snapshots',
|
||||
$this->prefix . 'miner_offers',
|
||||
$this->prefix . 'purchased_miners',
|
||||
];
|
||||
@@ -1340,6 +1470,7 @@ final class SchemaManager
|
||||
$this->prefix . 'measurements',
|
||||
$this->prefix . 'measurement_rates',
|
||||
$this->prefix . 'payouts',
|
||||
$this->prefix . 'wallet_snapshots',
|
||||
$this->prefix . 'miner_offers',
|
||||
$this->prefix . 'targets',
|
||||
$this->prefix . 'dashboard_definitions',
|
||||
|
||||
Reference in New Issue
Block a user