From 28241e70c3325fe4d74b8849f916e2c2efff5b28 Mon Sep 17 00:00:00 2001 From: Padreug Date: Sun, 26 Apr 2026 16:00:04 +0200 Subject: [PATCH] feat: add deposit edit and delete for pending deposits Add PUT /api/v1/dca/deposits/{id} endpoint to update amount, currency, and notes on pending deposits. Add DELETE endpoint to remove deposits not yet inserted into the machine. Both endpoints reject confirmed deposits. Frontend now shows edit/delete buttons only for pending rows. Co-Authored-By: Claude Opus 4.6 (1M context) --- crud.py | 24 ++++++++++++- models.py | 12 +++++++ static/js/index.js | 26 ++++++++++++-- templates/satmachineadmin/index.html | 9 +++++ views_api.py | 53 ++++++++++++++++++++++++++++ 5 files changed, 121 insertions(+), 3 deletions(-) diff --git a/crud.py b/crud.py index 94c1d20..326352c 100644 --- a/crud.py +++ b/crud.py @@ -8,7 +8,7 @@ from lnbits.helpers import urlsafe_short_hash from .models import ( CreateDcaClientData, DcaClient, UpdateDcaClientData, - CreateDepositData, DcaDeposit, UpdateDepositStatusData, + CreateDepositData, DcaDeposit, UpdateDepositData, UpdateDepositStatusData, CreateDcaPaymentData, DcaPayment, ClientBalanceSummary, CreateLamassuConfigData, LamassuConfig, UpdateLamassuConfigData, @@ -153,6 +153,28 @@ async def update_deposit_status(deposit_id: str, data: UpdateDepositStatusData) return await get_deposit(deposit_id) +async def update_deposit(deposit_id: str, data: UpdateDepositData) -> Optional[DcaDeposit]: + update_data = {k: v for k, v in data.dict().items() if v is not None} + if not update_data: + return await get_deposit(deposit_id) + + set_clause = ", ".join([f"{k} = :{k}" for k in update_data.keys()]) + update_data["id"] = deposit_id + + await db.execute( + f"UPDATE satoshimachine.dca_deposits SET {set_clause} WHERE id = :id", + update_data + ) + return await get_deposit(deposit_id) + + +async def delete_deposit(deposit_id: str) -> None: + await db.execute( + "DELETE FROM satoshimachine.dca_deposits WHERE id = :id", + {"id": deposit_id} + ) + + # DCA Payment CRUD Operations async def create_dca_payment(data: CreateDcaPaymentData) -> DcaPayment: payment_id = urlsafe_short_hash() diff --git a/models.py b/models.py index e4bf64d..2fb60a6 100644 --- a/models.py +++ b/models.py @@ -60,6 +60,18 @@ class DcaDeposit(BaseModel): confirmed_at: Optional[datetime] +class UpdateDepositData(BaseModel): + amount: Optional[float] = None + currency: Optional[str] = None + notes: Optional[str] = None + + @validator('amount') + def round_amount_to_cents(cls, v): + if v is not None: + return round(float(v), 2) + return v + + class UpdateDepositStatusData(BaseModel): status: str notes: Optional[str] = None diff --git a/static/js/index.js b/static/js/index.js index 610ec0a..50b0fce 100644 --- a/static/js/index.js +++ b/static/js/index.js @@ -389,12 +389,12 @@ window.app = Vue.createApp({ } if (this.depositFormDialog.data.id) { - // Update existing deposit (mainly for notes/status) + // Update existing pending deposit const { data: updatedDeposit } = await LNbits.api.request( 'PUT', `/satmachineadmin/api/v1/dca/deposits/${this.depositFormDialog.data.id}`, null, - { status: this.depositFormDialog.data.status, notes: data.notes } + { amount: data.amount, currency: data.currency, notes: data.notes } ) const index = this.deposits.findIndex(d => d.id === updatedDeposit.id) if (index !== -1) { @@ -460,6 +460,28 @@ window.app = Vue.createApp({ this.depositFormDialog.show = true }, + async deleteDeposit(deposit) { + try { + await LNbits.utils + .confirmDialog('Are you sure you want to delete this pending deposit?') + .onOk(async () => { + await LNbits.api.request( + 'DELETE', + `/satmachineadmin/api/v1/dca/deposits/${deposit.id}`, + null + ) + this.deposits = this.deposits.filter(d => d.id !== deposit.id) + this.$q.notify({ + type: 'positive', + message: 'Deposit deleted successfully', + timeout: 5000 + }) + }) + } catch (error) { + LNbits.utils.notifyApiError(error) + } + }, + // Export Methods async exportClientsCSV() { await LNbits.utils.exportCSV(this.clientsTable.columns, this.dcaClients) diff --git a/templates/satmachineadmin/index.html b/templates/satmachineadmin/index.html index 83de8c7..9c0f3a9 100644 --- a/templates/satmachineadmin/index.html +++ b/templates/satmachineadmin/index.html @@ -192,12 +192,21 @@ Confirm Deposit Edit Deposit + + Delete Deposit + diff --git a/views_api.py b/views_api.py index a4a8b94..eb38da8 100644 --- a/views_api.py +++ b/views_api.py @@ -19,7 +19,9 @@ from .crud import ( create_deposit, get_all_deposits, get_deposit, + update_deposit, update_deposit_status, + delete_deposit, get_client_balance_summary, # Lamassu config CRUD operations create_lamassu_config, @@ -39,6 +41,7 @@ from .models import ( UpdateDcaClientData, CreateDepositData, DcaDeposit, + UpdateDepositData, UpdateDepositStatusData, ClientBalanceSummary, CreateLamassuConfigData, @@ -161,6 +164,56 @@ async def api_update_deposit_status( return updated_deposit +@satmachineadmin_api_router.put("/api/v1/dca/deposits/{deposit_id}") +async def api_update_deposit( + deposit_id: str, + data: UpdateDepositData, + user: User = Depends(check_super_user), +) -> DcaDeposit: + """Update deposit fields (amount, currency, notes). Only pending deposits can be edited.""" + deposit = await get_deposit(deposit_id) + if not deposit: + raise HTTPException( + status_code=HTTPStatus.NOT_FOUND, detail="Deposit not found." + ) + + if deposit.status != "pending": + raise HTTPException( + status_code=HTTPStatus.BAD_REQUEST, + detail="Only pending deposits can be edited.", + ) + + updated_deposit = await update_deposit(deposit_id, data) + if not updated_deposit: + raise HTTPException( + status_code=HTTPStatus.INTERNAL_SERVER_ERROR, + detail="Failed to update deposit.", + ) + return updated_deposit + + +@satmachineadmin_api_router.delete("/api/v1/dca/deposits/{deposit_id}") +async def api_delete_deposit( + deposit_id: str, + user: User = Depends(check_super_user), +): + """Delete a deposit. Only pending deposits (not yet inserted into the machine) can be deleted.""" + deposit = await get_deposit(deposit_id) + if not deposit: + raise HTTPException( + status_code=HTTPStatus.NOT_FOUND, detail="Deposit not found." + ) + + if deposit.status != "pending": + raise HTTPException( + status_code=HTTPStatus.BAD_REQUEST, + detail="Only pending deposits can be deleted. Confirmed deposits have already been inserted into the machine.", + ) + + await delete_deposit(deposit_id) + return {"message": "Deposit deleted successfully"} + + # Transaction Polling Endpoints