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) {
|
async function submitPurchaseMiner(event) {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
if (!purchaseMinerForm.offer_id) {
|
if (!purchaseMinerForm.offer_id) {
|
||||||
@@ -3722,14 +3749,27 @@
|
|||||||
]),
|
]),
|
||||||
h('td', { key: 'active' }, row.is_active ? 'ja' : 'nein'),
|
h('td', { key: 'active' }, row.is_active ? 'ja' : 'nein'),
|
||||||
h('td', { key: 'action' },
|
h('td', { key: 'action' },
|
||||||
row.can_toggle_auto_renew
|
h('div', { className: 'mc-inline-row' }, [
|
||||||
? h('button', {
|
row.can_toggle_auto_renew
|
||||||
type: 'button',
|
? h('button', {
|
||||||
className: 'mc-button mc-button--ghost',
|
key: 'renew',
|
||||||
onClick: () => toggleMinerAutoRenew(row),
|
type: 'button',
|
||||||
disabled: saving,
|
className: 'mc-button mc-button--ghost',
|
||||||
}, row.auto_renew ? 'Verlaengerung aus' : 'Verlaengerung an')
|
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))]
|
: [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}/connection-test`
|
||||||
- `GET /api/mining-checker/v1/projects/{projectKey}/fx-history`
|
- `GET /api/mining-checker/v1/projects/{projectKey}/fx-history`
|
||||||
- `POST /api/mining-checker/v1/projects/{projectKey}/legacy-fx-migrate`
|
- `POST /api/mining-checker/v1/projects/{projectKey}/legacy-fx-migrate`
|
||||||
|
- `DELETE /api/mining-checker/v1/projects/{projectKey}/purchased-miners/{minerId}`
|
||||||
|
|
||||||
## Integration
|
## 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.
|
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.
|
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.
|
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
|
### Historische Krypto-Miner
|
||||||
|
|
||||||
|
|||||||
@@ -327,6 +327,11 @@ final class Router
|
|||||||
Http::json(['data' => $this->updatePurchasedMiner($projectKey, (int) $matches[1], Http::input())]);
|
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') {
|
if (preg_match('~^miner-offers/([^/]+)/purchase$~', $resource, $matches) && $method === 'POST') {
|
||||||
$this->repository()->ensureProject($projectKey);
|
$this->repository()->ensureProject($projectKey);
|
||||||
Http::json(['data' => $this->purchaseMiner($projectKey, rawurldecode((string) $matches[1]), Http::input())], 201);
|
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
|
private function findComputedMinerOffer(string $projectKey, string $offerId): ?array
|
||||||
{
|
{
|
||||||
foreach ($this->minerOffers($projectKey) as $offer) {
|
foreach ($this->minerOffers($projectKey) as $offer) {
|
||||||
|
|||||||
@@ -1144,6 +1144,37 @@ final class MiningRepository
|
|||||||
return $this->normalizeRow($fetch->fetch() ?: []);
|
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
|
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');
|
$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