miner
This commit is contained in:
@@ -2775,6 +2775,33 @@
|
||||
}
|
||||
}
|
||||
|
||||
async function deletePurchasedMiner(miner) {
|
||||
if (!miner || !miner.id) {
|
||||
return;
|
||||
}
|
||||
|
||||
const label = String(miner.label || 'diesen Miner');
|
||||
if (!window.confirm(`Miete ${label} wirklich loeschen? Kosten, feste Krypto-Bewertung und der Wallet-Abzug werden ebenfalls entfernt.`)) {
|
||||
return;
|
||||
}
|
||||
|
||||
setSaving(true);
|
||||
setError('');
|
||||
setMessage('');
|
||||
try {
|
||||
await request(`${apiBase}/projects/${encodeURIComponent(projectKey)}/purchased-miners/${encodeURIComponent(miner.id)}`, {
|
||||
method: 'DELETE',
|
||||
});
|
||||
invalidateProjectBootstrapCache(projectKey);
|
||||
await loadBootstrap(projectKey);
|
||||
setMessage('Gemieteten Miner mit allen zugeordneten Kosten geloescht.');
|
||||
} catch (err) {
|
||||
setError(err.message);
|
||||
} finally {
|
||||
setSaving(false);
|
||||
}
|
||||
}
|
||||
|
||||
async function submitPurchaseMiner(event) {
|
||||
event.preventDefault();
|
||||
if (!purchaseMinerForm.offer_id) {
|
||||
@@ -3722,14 +3749,27 @@
|
||||
]),
|
||||
h('td', { key: 'active' }, row.is_active ? 'ja' : 'nein'),
|
||||
h('td', { key: 'action' },
|
||||
row.can_toggle_auto_renew
|
||||
? h('button', {
|
||||
type: 'button',
|
||||
className: 'mc-button mc-button--ghost',
|
||||
onClick: () => toggleMinerAutoRenew(row),
|
||||
disabled: saving,
|
||||
}, row.auto_renew ? 'Verlaengerung aus' : 'Verlaengerung an')
|
||||
: '—'
|
||||
h('div', { className: 'mc-inline-row' }, [
|
||||
row.can_toggle_auto_renew
|
||||
? h('button', {
|
||||
key: 'renew',
|
||||
type: 'button',
|
||||
className: 'mc-button mc-button--ghost',
|
||||
onClick: () => toggleMinerAutoRenew(row),
|
||||
disabled: saving,
|
||||
}, row.auto_renew ? 'Verlaengerung aus' : 'Verlaengerung an')
|
||||
: null,
|
||||
row.source === 'miete'
|
||||
? h('button', {
|
||||
key: 'delete',
|
||||
type: 'button',
|
||||
className: 'mc-button mc-button--ghost',
|
||||
onClick: () => deletePurchasedMiner({ id: row.miner_id, label: row.label }),
|
||||
disabled: saving,
|
||||
}, 'Loeschen')
|
||||
: null,
|
||||
!row.can_toggle_auto_renew && row.source !== 'miete' ? '—' : null,
|
||||
])
|
||||
),
|
||||
]))
|
||||
: [h('tr', { key: 'empty' }, h('td', { colSpan: 9 }, emptyText))]
|
||||
|
||||
@@ -50,6 +50,7 @@ custom/apps/mining-checker/
|
||||
- `GET /api/mining-checker/v1/projects/{projectKey}/connection-test`
|
||||
- `GET /api/mining-checker/v1/projects/{projectKey}/fx-history`
|
||||
- `POST /api/mining-checker/v1/projects/{projectKey}/legacy-fx-migrate`
|
||||
- `DELETE /api/mining-checker/v1/projects/{projectKey}/purchased-miners/{minerId}`
|
||||
|
||||
## Integration
|
||||
|
||||
@@ -137,6 +138,7 @@ Diese Regeln sind bei jeder Erweiterung der Wallet-, Miner- oder Uebersichtslogi
|
||||
6. Jede erfasste Auszahlung aus dem Mining-Tool muss den Walletbestand automatisch fortschreiben. Externe Auszahlungen aus dem hier verfolgten Wallet reduzieren ihn.
|
||||
7. Der angezeigte bisherige Wert trennt variable Werte (Coins im Mining-Tool und Wallet zum aktuellen Kurs) von fixen Werten (bereits mit Krypto bezahlte Miner zum Ausgabezeitpunkt). Die Gesamtsumme besteht aus beiden Kategorien.
|
||||
8. Jede neue feste Krypto-Buchung referenziert die `fetch_id` des Moduls `fx-rates`. Der Mining-Checker fuehrt keine eigene Kurstabelle. Umrechnungen in andere Berichtswährungen verwenden fuer fixe Werte denselben historischen API-Snapshot.
|
||||
9. Wird ein gemieteter Miner geloescht, wird sein kompletter Datensatz entfernt: Mietkosten, feste Krypto-Bewertung, FX-Referenz und der daraus abgeleitete Wallet-Abzug entfallen. Der Miner beeinflusst danach weder Walletbestand noch Ausgaben, Reinvest, Hashrate oder Break-even. Basis-Angebote, Mining-Uploads und andere Wallet-Buchungen bleiben unveraendert.
|
||||
|
||||
### Historische Krypto-Miner
|
||||
|
||||
|
||||
@@ -327,6 +327,11 @@ final class Router
|
||||
Http::json(['data' => $this->updatePurchasedMiner($projectKey, (int) $matches[1], Http::input())]);
|
||||
}
|
||||
|
||||
if (preg_match('~^purchased-miners/(\d+)$~', $resource, $matches) && $method === 'DELETE') {
|
||||
$this->deletePurchasedMiner($projectKey, (int) $matches[1]);
|
||||
Http::json(['data' => ['deleted' => true]]);
|
||||
}
|
||||
|
||||
if (preg_match('~^miner-offers/([^/]+)/purchase$~', $resource, $matches) && $method === 'POST') {
|
||||
$this->repository()->ensureProject($projectKey);
|
||||
Http::json(['data' => $this->purchaseMiner($projectKey, rawurldecode((string) $matches[1]), Http::input())], 201);
|
||||
@@ -2179,6 +2184,19 @@ final class Router
|
||||
);
|
||||
}
|
||||
|
||||
private function deletePurchasedMiner(string $projectKey, int $minerId): void
|
||||
{
|
||||
if ($minerId <= 0) {
|
||||
throw new ApiException('miner_id ist ungueltig.', 422);
|
||||
}
|
||||
|
||||
if (!is_array($this->repository()->getPurchasedMiner($projectKey, $minerId))) {
|
||||
throw new ApiException('Gemieteter Miner nicht gefunden.', 404);
|
||||
}
|
||||
|
||||
$this->repository()->deletePurchasedMiner($projectKey, $minerId);
|
||||
}
|
||||
|
||||
private function findComputedMinerOffer(string $projectKey, string $offerId): ?array
|
||||
{
|
||||
foreach ($this->minerOffers($projectKey) as $offer) {
|
||||
|
||||
@@ -1144,6 +1144,37 @@ final class MiningRepository
|
||||
return $this->normalizeRow($fetch->fetch() ?: []);
|
||||
}
|
||||
|
||||
public function deletePurchasedMiner(string $projectKey, int $minerId): void
|
||||
{
|
||||
if ($minerId <= 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
$delete = $this->pdo->prepare(
|
||||
'DELETE FROM ' . $this->table('purchased_miners') . '
|
||||
WHERE id = :id AND project_key = :project_key AND owner_sub = :owner_sub'
|
||||
);
|
||||
$delete->execute([
|
||||
'id' => $minerId,
|
||||
'project_key' => $projectKey,
|
||||
'owner_sub' => $this->ownerSub,
|
||||
]);
|
||||
|
||||
if ($delete->rowCount() > 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Older rows without an owner assignment remain deletable like the existing read/update fallback.
|
||||
$legacyDelete = $this->pdo->prepare(
|
||||
'DELETE FROM ' . $this->table('purchased_miners') . '
|
||||
WHERE id = :id AND project_key = :project_key AND owner_sub IS NULL'
|
||||
);
|
||||
$legacyDelete->execute([
|
||||
'id' => $minerId,
|
||||
'project_key' => $projectKey,
|
||||
]);
|
||||
}
|
||||
|
||||
public function getMinerOffer(string $projectKey, int $offerId): ?array
|
||||
{
|
||||
$stmt = $this->pdo->prepare('SELECT * FROM ' . $this->table('miner_offers') . ' WHERE project_key = :project_key AND id = :id LIMIT 1');
|
||||
|
||||
Reference in New Issue
Block a user