Add DCA admin extension with CRUD operations for clients and deposits, including UI components for managing deposits and client details.

This commit is contained in:
padreug 2025-06-17 19:26:31 +02:00
parent 1196349cbc
commit 7bafc67370
3 changed files with 755 additions and 43 deletions

View file

@ -4,6 +4,69 @@ window.app = Vue.createApp({
delimiters: ['${', '}'],
data: function () {
return {
// DCA Admin Data
dcaClients: [],
deposits: [],
totalDcaBalance: 0,
// Table configurations
clientsTable: {
columns: [
{name: 'user_id', align: 'left', label: 'User ID', field: 'user_id'},
{name: 'wallet_id', align: 'left', label: 'Wallet ID', field: 'wallet_id'},
{name: 'dca_mode', align: 'left', label: 'DCA Mode', field: 'dca_mode'},
{name: 'fixed_mode_daily_limit', align: 'left', label: 'Daily Limit', field: 'fixed_mode_daily_limit'},
{name: 'status', align: 'left', label: 'Status', field: 'status'}
],
pagination: {
rowsPerPage: 10
}
},
depositsTable: {
columns: [
{name: 'client_id', align: 'left', label: 'Client', field: 'client_id'},
{name: 'amount', align: 'left', label: 'Amount', field: 'amount'},
{name: 'currency', align: 'left', label: 'Currency', field: 'currency'},
{name: 'status', align: 'left', label: 'Status', field: 'status'},
{name: 'created_at', align: 'left', label: 'Created', field: 'created_at'},
{name: 'notes', align: 'left', label: 'Notes', field: 'notes'}
],
pagination: {
rowsPerPage: 10
}
},
// Dialog states
clientFormDialog: {
show: false,
data: {
dca_mode: 'flow',
currency: 'GTQ'
}
},
depositFormDialog: {
show: false,
data: {
currency: 'GTQ'
}
},
clientDetailsDialog: {
show: false,
data: null,
balance: null
},
// Options
dcaModeOptions: [
{label: 'Flow Mode', value: 'flow'},
{label: 'Fixed Mode', value: 'fixed'}
],
currencyOptions: [
{label: 'GTQ', value: 'GTQ'},
{label: 'USD', value: 'USD'}
],
// Legacy data (keep for backward compatibility)
invoiceAmount: 10,
qrValue: 'lnurlpay',
myex: [],
@ -11,18 +74,8 @@ window.app = Vue.createApp({
columns: [
{name: 'id', align: 'left', label: 'ID', field: 'id'},
{name: 'name', align: 'left', label: 'Name', field: 'name'},
{
name: 'wallet',
align: 'left',
label: 'Wallet',
field: 'wallet'
},
{
name: 'total',
align: 'left',
label: 'Total sent/received',
field: 'total'
}
{name: 'wallet', align: 'left', label: 'Wallet', field: 'wallet'},
{name: 'total', align: 'left', label: 'Total sent/received', field: 'total'}
],
pagination: {
rowsPerPage: 10
@ -45,6 +98,217 @@ window.app = Vue.createApp({
///////////////////////////////////////////////////
methods: {
// Utility Methods
formatCurrency(amount) {
if (!amount) return 'Q 0.00'
return `Q ${(amount / 100).toFixed(2)}`
},
formatDate(dateString) {
if (!dateString) return ''
return new Date(dateString).toLocaleDateString()
},
// DCA Client Methods
async getDcaClients() {
try {
const {data} = await LNbits.api.request(
'GET',
'/myextension/api/v1/dca/clients',
this.g.user.wallets[0].inkey
)
this.dcaClients = data
} catch (error) {
LNbits.utils.notifyApiError(error)
}
},
async sendClientData() {
try {
const data = {
user_id: this.clientFormDialog.data.user_id,
wallet_id: this.clientFormDialog.data.wallet_id,
dca_mode: this.clientFormDialog.data.dca_mode,
fixed_mode_daily_limit: this.clientFormDialog.data.fixed_mode_daily_limit
}
if (this.clientFormDialog.data.id) {
// Update existing client
const {data: updatedClient} = await LNbits.api.request(
'PUT',
`/myextension/api/v1/dca/clients/${this.clientFormDialog.data.id}`,
this.g.user.wallets[0].adminkey,
data
)
// Update client in array
const index = this.dcaClients.findIndex(c => c.id === updatedClient.id)
if (index !== -1) {
this.dcaClients.splice(index, 1, updatedClient)
}
} else {
// Create new client
const {data: newClient} = await LNbits.api.request(
'POST',
'/myextension/api/v1/dca/clients',
this.g.user.wallets[0].adminkey,
data
)
this.dcaClients.push(newClient)
}
this.closeClientFormDialog()
this.$q.notify({
type: 'positive',
message: this.clientFormDialog.data.id ? 'Client updated successfully' : 'Client created successfully',
timeout: 5000
})
} catch (error) {
LNbits.utils.notifyApiError(error)
}
},
closeClientFormDialog() {
this.clientFormDialog.show = false
this.clientFormDialog.data = {
dca_mode: 'flow',
currency: 'GTQ'
}
},
editClient(client) {
this.clientFormDialog.data = {...client}
this.clientFormDialog.show = true
},
async viewClientDetails(client) {
try {
const {data: balance} = await LNbits.api.request(
'GET',
`/myextension/api/v1/dca/clients/${client.id}/balance`,
this.g.user.wallets[0].inkey
)
this.clientDetailsDialog.data = client
this.clientDetailsDialog.balance = balance
this.clientDetailsDialog.show = true
} catch (error) {
LNbits.utils.notifyApiError(error)
}
},
// Deposit Methods
async getDeposits() {
try {
const {data} = await LNbits.api.request(
'GET',
'/myextension/api/v1/dca/deposits',
this.g.user.wallets[0].inkey
)
this.deposits = data
} catch (error) {
LNbits.utils.notifyApiError(error)
}
},
addDepositDialog(client) {
this.depositFormDialog.data = {
client_id: client.id,
client_name: `${client.user_id.substring(0, 8)}...`,
currency: 'GTQ'
}
this.depositFormDialog.show = true
},
async sendDepositData() {
try {
const data = {
client_id: this.depositFormDialog.data.client_id,
amount: this.depositFormDialog.data.amount,
currency: this.depositFormDialog.data.currency,
notes: this.depositFormDialog.data.notes
}
if (this.depositFormDialog.data.id) {
// Update existing deposit (mainly for notes/status)
const {data: updatedDeposit} = await LNbits.api.request(
'PUT',
`/myextension/api/v1/dca/deposits/${this.depositFormDialog.data.id}`,
this.g.user.wallets[0].adminkey,
{status: this.depositFormDialog.data.status, notes: data.notes}
)
const index = this.deposits.findIndex(d => d.id === updatedDeposit.id)
if (index !== -1) {
this.deposits.splice(index, 1, updatedDeposit)
}
} else {
// Create new deposit
const {data: newDeposit} = await LNbits.api.request(
'POST',
'/myextension/api/v1/dca/deposits',
this.g.user.wallets[0].adminkey,
data
)
this.deposits.unshift(newDeposit)
}
this.closeDepositFormDialog()
this.$q.notify({
type: 'positive',
message: this.depositFormDialog.data.id ? 'Deposit updated successfully' : 'Deposit created successfully',
timeout: 5000
})
} catch (error) {
LNbits.utils.notifyApiError(error)
}
},
closeDepositFormDialog() {
this.depositFormDialog.show = false
this.depositFormDialog.data = {
currency: 'GTQ'
}
},
async confirmDeposit(deposit) {
try {
await LNbits.utils
.confirmDialog('Confirm that this deposit has been physically placed in the ATM machine?')
.onOk(async () => {
const {data: updatedDeposit} = await LNbits.api.request(
'PUT',
`/myextension/api/v1/dca/deposits/${deposit.id}/status`,
this.g.user.wallets[0].adminkey,
{status: 'confirmed', notes: 'Confirmed by admin - money placed in machine'}
)
const index = this.deposits.findIndex(d => d.id === deposit.id)
if (index !== -1) {
this.deposits.splice(index, 1, updatedDeposit)
}
this.$q.notify({
type: 'positive',
message: 'Deposit confirmed! DCA is now active for this client.',
timeout: 5000
})
})
} catch (error) {
LNbits.utils.notifyApiError(error)
}
},
editDeposit(deposit) {
this.depositFormDialog.data = {...deposit}
this.depositFormDialog.show = true
},
// Export Methods
async exportClientsCSV() {
await LNbits.utils.exportCSV(this.clientsTable.columns, this.dcaClients)
},
async exportDepositsCSV() {
await LNbits.utils.exportCSV(this.depositsTable.columns, this.deposits)
},
// Legacy Methods (keep for backward compatibility)
async closeFormDialog() {
this.formDialog.show = false
this.formDialog.data = {}
@ -237,6 +501,30 @@ window.app = Vue.createApp({
//////LIFECYCLE FUNCTIONS RUNNING ON PAGE LOAD/////
///////////////////////////////////////////////////
async created() {
// Load DCA admin data
await Promise.all([
this.getDcaClients(),
this.getDeposits()
])
// Calculate total DCA balance
this.calculateTotalDcaBalance()
// Legacy data loading
await this.getMyExtensions()
},
watch: {
deposits() {
this.calculateTotalDcaBalance()
}
},
computed: {
calculateTotalDcaBalance() {
this.totalDcaBalance = this.deposits
.filter(d => d.status === 'confirmed')
.reduce((total, deposit) => total + deposit.amount, 0)
}
}
})

View file

@ -1,18 +1,140 @@
<!--/////////////////////////////////////////////////-->
<!--//PAGE FOR THE EXTENSIONS BACKEND IN LNBITS//////-->
<!--//PAGE FOR THE DCA ADMIN EXTENSION IN LNBITS//////-->
<!--/////////////////////////////////////////////////-->
{% extends "base.html" %} {% from "macros.jinja" import window_vars with context
%} {% block scripts %} {{ window_vars(user) }}
<script src="{{ static_url_for('myextension/static', path='js/index.js') }}"></script>
{% endblock %} {% block page %}
<div class="row q-col-gutter-md" id="makeItRain">
<div class="row q-col-gutter-md" id="dcaAdmin">
<div class="col-12 col-md-8 col-lg-7 q-gutter-y-md">
<!-- Client Management Section -->
<q-card>
<q-card-section>
<q-btn unelevated color="primary" @click="formDialog.show = true"
>New MyExtension</q-btn
<div class="row items-center no-wrap q-mb-md">
<div class="col">
<h5 class="text-subtitle1 q-my-none">DCA Client Management</h5>
</div>
<div class="col-auto">
<q-btn unelevated color="primary" @click="clientFormDialog.show = true">
Add New Client
</q-btn>
</div>
</div>
</q-card-section>
</q-card>
<!-- DCA Clients Table -->
<q-card>
<q-card-section>
<div class="row items-center no-wrap q-mb-md">
<div class="col">
<h6 class="text-subtitle2 q-my-none">Active DCA Clients</h6>
</div>
<div class="col-auto">
<q-btn flat color="grey" @click="exportClientsCSV">Export to CSV</q-btn>
</div>
</div>
<q-table
dense
flat
:rows="dcaClients"
row-key="id"
:columns="clientsTable.columns"
v-model:pagination="clientsTable.pagination"
>
<template v-slot:body="props">
<q-tr :props="props">
<q-td v-for="col in props.cols" :key="col.name" :props="props">
<div v-if="col.field == 'user_id'">${ col.value.substring(0, 8) }...</div>
<div v-else-if="col.field == 'wallet_id'">${ col.value.substring(0, 8) }...</div>
<div v-else-if="col.field == 'status'">
<q-badge :color="col.value === 'active' ? 'green' : 'red'">
${ col.value }
</q-badge>
</div>
<div v-else>${ col.value }</div>
</q-td>
<q-td auto-width>
<q-btn
flat dense size="sm" icon="account_balance_wallet"
color="primary" class="q-mr-sm"
@click="addDepositDialog(props.row)"
>
<q-tooltip>Add Deposit</q-tooltip>
</q-btn>
<q-btn
flat dense size="sm" icon="visibility"
color="blue" class="q-mr-sm"
@click="viewClientDetails(props.row)"
>
<q-tooltip>View Details</q-tooltip>
</q-btn>
<q-btn
flat dense size="sm" icon="edit"
color="orange" class="q-mr-sm"
@click="editClient(props.row)"
>
<q-tooltip>Edit Client</q-tooltip>
</q-btn>
</q-td>
</q-tr>
</template>
</q-table>
</q-card-section>
</q-card>
<!-- Deposits Management Section -->
<q-card>
<q-card-section>
<div class="row items-center no-wrap q-mb-md">
<div class="col">
<h6 class="text-subtitle2 q-my-none">Recent Deposits</h6>
</div>
<div class="col-auto">
<q-btn flat color="grey" @click="exportDepositsCSV">Export to CSV</q-btn>
</div>
</div>
<q-table
dense
flat
:rows="deposits"
row-key="id"
:columns="depositsTable.columns"
v-model:pagination="depositsTable.pagination"
>
<template v-slot:body="props">
<q-tr :props="props">
<q-td v-for="col in props.cols" :key="col.name" :props="props">
<div v-if="col.field == 'amount'">${ formatCurrency(col.value) }</div>
<div v-else-if="col.field == 'status'">
<q-badge :color="col.value === 'confirmed' ? 'green' : 'orange'">
${ col.value }
</q-badge>
</div>
<div v-else-if="col.field == 'created_at'">${ formatDate(col.value) }</div>
<div v-else>${ col.value }</div>
</q-td>
<q-td auto-width>
<q-btn
v-if="props.row.status === 'pending'"
flat dense size="sm" icon="check_circle"
color="green" class="q-mr-sm"
@click="confirmDeposit(props.row)"
>
<q-tooltip>Confirm Deposit</q-tooltip>
</q-btn>
<q-btn
flat dense size="sm" icon="edit"
color="orange"
@click="editDeposit(props.row)"
>
<q-tooltip>Edit Deposit</q-tooltip>
</q-btn>
</q-td>
</q-tr>
</template>
</q-table>
</q-card-section>
</q-card>
@ -109,75 +231,99 @@
<q-card>
<q-card-section>
<h6 class="text-subtitle1 q-my-none">
{{SITE_TITLE}} MyExtension extension
{{SITE_TITLE}} DCA Admin Extension
</h6>
<p>
Simple extension you can use as a base for your own extension. <br />
Includes very simple LNURL-pay and LNURL-withdraw example.
Dollar Cost Averaging administration for Lamassu ATM integration. <br />
Manage client deposits and DCA distribution settings.
</p>
</q-card-section>
<q-card-section class="q-pa-none">
<q-separator></q-separator>
<q-list>
{% include "myextension/_api_docs.html" %}
<q-expansion-item
group="api"
icon="info"
label="DCA System Status"
:content-inset-level="0.5"
>
<q-card-section class="text-caption">
<div class="row">
<div class="col-6">Active Clients:</div>
<div class="col-6">${ dcaClients.filter(c => c.status === 'active').length }</div>
</div>
<div class="row">
<div class="col-6">Pending Deposits:</div>
<div class="col-6">${ deposits.filter(d => d.status === 'pending').length }</div>
</div>
<div class="row">
<div class="col-6">Total DCA Balance:</div>
<div class="col-6">${ formatCurrency(totalDcaBalance) }</div>
</div>
</q-card-section>
</q-expansion-item>
<q-separator></q-separator>
{% include "myextension/_myextension.html" %}
{% include "myextension/_api_docs.html" %}
</q-list>
</q-card-section>
</q-card>
</div>
<!--/////////////////////////////////////////////////-->
<!--//////////////FORM DIALOG////////////////////////-->
<!--//////////////DCA CLIENT FORM DIALOG//////////////-->
<!--/////////////////////////////////////////////////-->
<q-dialog v-model="formDialog.show" position="top" @hide="closeFormDialog">
<q-dialog v-model="clientFormDialog.show" position="top" @hide="closeClientFormDialog">
<q-card class="q-pa-lg q-pt-xl" style="width: 500px">
<q-form @submit="sendMyExtensionData" class="q-gutter-md">
<q-form @submit="sendClientData" class="q-gutter-md">
<q-input
filled
dense
v-model.trim="formDialog.data.name"
label="Name"
placeholder="Name for your record"
v-model.trim="clientFormDialog.data.user_id"
label="User ID *"
placeholder="LNBits User ID"
></q-input>
<q-select
filled
dense
emit-value
v-model="formDialog.data.wallet"
v-model="clientFormDialog.data.wallet_id"
:options="g.user.walletOptions"
label="Wallet *"
label="DCA Wallet *"
hint="Wallet to receive DCA payments"
></q-select>
<q-select
filled
dense
emit-value
v-model="clientFormDialog.data.dca_mode"
:options="dcaModeOptions"
label="DCA Mode *"
></q-select>
<q-input
v-if="clientFormDialog.data.dca_mode === 'fixed'"
filled
dense
type="number"
v-model.trim="formDialog.data.lnurlwithdrawamount"
label="LNURL-withdraw amount"
></q-input>
<q-input
filled
dense
type="number"
v-model.trim="formDialog.data.lnurlpayamount"
label="LNURL-pay amount"
v-model.number="clientFormDialog.data.fixed_mode_daily_limit"
label="Daily Limit (GTQ)"
placeholder="Maximum daily DCA amount"
></q-input>
<div class="row q-mt-lg">
<q-btn
v-if="formDialog.data.id"
v-if="clientFormDialog.data.id"
unelevated
color="primary"
type="submit"
>Update MyExtension</q-btn
>Update Client</q-btn
>
<q-btn
v-else
unelevated
color="primary"
:disable="formDialog.data.name == null || formDialog.data.wallet == null || formDialog.data.lnurlwithdrawamount == null || formDialog.data.lnurlpayamount == null"
:disable="!clientFormDialog.data.user_id || !clientFormDialog.data.wallet_id"
type="submit"
>Create MyExtension</q-btn
>Create Client</q-btn
>
<q-btn v-close-popup flat color="grey" class="q-ml-auto"
>Cancel</q-btn
@ -187,6 +333,117 @@
</q-card>
</q-dialog>
<!--/////////////////////////////////////////////////-->
<!--//////////////DEPOSIT FORM DIALOG////////////////-->
<!--/////////////////////////////////////////////////-->
<q-dialog v-model="depositFormDialog.show" position="top" @hide="closeDepositFormDialog">
<q-card class="q-pa-lg q-pt-xl" style="width: 500px">
<q-form @submit="sendDepositData" class="q-gutter-md">
<div v-if="depositFormDialog.data.client_name" class="text-h6 q-mb-md">
Deposit for: ${ depositFormDialog.data.client_name }
</div>
<q-input
filled
dense
type="number"
v-model.number="depositFormDialog.data.amount"
label="Deposit Amount (GTQ) *"
placeholder="Amount in centavos (GTQ * 100)"
hint="Enter amount in centavos (1 GTQ = 100 centavos)"
></q-input>
<q-select
filled
dense
emit-value
v-model="depositFormDialog.data.currency"
:options="currencyOptions"
label="Currency"
></q-select>
<q-input
filled
dense
type="textarea"
v-model.trim="depositFormDialog.data.notes"
label="Notes"
placeholder="Optional notes about this deposit"
rows="3"
></q-input>
<div class="row q-mt-lg">
<q-btn
v-if="depositFormDialog.data.id"
unelevated
color="primary"
type="submit"
>Update Deposit</q-btn
>
<q-btn
v-else
unelevated
color="primary"
:disable="!depositFormDialog.data.amount"
type="submit"
>Create Deposit</q-btn
>
<q-btn v-close-popup flat color="grey" class="q-ml-auto"
>Cancel</q-btn
>
</div>
</q-form>
</q-card>
</q-dialog>
<!--/////////////////////////////////////////////////-->
<!--//////////////CLIENT DETAILS DIALOG//////////////-->
<!--/////////////////////////////////////////////////-->
<q-dialog v-model="clientDetailsDialog.show" position="top">
<q-card class="q-pa-lg" style="width: 600px; max-width: 90vw">
<div class="text-h6 q-mb-md">Client Details</div>
<div v-if="clientDetailsDialog.data">
<q-list>
<q-item>
<q-item-section>
<q-item-label caption>User ID</q-item-label>
<q-item-label>${ clientDetailsDialog.data.user_id }</q-item-label>
</q-item-section>
</q-item>
<q-item>
<q-item-section>
<q-item-label caption>Wallet ID</q-item-label>
<q-item-label>${ clientDetailsDialog.data.wallet_id }</q-item-label>
</q-item-section>
</q-item>
<q-item>
<q-item-section>
<q-item-label caption>DCA Mode</q-item-label>
<q-item-label>${ clientDetailsDialog.data.dca_mode }</q-item-label>
</q-item-section>
</q-item>
<q-item v-if="clientDetailsDialog.data.fixed_mode_daily_limit">
<q-item-section>
<q-item-label caption>Daily Limit</q-item-label>
<q-item-label>${ formatCurrency(clientDetailsDialog.data.fixed_mode_daily_limit) }</q-item-label>
</q-item-section>
</q-item>
<q-item>
<q-item-section>
<q-item-label caption>Balance Summary</q-item-label>
<q-item-label v-if="clientDetailsDialog.balance">
Deposits: ${ formatCurrency(clientDetailsDialog.balance.total_deposits) } |
Payments: ${ formatCurrency(clientDetailsDialog.balance.total_payments) } |
Remaining: ${ formatCurrency(clientDetailsDialog.balance.remaining_balance) }
</q-item-label>
</q-item-section>
</q-item>
</q-list>
</div>
<div class="row q-mt-lg">
<q-btn v-close-popup flat color="grey" class="q-ml-auto">Close</q-btn>
</div>
</q-card>
</q-dialog>
<!--/////////////////////////////////////////////////-->
<!--//////////////QR Code DIALOG/////////////////////-->
<!--/////////////////////////////////////////////////-->

View file

@ -15,9 +15,26 @@ from .crud import (
get_myextension,
get_myextensions,
update_myextension,
# DCA CRUD operations
create_dca_client,
get_dca_clients,
get_dca_client,
update_dca_client,
delete_dca_client,
create_deposit,
get_all_deposits,
get_deposit,
update_deposit_status,
get_client_balance_summary,
)
from .helpers import lnurler
from .models import CreateMyExtensionData, CreatePayment, MyExtension
from .models import (
CreateMyExtensionData, CreatePayment, MyExtension,
# DCA models
CreateDcaClientData, DcaClient, UpdateDcaClientData,
CreateDepositData, DcaDeposit, UpdateDepositStatusData,
ClientBalanceSummary
)
myextension_api_router = APIRouter()
@ -173,3 +190,153 @@ async def api_myextension_create_invoice(data: CreatePayment) -> dict:
)
return {"payment_hash": payment.payment_hash, "payment_request": payment.bolt11}
###################################################
################ DCA API ENDPOINTS ################
###################################################
# DCA Client Endpoints
@myextension_api_router.get("/api/v1/dca/clients")
async def api_get_dca_clients(
wallet: WalletTypeInfo = Depends(require_invoice_key),
) -> list[DcaClient]:
"""Get all DCA clients"""
return await get_dca_clients()
@myextension_api_router.get("/api/v1/dca/clients/{client_id}")
async def api_get_dca_client(
client_id: str,
wallet: WalletTypeInfo = Depends(require_invoice_key),
) -> DcaClient:
"""Get a specific DCA client"""
client = await get_dca_client(client_id)
if not client:
raise HTTPException(
status_code=HTTPStatus.NOT_FOUND, detail="DCA client not found."
)
return client
@myextension_api_router.post("/api/v1/dca/clients", status_code=HTTPStatus.CREATED)
async def api_create_dca_client(
data: CreateDcaClientData,
wallet: WalletTypeInfo = Depends(require_admin_key),
) -> DcaClient:
"""Create a new DCA client"""
return await create_dca_client(data)
@myextension_api_router.put("/api/v1/dca/clients/{client_id}")
async def api_update_dca_client(
client_id: str,
data: UpdateDcaClientData,
wallet: WalletTypeInfo = Depends(require_admin_key),
) -> DcaClient:
"""Update a DCA client"""
client = await get_dca_client(client_id)
if not client:
raise HTTPException(
status_code=HTTPStatus.NOT_FOUND, detail="DCA client not found."
)
updated_client = await update_dca_client(client_id, data)
if not updated_client:
raise HTTPException(
status_code=HTTPStatus.INTERNAL_SERVER_ERROR, detail="Failed to update client."
)
return updated_client
@myextension_api_router.delete("/api/v1/dca/clients/{client_id}")
async def api_delete_dca_client(
client_id: str,
wallet: WalletTypeInfo = Depends(require_admin_key),
):
"""Delete a DCA client"""
client = await get_dca_client(client_id)
if not client:
raise HTTPException(
status_code=HTTPStatus.NOT_FOUND, detail="DCA client not found."
)
await delete_dca_client(client_id)
return {"message": "Client deleted successfully"}
@myextension_api_router.get("/api/v1/dca/clients/{client_id}/balance")
async def api_get_client_balance(
client_id: str,
wallet: WalletTypeInfo = Depends(require_invoice_key),
) -> ClientBalanceSummary:
"""Get client balance summary"""
client = await get_dca_client(client_id)
if not client:
raise HTTPException(
status_code=HTTPStatus.NOT_FOUND, detail="DCA client not found."
)
return await get_client_balance_summary(client_id)
# DCA Deposit Endpoints
@myextension_api_router.get("/api/v1/dca/deposits")
async def api_get_deposits(
wallet: WalletTypeInfo = Depends(require_invoice_key),
) -> list[DcaDeposit]:
"""Get all deposits"""
return await get_all_deposits()
@myextension_api_router.get("/api/v1/dca/deposits/{deposit_id}")
async def api_get_deposit(
deposit_id: str,
wallet: WalletTypeInfo = Depends(require_invoice_key),
) -> DcaDeposit:
"""Get a specific deposit"""
deposit = await get_deposit(deposit_id)
if not deposit:
raise HTTPException(
status_code=HTTPStatus.NOT_FOUND, detail="Deposit not found."
)
return deposit
@myextension_api_router.post("/api/v1/dca/deposits", status_code=HTTPStatus.CREATED)
async def api_create_deposit(
data: CreateDepositData,
wallet: WalletTypeInfo = Depends(require_admin_key),
) -> DcaDeposit:
"""Create a new deposit"""
# Verify client exists
client = await get_dca_client(data.client_id)
if not client:
raise HTTPException(
status_code=HTTPStatus.NOT_FOUND, detail="DCA client not found."
)
return await create_deposit(data)
@myextension_api_router.put("/api/v1/dca/deposits/{deposit_id}/status")
async def api_update_deposit_status(
deposit_id: str,
data: UpdateDepositStatusData,
wallet: WalletTypeInfo = Depends(require_admin_key),
) -> DcaDeposit:
"""Update deposit status (e.g., confirm deposit)"""
deposit = await get_deposit(deposit_id)
if not deposit:
raise HTTPException(
status_code=HTTPStatus.NOT_FOUND, detail="Deposit not found."
)
updated_deposit = await update_deposit_status(deposit_id, data)
if not updated_deposit:
raise HTTPException(
status_code=HTTPStatus.INTERNAL_SERVER_ERROR, detail="Failed to update deposit."
)
return updated_deposit