From 6db94179ccba64d584f64ee2e41f81b94d6c2951 Mon Sep 17 00:00:00 2001 From: padreug Date: Fri, 20 Jun 2025 22:16:58 +0200 Subject: [PATCH] Replace [mM]yextension with [sS]at[mM]achine[aA]dmin --- CLAUDE.md | 6 +- README.md | 2 +- __init__.py | 34 ++++---- config.json | 6 +- crud.py | 74 +++++++++--------- description.md | 2 +- manifest.json | 4 +- migrations.py | 10 +-- pyproject.toml | 2 +- replaceStuff.sh | 36 +++++++++ static/image/1.png | Bin 13110 -> 0 bytes static/image/2.png | Bin 13110 -> 0 bytes static/image/3.png | Bin 13110 -> 0 bytes static/image/{myextension.png => aio.png} | Bin static/js/index.js | 32 ++++---- tasks.py | 2 +- .../_api_docs.html | 2 +- .../index.html | 4 +- tests/test_init.py | 4 +- views.py | 12 +-- views_api.py | 38 ++++----- 21 files changed, 153 insertions(+), 117 deletions(-) create mode 100755 replaceStuff.sh delete mode 100644 static/image/1.png delete mode 100644 static/image/2.png delete mode 100644 static/image/3.png rename static/image/{myextension.png => aio.png} (100%) rename templates/{myextension => satmachineadmin}/_api_docs.html (61%) rename templates/{myextension => satmachineadmin}/index.html (99%) diff --git a/CLAUDE.md b/CLAUDE.md index 3762810..616c522 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -4,7 +4,7 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co ## Project Overview -This is an LNBits extension called "satoshimachine" (currently using template name "MyExtension"). LNBits extensions are modular add-ons that extend the functionality of the LNBits Lightning Network wallet platform. +This is an LNBits extension called "satoshimachine" (currently using template name "SatMachineAdmin"). LNBits extensions are modular add-ons that extend the functionality of the LNBits Lightning Network wallet platform. ## Development Commands @@ -99,7 +99,7 @@ The global `this.g` object provides access to: ## Important Notes -- This extension is currently a template with placeholder "MyExtension" naming +- This extension is currently a template with placeholder "SatMachineAdmin" naming - The actual functionality appears to be related to "satoshimachine" based on directory structure - The `tmp/` directory contains a more developed version with DCA (Dollar Cost Averaging) functionality - Extensions must follow snake_case naming conventions for Python files @@ -217,4 +217,4 @@ sudo chmod 600 /var/lib/postgresql/.ssh/authorized_keys - `crud.py` - Database operations including poll tracking - `migrations.py` - Schema evolution (m001-m009) - `static/js/index.js` - Admin interface JavaScript -- `templates/myextension/index.html` - Admin UI templates \ No newline at end of file +- `templates/satmachineadmin/index.html` - Admin UI templates \ No newline at end of file diff --git a/README.md b/README.md index 0d9ad0e..725355a 100644 --- a/README.md +++ b/README.md @@ -188,7 +188,7 @@ The extension creates several tables: ├── config.json # Extension configuration ├── manifest.json # Extension manifest ├── templates/ -│ └── myextension/ +│ └── satmachineadmin/ │ └── index.html # Main UI template └── static/ └── js/ diff --git a/__init__.py b/__init__.py index 50f9d52..8c16954 100644 --- a/__init__.py +++ b/__init__.py @@ -6,30 +6,30 @@ from loguru import logger from .crud import db from .tasks import wait_for_paid_invoices, hourly_transaction_polling -from .views import myextension_generic_router -from .views_api import myextension_api_router +from .views import satmachineadmin_generic_router +from .views_api import satmachineadmin_api_router logger.debug( - "This logged message is from myextension/__init__.py, you can debug in your " + "This logged message is from satmachineadmin/__init__.py, you can debug in your " "extension using 'import logger from loguru' and 'logger.debug()'." ) -myextension_ext: APIRouter = APIRouter(prefix="/myextension", tags=["DCA Admin"]) -myextension_ext.include_router(myextension_generic_router) -myextension_ext.include_router(myextension_api_router) +satmachineadmin_ext: APIRouter = APIRouter(prefix="/satmachineadmin", tags=["DCA Admin"]) +satmachineadmin_ext.include_router(satmachineadmin_generic_router) +satmachineadmin_ext.include_router(satmachineadmin_api_router) -myextension_static_files = [ +satmachineadmin_static_files = [ { - "path": "/myextension/static", - "name": "myextension_static", + "path": "/satmachineadmin/static", + "name": "satmachineadmin_static", } ] scheduled_tasks: list[asyncio.Task] = [] -def myextension_stop(): +def satmachineadmin_stop(): for task in scheduled_tasks: try: task.cancel() @@ -37,20 +37,20 @@ def myextension_stop(): logger.warning(ex) -def myextension_start(): +def satmachineadmin_start(): # Start invoice listener task - invoice_task = create_permanent_unique_task("ext_myextension", wait_for_paid_invoices) + invoice_task = create_permanent_unique_task("ext_satmachineadmin", wait_for_paid_invoices) scheduled_tasks.append(invoice_task) # Start hourly transaction polling task - polling_task = create_permanent_unique_task("ext_myextension_polling", hourly_transaction_polling) + polling_task = create_permanent_unique_task("ext_satmachineadmin_polling", hourly_transaction_polling) scheduled_tasks.append(polling_task) __all__ = [ "db", - "myextension_ext", - "myextension_static_files", - "myextension_start", - "myextension_stop", + "satmachineadmin_ext", + "satmachineadmin_static_files", + "satmachineadmin_start", + "satmachineadmin_stop", ] diff --git a/config.json b/config.json index 6be901a..8c1f39e 100644 --- a/config.json +++ b/config.json @@ -1,7 +1,7 @@ { "name": "DCA Admin", "short_description": "Dollar Cost Averaging administration for Lamassu ATM integration", - "tile": "/myextension/static/image/myextension.png", + "tile": "/satmachineadmin/static/image/aio.png", "min_lnbits_version": "1.0.0", "contributors": [ { @@ -26,7 +26,7 @@ } ], "images": [], - "description_md": "/myextension/description.md", - "terms_and_conditions_md": "/myextension/toc.md", + "description_md": "/satmachineadmin/description.md", + "terms_and_conditions_md": "/satmachineadmin/toc.md", "license": "MIT" } diff --git a/crud.py b/crud.py index 2cf20e0..27781a6 100644 --- a/crud.py +++ b/crud.py @@ -15,7 +15,7 @@ from .models import ( CreateLamassuTransactionData, StoredLamassuTransaction ) -db = Database("ext_myextension") +db = Database("ext_satmachineadmin") # DCA Client CRUD Operations @@ -23,7 +23,7 @@ async def create_dca_client(data: CreateDcaClientData) -> DcaClient: client_id = urlsafe_short_hash() await db.execute( """ - INSERT INTO myextension.dca_clients + INSERT INTO satmachineadmin.dca_clients (id, user_id, wallet_id, username, dca_mode, fixed_mode_daily_limit, status, created_at, updated_at) VALUES (:id, :user_id, :wallet_id, :username, :dca_mode, :fixed_mode_daily_limit, :status, :created_at, :updated_at) """, @@ -44,7 +44,7 @@ async def create_dca_client(data: CreateDcaClientData) -> DcaClient: async def get_dca_client(client_id: str) -> Optional[DcaClient]: return await db.fetchone( - "SELECT * FROM myextension.dca_clients WHERE id = :id", + "SELECT * FROM satmachineadmin.dca_clients WHERE id = :id", {"id": client_id}, DcaClient, ) @@ -52,14 +52,14 @@ async def get_dca_client(client_id: str) -> Optional[DcaClient]: async def get_dca_clients() -> List[DcaClient]: return await db.fetchall( - "SELECT * FROM myextension.dca_clients ORDER BY created_at DESC", + "SELECT * FROM satmachineadmin.dca_clients ORDER BY created_at DESC", model=DcaClient, ) async def get_dca_client_by_user(user_id: str) -> Optional[DcaClient]: return await db.fetchone( - "SELECT * FROM myextension.dca_clients WHERE user_id = :user_id", + "SELECT * FROM satmachineadmin.dca_clients WHERE user_id = :user_id", {"user_id": user_id}, DcaClient, ) @@ -75,7 +75,7 @@ async def update_dca_client(client_id: str, data: UpdateDcaClientData) -> Option update_data["id"] = client_id await db.execute( - f"UPDATE myextension.dca_clients SET {set_clause} WHERE id = :id", + f"UPDATE satmachineadmin.dca_clients SET {set_clause} WHERE id = :id", update_data ) return await get_dca_client(client_id) @@ -83,7 +83,7 @@ async def update_dca_client(client_id: str, data: UpdateDcaClientData) -> Option async def delete_dca_client(client_id: str) -> None: await db.execute( - "DELETE FROM myextension.dca_clients WHERE id = :id", + "DELETE FROM satmachineadmin.dca_clients WHERE id = :id", {"id": client_id} ) @@ -93,7 +93,7 @@ async def create_deposit(data: CreateDepositData) -> DcaDeposit: deposit_id = urlsafe_short_hash() await db.execute( """ - INSERT INTO myextension.dca_deposits + INSERT INTO satmachineadmin.dca_deposits (id, client_id, amount, currency, status, notes, created_at) VALUES (:id, :client_id, :amount, :currency, :status, :notes, :created_at) """, @@ -112,7 +112,7 @@ async def create_deposit(data: CreateDepositData) -> DcaDeposit: async def get_deposit(deposit_id: str) -> Optional[DcaDeposit]: return await db.fetchone( - "SELECT * FROM myextension.dca_deposits WHERE id = :id", + "SELECT * FROM satmachineadmin.dca_deposits WHERE id = :id", {"id": deposit_id}, DcaDeposit, ) @@ -120,7 +120,7 @@ async def get_deposit(deposit_id: str) -> Optional[DcaDeposit]: async def get_deposits_by_client(client_id: str) -> List[DcaDeposit]: return await db.fetchall( - "SELECT * FROM myextension.dca_deposits WHERE client_id = :client_id ORDER BY created_at DESC", + "SELECT * FROM satmachineadmin.dca_deposits WHERE client_id = :client_id ORDER BY created_at DESC", {"client_id": client_id}, DcaDeposit, ) @@ -128,7 +128,7 @@ async def get_deposits_by_client(client_id: str) -> List[DcaDeposit]: async def get_all_deposits() -> List[DcaDeposit]: return await db.fetchall( - "SELECT * FROM myextension.dca_deposits ORDER BY created_at DESC", + "SELECT * FROM satmachineadmin.dca_deposits ORDER BY created_at DESC", model=DcaDeposit, ) @@ -147,7 +147,7 @@ async def update_deposit_status(deposit_id: str, data: UpdateDepositStatusData) filtered_data["id"] = deposit_id await db.execute( - f"UPDATE myextension.dca_deposits SET {set_clause} WHERE id = :id", + f"UPDATE satmachineadmin.dca_deposits SET {set_clause} WHERE id = :id", filtered_data ) return await get_deposit(deposit_id) @@ -158,7 +158,7 @@ async def create_dca_payment(data: CreateDcaPaymentData) -> DcaPayment: payment_id = urlsafe_short_hash() await db.execute( """ - INSERT INTO myextension.dca_payments + INSERT INTO satmachineadmin.dca_payments (id, client_id, amount_sats, amount_fiat, exchange_rate, transaction_type, lamassu_transaction_id, payment_hash, status, created_at) VALUES (:id, :client_id, :amount_sats, :amount_fiat, :exchange_rate, :transaction_type, @@ -182,7 +182,7 @@ async def create_dca_payment(data: CreateDcaPaymentData) -> DcaPayment: async def get_dca_payment(payment_id: str) -> Optional[DcaPayment]: return await db.fetchone( - "SELECT * FROM myextension.dca_payments WHERE id = :id", + "SELECT * FROM satmachineadmin.dca_payments WHERE id = :id", {"id": payment_id}, DcaPayment, ) @@ -190,7 +190,7 @@ async def get_dca_payment(payment_id: str) -> Optional[DcaPayment]: async def get_payments_by_client(client_id: str) -> List[DcaPayment]: return await db.fetchall( - "SELECT * FROM myextension.dca_payments WHERE client_id = :client_id ORDER BY created_at DESC", + "SELECT * FROM satmachineadmin.dca_payments WHERE client_id = :client_id ORDER BY created_at DESC", {"client_id": client_id}, DcaPayment, ) @@ -198,7 +198,7 @@ async def get_payments_by_client(client_id: str) -> List[DcaPayment]: async def get_all_payments() -> List[DcaPayment]: return await db.fetchall( - "SELECT * FROM myextension.dca_payments ORDER BY created_at DESC", + "SELECT * FROM satmachineadmin.dca_payments ORDER BY created_at DESC", model=DcaPayment, ) @@ -206,14 +206,14 @@ async def get_all_payments() -> List[DcaPayment]: async def update_dca_payment_status(payment_id: str, status: str) -> None: """Update the status of a DCA payment""" await db.execute( - "UPDATE myextension.dca_payments SET status = :status WHERE id = :id", + "UPDATE satmachineadmin.dca_payments SET status = :status WHERE id = :id", {"status": status, "id": payment_id} ) async def get_payments_by_lamassu_transaction(lamassu_transaction_id: str) -> List[DcaPayment]: return await db.fetchall( - "SELECT * FROM myextension.dca_payments WHERE lamassu_transaction_id = :transaction_id", + "SELECT * FROM satmachineadmin.dca_payments WHERE lamassu_transaction_id = :transaction_id", {"transaction_id": lamassu_transaction_id}, DcaPayment, ) @@ -225,7 +225,7 @@ async def get_client_balance_summary(client_id: str) -> ClientBalanceSummary: total_deposits_result = await db.fetchone( """ SELECT COALESCE(SUM(amount), 0) as total, currency - FROM myextension.dca_deposits + FROM satmachineadmin.dca_deposits WHERE client_id = :client_id AND status = 'confirmed' GROUP BY currency """, @@ -236,7 +236,7 @@ async def get_client_balance_summary(client_id: str) -> ClientBalanceSummary: total_payments_result = await db.fetchone( """ SELECT COALESCE(SUM(amount_fiat), 0) as total - FROM myextension.dca_payments + FROM satmachineadmin.dca_payments WHERE client_id = :client_id AND status = 'confirmed' """, {"client_id": client_id} @@ -257,14 +257,14 @@ async def get_client_balance_summary(client_id: str) -> ClientBalanceSummary: async def get_flow_mode_clients() -> List[DcaClient]: return await db.fetchall( - "SELECT * FROM myextension.dca_clients WHERE dca_mode = 'flow' AND status = 'active'", + "SELECT * FROM satmachineadmin.dca_clients WHERE dca_mode = 'flow' AND status = 'active'", model=DcaClient, ) async def get_fixed_mode_clients() -> List[DcaClient]: return await db.fetchall( - "SELECT * FROM myextension.dca_clients WHERE dca_mode = 'fixed' AND status = 'active'", + "SELECT * FROM satmachineadmin.dca_clients WHERE dca_mode = 'fixed' AND status = 'active'", model=DcaClient, ) @@ -275,13 +275,13 @@ async def create_lamassu_config(data: CreateLamassuConfigData) -> LamassuConfig: # Deactivate any existing configs first (only one active config allowed) await db.execute( - "UPDATE myextension.lamassu_config SET is_active = false, updated_at = :updated_at", + "UPDATE satmachineadmin.lamassu_config SET is_active = false, updated_at = :updated_at", {"updated_at": datetime.now()} ) await db.execute( """ - INSERT INTO myextension.lamassu_config + INSERT INTO satmachineadmin.lamassu_config (id, host, port, database_name, username, password, source_wallet_id, commission_wallet_id, is_active, created_at, updated_at, use_ssh_tunnel, ssh_host, ssh_port, ssh_username, ssh_password, ssh_private_key) VALUES (:id, :host, :port, :database_name, :username, :password, :source_wallet_id, :commission_wallet_id, :is_active, :created_at, :updated_at, @@ -312,7 +312,7 @@ async def create_lamassu_config(data: CreateLamassuConfigData) -> LamassuConfig: async def get_lamassu_config(config_id: str) -> Optional[LamassuConfig]: return await db.fetchone( - "SELECT * FROM myextension.lamassu_config WHERE id = :id", + "SELECT * FROM satmachineadmin.lamassu_config WHERE id = :id", {"id": config_id}, LamassuConfig, ) @@ -320,14 +320,14 @@ async def get_lamassu_config(config_id: str) -> Optional[LamassuConfig]: async def get_active_lamassu_config() -> Optional[LamassuConfig]: return await db.fetchone( - "SELECT * FROM myextension.lamassu_config WHERE is_active = true ORDER BY created_at DESC LIMIT 1", + "SELECT * FROM satmachineadmin.lamassu_config WHERE is_active = true ORDER BY created_at DESC LIMIT 1", model=LamassuConfig, ) async def get_all_lamassu_configs() -> List[LamassuConfig]: return await db.fetchall( - "SELECT * FROM myextension.lamassu_config ORDER BY created_at DESC", + "SELECT * FROM satmachineadmin.lamassu_config ORDER BY created_at DESC", model=LamassuConfig, ) @@ -342,7 +342,7 @@ async def update_lamassu_config(config_id: str, data: UpdateLamassuConfigData) - update_data["id"] = config_id await db.execute( - f"UPDATE myextension.lamassu_config SET {set_clause} WHERE id = :id", + f"UPDATE satmachineadmin.lamassu_config SET {set_clause} WHERE id = :id", update_data ) return await get_lamassu_config(config_id) @@ -352,7 +352,7 @@ async def update_config_test_result(config_id: str, success: bool) -> None: utc_now = datetime.now(timezone.utc) await db.execute( """ - UPDATE myextension.lamassu_config + UPDATE satmachineadmin.lamassu_config SET test_connection_last = :test_time, test_connection_success = :success, updated_at = :updated_at WHERE id = :id """, @@ -367,7 +367,7 @@ async def update_config_test_result(config_id: str, success: bool) -> None: async def delete_lamassu_config(config_id: str) -> None: await db.execute( - "DELETE FROM myextension.lamassu_config WHERE id = :id", + "DELETE FROM satmachineadmin.lamassu_config WHERE id = :id", {"id": config_id} ) @@ -377,7 +377,7 @@ async def update_poll_start_time(config_id: str) -> None: utc_now = datetime.now(timezone.utc) await db.execute( """ - UPDATE myextension.lamassu_config + UPDATE satmachineadmin.lamassu_config SET last_poll_time = :poll_time, updated_at = :updated_at WHERE id = :id """, @@ -394,7 +394,7 @@ async def update_poll_success_time(config_id: str) -> None: utc_now = datetime.now(timezone.utc) await db.execute( """ - UPDATE myextension.lamassu_config + UPDATE satmachineadmin.lamassu_config SET last_successful_poll = :poll_time, updated_at = :updated_at WHERE id = :id """, @@ -412,7 +412,7 @@ async def create_lamassu_transaction(data: CreateLamassuTransactionData) -> Stor transaction_id = urlsafe_short_hash() await db.execute( """ - INSERT INTO myextension.lamassu_transactions + INSERT INTO satmachineadmin.lamassu_transactions (id, lamassu_transaction_id, fiat_amount, crypto_amount, commission_percentage, discount, effective_commission, commission_amount_sats, base_amount_sats, exchange_rate, crypto_code, fiat_code, device_id, transaction_time, processed_at, @@ -448,7 +448,7 @@ async def create_lamassu_transaction(data: CreateLamassuTransactionData) -> Stor async def get_lamassu_transaction(transaction_id: str) -> Optional[StoredLamassuTransaction]: """Get a stored Lamassu transaction by ID""" return await db.fetchone( - "SELECT * FROM myextension.lamassu_transactions WHERE id = :id", + "SELECT * FROM satmachineadmin.lamassu_transactions WHERE id = :id", {"id": transaction_id}, StoredLamassuTransaction, ) @@ -457,7 +457,7 @@ async def get_lamassu_transaction(transaction_id: str) -> Optional[StoredLamassu async def get_lamassu_transaction_by_lamassu_id(lamassu_transaction_id: str) -> Optional[StoredLamassuTransaction]: """Get a stored Lamassu transaction by Lamassu transaction ID""" return await db.fetchone( - "SELECT * FROM myextension.lamassu_transactions WHERE lamassu_transaction_id = :lamassu_id", + "SELECT * FROM satmachineadmin.lamassu_transactions WHERE lamassu_transaction_id = :lamassu_id", {"lamassu_id": lamassu_transaction_id}, StoredLamassuTransaction, ) @@ -466,7 +466,7 @@ async def get_lamassu_transaction_by_lamassu_id(lamassu_transaction_id: str) -> async def get_all_lamassu_transactions() -> List[StoredLamassuTransaction]: """Get all stored Lamassu transactions""" return await db.fetchall( - "SELECT * FROM myextension.lamassu_transactions ORDER BY transaction_time DESC", + "SELECT * FROM satmachineadmin.lamassu_transactions ORDER BY transaction_time DESC", model=StoredLamassuTransaction, ) @@ -479,7 +479,7 @@ async def update_lamassu_transaction_distribution_stats( """Update distribution statistics for a Lamassu transaction""" await db.execute( """ - UPDATE myextension.lamassu_transactions + UPDATE satmachineadmin.lamassu_transactions SET clients_count = :clients_count, distributions_total_sats = :distributions_total_sats WHERE id = :id """, diff --git a/description.md b/description.md index cdb664d..a748691 100644 --- a/description.md +++ b/description.md @@ -1,4 +1,4 @@ -MyExtension can be used as a template for building new extensions, it includes a bunch of functions that can be edited/deleted as you need them. +SatMachineAdmin can be used as a template for building new extensions, it includes a bunch of functions that can be edited/deleted as you need them. This is a longform description that will be used in the advanced description when users click on the "more" button on the extension cards. diff --git a/manifest.json b/manifest.json index 3e05b16..989cbae 100644 --- a/manifest.json +++ b/manifest.json @@ -1,9 +1,9 @@ { "repos": [ { - "id": "myextension", + "id": "satmachineadmin", "organisation": "lnbits", - "repository": "myextension" + "repository": "satmachineadmin" } ] } diff --git a/migrations.py b/migrations.py index f8812be..0e1cafa 100644 --- a/migrations.py +++ b/migrations.py @@ -10,7 +10,7 @@ async def m001_initial_dca_schema(db): # DCA Clients table await db.execute( f""" - CREATE TABLE myextension.dca_clients ( + CREATE TABLE satmachineadmin.dca_clients ( id TEXT PRIMARY KEY NOT NULL, user_id TEXT NOT NULL, wallet_id TEXT NOT NULL, @@ -27,7 +27,7 @@ async def m001_initial_dca_schema(db): # DCA Deposits table await db.execute( f""" - CREATE TABLE myextension.dca_deposits ( + CREATE TABLE satmachineadmin.dca_deposits ( id TEXT PRIMARY KEY NOT NULL, client_id TEXT NOT NULL, amount INTEGER NOT NULL, @@ -43,7 +43,7 @@ async def m001_initial_dca_schema(db): # DCA Payments table await db.execute( f""" - CREATE TABLE myextension.dca_payments ( + CREATE TABLE satmachineadmin.dca_payments ( id TEXT PRIMARY KEY NOT NULL, client_id TEXT NOT NULL, amount_sats INTEGER NOT NULL, @@ -61,7 +61,7 @@ async def m001_initial_dca_schema(db): # Lamassu Configuration table await db.execute( f""" - CREATE TABLE myextension.lamassu_config ( + CREATE TABLE satmachineadmin.lamassu_config ( id TEXT PRIMARY KEY NOT NULL, host TEXT NOT NULL, port INTEGER NOT NULL DEFAULT 5432, @@ -90,7 +90,7 @@ async def m001_initial_dca_schema(db): # Lamassu Transactions table (for audit trail) await db.execute( f""" - CREATE TABLE myextension.lamassu_transactions ( + CREATE TABLE satmachineadmin.lamassu_transactions ( id TEXT PRIMARY KEY NOT NULL, lamassu_transaction_id TEXT NOT NULL UNIQUE, fiat_amount INTEGER NOT NULL, diff --git a/pyproject.toml b/pyproject.toml index 7d7ce36..c5c417d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,5 +1,5 @@ [tool.poetry] -name = "myextension" +name = "satmachineadmin" version = "0.0.0" description = "Eightball is a simple API that allows you to create a random number generator." authors = ["benarc", "dni "] diff --git a/replaceStuff.sh b/replaceStuff.sh new file mode 100755 index 0000000..7ef780a --- /dev/null +++ b/replaceStuff.sh @@ -0,0 +1,36 @@ +#!/usr/bin/env bash + +# Usage: ./rename-plugin.sh oldname newname +# Example: ./rename-plugin.sh example mysuperplugin + +set -euo pipefail + +OLD_NAME="$1" +NEW_NAME="$2" + +# 1. Rename files with OLD_NAME in the filename +find . -type f -name "*${OLD_NAME}*" | while read -r file; do + dir=$(dirname "$file") + base=$(basename "$file") + new_base="${base//$OLD_NAME/$NEW_NAME}" + new_path="$dir/$new_base" + mv "$file" "$new_path" + echo "Renamed file: $file -> $new_path" +done + +# 2. Replace occurrences of OLD_NAME in file content +echo "Replacing content inside files..." +find . -type f -print0 | xargs -0 sed -i "s/${OLD_NAME}/${NEW_NAME}/g" + +# 3. Rename directories with OLD_NAME in the path (from deepest up) +echo "Renaming directories..." +find . -depth -type d -name "*${OLD_NAME}*" | while read -r dir; do + parent=$(dirname "$dir") + base=$(basename "$dir") + new_base="${base//$OLD_NAME/$NEW_NAME}" + new_path="$parent/$new_base" + mv "$dir" "$new_path" + echo "Renamed directory: $dir -> $new_path" +done + +echo "✅ All done." diff --git a/static/image/1.png b/static/image/1.png deleted file mode 100644 index e0a39323dc5363065111296ca3597ac93cc059f4..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 13110 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 diff --git a/static/image/2.png b/static/image/2.png deleted file mode 100644 index e0a39323dc5363065111296ca3597ac93cc059f4..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 13110 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 diff --git a/static/image/3.png b/static/image/3.png deleted file mode 100644 index e0a39323dc5363065111296ca3597ac93cc059f4..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 13110 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 diff --git a/static/image/myextension.png b/static/image/aio.png similarity index 100% rename from static/image/myextension.png rename to static/image/aio.png diff --git a/static/js/index.js b/static/js/index.js index fc9a2d5..c5d105b 100644 --- a/static/js/index.js +++ b/static/js/index.js @@ -165,7 +165,7 @@ window.app = Vue.createApp({ try { const {data} = await LNbits.api.request( 'GET', - '/myextension/api/v1/dca/config', + '/satmachineadmin/api/v1/dca/config', this.g.user.wallets[0].inkey ) this.lamassuConfig = data @@ -210,7 +210,7 @@ window.app = Vue.createApp({ const {data: config} = await LNbits.api.request( 'POST', - '/myextension/api/v1/dca/config', + '/satmachineadmin/api/v1/dca/config', this.g.user.wallets[0].adminkey, data ) @@ -253,7 +253,7 @@ window.app = Vue.createApp({ try { const { data } = await LNbits.api.request( 'GET', - '/myextension/api/v1/dca/clients', + '/satmachineadmin/api/v1/dca/clients', this.g.user.wallets[0].inkey ) @@ -263,7 +263,7 @@ window.app = Vue.createApp({ try { const { data: balance } = await LNbits.api.request( 'GET', - `/myextension/api/v1/dca/clients/${client.id}/balance`, + `/satmachineadmin/api/v1/dca/clients/${client.id}/balance`, this.g.user.wallets[0].inkey ) return { @@ -298,7 +298,7 @@ window.app = Vue.createApp({ const { data: newClient } = await LNbits.api.request( 'POST', - '/myextension/api/v1/dca/clients', + '/satmachineadmin/api/v1/dca/clients', this.g.user.wallets[0].adminkey, testData ) @@ -327,7 +327,7 @@ window.app = Vue.createApp({ const { data: newDeposit } = await LNbits.api.request( 'POST', - '/myextension/api/v1/dca/deposits', + '/satmachineadmin/api/v1/dca/deposits', this.g.user.wallets[0].adminkey, data ) @@ -355,7 +355,7 @@ window.app = Vue.createApp({ try { const { data: balance } = await LNbits.api.request( 'GET', - `/myextension/api/v1/dca/clients/${client.id}/balance`, + `/satmachineadmin/api/v1/dca/clients/${client.id}/balance`, this.g.user.wallets[0].inkey ) this.clientDetailsDialog.data = client @@ -371,7 +371,7 @@ window.app = Vue.createApp({ try { const { data } = await LNbits.api.request( 'GET', - '/myextension/api/v1/dca/deposits', + '/satmachineadmin/api/v1/dca/deposits', this.g.user.wallets[0].inkey ) this.deposits = data @@ -402,7 +402,7 @@ window.app = Vue.createApp({ // Update existing deposit (mainly for notes/status) const { data: updatedDeposit } = await LNbits.api.request( 'PUT', - `/myextension/api/v1/dca/deposits/${this.depositFormDialog.data.id}`, + `/satmachineadmin/api/v1/dca/deposits/${this.depositFormDialog.data.id}`, this.g.user.wallets[0].adminkey, { status: this.depositFormDialog.data.status, notes: data.notes } ) @@ -414,7 +414,7 @@ window.app = Vue.createApp({ // Create new deposit const { data: newDeposit } = await LNbits.api.request( 'POST', - '/myextension/api/v1/dca/deposits', + '/satmachineadmin/api/v1/dca/deposits', this.g.user.wallets[0].adminkey, data ) @@ -446,7 +446,7 @@ window.app = Vue.createApp({ .onOk(async () => { const { data: updatedDeposit } = await LNbits.api.request( 'PUT', - `/myextension/api/v1/dca/deposits/${deposit.id}/status`, + `/satmachineadmin/api/v1/dca/deposits/${deposit.id}/status`, this.g.user.wallets[0].adminkey, { status: 'confirmed', notes: 'Confirmed by admin - money placed in machine' } ) @@ -489,7 +489,7 @@ window.app = Vue.createApp({ try { const {data} = await LNbits.api.request( 'POST', - '/myextension/api/v1/dca/test-connection', + '/satmachineadmin/api/v1/dca/test-connection', this.g.user.wallets[0].adminkey ) @@ -535,7 +535,7 @@ window.app = Vue.createApp({ try { const {data} = await LNbits.api.request( 'POST', - '/myextension/api/v1/dca/manual-poll', + '/satmachineadmin/api/v1/dca/manual-poll', this.g.user.wallets[0].adminkey ) @@ -563,7 +563,7 @@ window.app = Vue.createApp({ try { const {data} = await LNbits.api.request( 'POST', - '/myextension/api/v1/dca/test-transaction', + '/satmachineadmin/api/v1/dca/test-transaction', this.g.user.wallets[0].adminkey ) @@ -616,7 +616,7 @@ window.app = Vue.createApp({ try { const { data } = await LNbits.api.request( 'GET', - '/myextension/api/v1/dca/transactions', + '/satmachineadmin/api/v1/dca/transactions', this.g.user.wallets[0].inkey ) this.lamassuTransactions = data @@ -629,7 +629,7 @@ window.app = Vue.createApp({ try { const { data: distributions } = await LNbits.api.request( 'GET', - `/myextension/api/v1/dca/transactions/${transaction.id}/distributions`, + `/satmachineadmin/api/v1/dca/transactions/${transaction.id}/distributions`, this.g.user.wallets[0].inkey ) diff --git a/tasks.py b/tasks.py index f653c3f..0ed9efe 100644 --- a/tasks.py +++ b/tasks.py @@ -18,7 +18,7 @@ from .transaction_processor import poll_lamassu_transactions async def wait_for_paid_invoices(): """Invoice listener for DCA-related payments""" invoice_queue = asyncio.Queue() - register_invoice_listener(invoice_queue, "ext_myextension") + register_invoice_listener(invoice_queue, "ext_satmachineadmin") while True: payment = await invoice_queue.get() await on_invoice_paid(payment) diff --git a/templates/myextension/_api_docs.html b/templates/satmachineadmin/_api_docs.html similarity index 61% rename from templates/myextension/_api_docs.html rename to templates/satmachineadmin/_api_docs.html index 70d1fd4..e9c9d17 100644 --- a/templates/myextension/_api_docs.html +++ b/templates/satmachineadmin/_api_docs.html @@ -4,5 +4,5 @@ label="API info" :content-inset-level="0.5" > - + diff --git a/templates/myextension/index.html b/templates/satmachineadmin/index.html similarity index 99% rename from templates/myextension/index.html rename to templates/satmachineadmin/index.html index 389f60a..297407b 100644 --- a/templates/myextension/index.html +++ b/templates/satmachineadmin/index.html @@ -4,7 +4,7 @@ {% extends "base.html" %} {% from "macros.jinja" import window_vars with context %} {% block scripts %} {{ window_vars(user) }} - + {% endblock %} {% block page %}
@@ -367,7 +367,7 @@ - {% include "myextension/_api_docs.html" %} + {% include "satmachineadmin/_api_docs.html" %} diff --git a/tests/test_init.py b/tests/test_init.py index 438fc5d..e2fc116 100644 --- a/tests/test_init.py +++ b/tests/test_init.py @@ -1,11 +1,11 @@ import pytest from fastapi import APIRouter -from .. import myextension_ext +from .. import satmachineadmin_ext # just import router and add it to a test router @pytest.mark.asyncio async def test_router(): router = APIRouter() - router.include_router(myextension_ext) + router.include_router(satmachineadmin_ext) diff --git a/views.py b/views.py index e6cf852..28b93c9 100644 --- a/views.py +++ b/views.py @@ -6,16 +6,16 @@ from lnbits.core.models import User from lnbits.decorators import check_user_exists from lnbits.helpers import template_renderer -myextension_generic_router = APIRouter() +satmachineadmin_generic_router = APIRouter() -def myextension_renderer(): - return template_renderer(["myextension/templates"]) +def satmachineadmin_renderer(): + return template_renderer(["satmachineadmin/templates"]) # DCA Admin page -@myextension_generic_router.get("/", response_class=HTMLResponse) +@satmachineadmin_generic_router.get("/", response_class=HTMLResponse) async def index(req: Request, user: User = Depends(check_user_exists)): - return myextension_renderer().TemplateResponse( - "myextension/index.html", {"request": req, "user": user.json()} + return satmachineadmin_renderer().TemplateResponse( + "satmachineadmin/index.html", {"request": req, "user": user.json()} ) diff --git a/views_api.py b/views_api.py index 7924e2d..3225239 100644 --- a/views_api.py +++ b/views_api.py @@ -43,7 +43,7 @@ from .models import ( StoredLamassuTransaction ) -myextension_api_router = APIRouter() +satmachineadmin_api_router = APIRouter() ################################################### @@ -52,7 +52,7 @@ myextension_api_router = APIRouter() # DCA Client Endpoints -@myextension_api_router.get("/api/v1/dca/clients") +@satmachineadmin_api_router.get("/api/v1/dca/clients") async def api_get_dca_clients( wallet: WalletTypeInfo = Depends(require_invoice_key), ) -> list[DcaClient]: @@ -60,7 +60,7 @@ async def api_get_dca_clients( return await get_dca_clients() -@myextension_api_router.get("/api/v1/dca/clients/{client_id}") +@satmachineadmin_api_router.get("/api/v1/dca/clients/{client_id}") async def api_get_dca_client( client_id: str, wallet: WalletTypeInfo = Depends(require_invoice_key), @@ -78,7 +78,7 @@ async def api_get_dca_client( # Admin extension only reads existing clients and manages their deposits # TEMPORARY: Test client creation endpoint (remove in production) -@myextension_api_router.post("/api/v1/dca/clients", status_code=HTTPStatus.CREATED) +@satmachineadmin_api_router.post("/api/v1/dca/clients", status_code=HTTPStatus.CREATED) async def api_create_test_dca_client( data: CreateDcaClientData, wallet: WalletTypeInfo = Depends(require_admin_key), @@ -87,7 +87,7 @@ async def api_create_test_dca_client( return await create_dca_client(data) -@myextension_api_router.get("/api/v1/dca/clients/{client_id}/balance") +@satmachineadmin_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), @@ -104,7 +104,7 @@ async def api_get_client_balance( # DCA Deposit Endpoints -@myextension_api_router.get("/api/v1/dca/deposits") +@satmachineadmin_api_router.get("/api/v1/dca/deposits") async def api_get_deposits( wallet: WalletTypeInfo = Depends(require_invoice_key), ) -> list[DcaDeposit]: @@ -112,7 +112,7 @@ async def api_get_deposits( return await get_all_deposits() -@myextension_api_router.get("/api/v1/dca/deposits/{deposit_id}") +@satmachineadmin_api_router.get("/api/v1/dca/deposits/{deposit_id}") async def api_get_deposit( deposit_id: str, wallet: WalletTypeInfo = Depends(require_invoice_key), @@ -126,7 +126,7 @@ async def api_get_deposit( return deposit -@myextension_api_router.post("/api/v1/dca/deposits", status_code=HTTPStatus.CREATED) +@satmachineadmin_api_router.post("/api/v1/dca/deposits", status_code=HTTPStatus.CREATED) async def api_create_deposit( data: CreateDepositData, wallet: WalletTypeInfo = Depends(require_admin_key), @@ -142,7 +142,7 @@ async def api_create_deposit( return await create_deposit(data) -@myextension_api_router.put("/api/v1/dca/deposits/{deposit_id}/status") +@satmachineadmin_api_router.put("/api/v1/dca/deposits/{deposit_id}/status") async def api_update_deposit_status( deposit_id: str, data: UpdateDepositStatusData, @@ -165,7 +165,7 @@ async def api_update_deposit_status( # Transaction Polling Endpoints -@myextension_api_router.post("/api/v1/dca/test-connection") +@satmachineadmin_api_router.post("/api/v1/dca/test-connection") async def api_test_database_connection( wallet: WalletTypeInfo = Depends(require_admin_key), ): @@ -188,7 +188,7 @@ async def api_test_database_connection( } -@myextension_api_router.post("/api/v1/dca/manual-poll") +@satmachineadmin_api_router.post("/api/v1/dca/manual-poll") async def api_manual_poll( wallet: WalletTypeInfo = Depends(require_admin_key), ): @@ -234,7 +234,7 @@ async def api_manual_poll( ) -@myextension_api_router.post("/api/v1/dca/test-transaction") +@satmachineadmin_api_router.post("/api/v1/dca/test-transaction") async def api_test_transaction( wallet: WalletTypeInfo = Depends(require_admin_key), crypto_atoms: int = 103, @@ -296,7 +296,7 @@ async def api_test_transaction( # Lamassu Transaction Endpoints -@myextension_api_router.get("/api/v1/dca/transactions") +@satmachineadmin_api_router.get("/api/v1/dca/transactions") async def api_get_lamassu_transactions( wallet: WalletTypeInfo = Depends(require_invoice_key), ) -> list[StoredLamassuTransaction]: @@ -304,7 +304,7 @@ async def api_get_lamassu_transactions( return await get_all_lamassu_transactions() -@myextension_api_router.get("/api/v1/dca/transactions/{transaction_id}") +@satmachineadmin_api_router.get("/api/v1/dca/transactions/{transaction_id}") async def api_get_lamassu_transaction( transaction_id: str, wallet: WalletTypeInfo = Depends(require_invoice_key), @@ -318,7 +318,7 @@ async def api_get_lamassu_transaction( return transaction -@myextension_api_router.get("/api/v1/dca/transactions/{transaction_id}/distributions") +@satmachineadmin_api_router.get("/api/v1/dca/transactions/{transaction_id}/distributions") async def api_get_transaction_distributions( transaction_id: str, wallet: WalletTypeInfo = Depends(require_invoice_key), @@ -356,7 +356,7 @@ async def api_get_transaction_distributions( # Lamassu Configuration Endpoints -@myextension_api_router.get("/api/v1/dca/config") +@satmachineadmin_api_router.get("/api/v1/dca/config") async def api_get_lamassu_config( wallet: WalletTypeInfo = Depends(require_invoice_key), ) -> Optional[LamassuConfig]: @@ -364,7 +364,7 @@ async def api_get_lamassu_config( return await get_active_lamassu_config() -@myextension_api_router.post("/api/v1/dca/config", status_code=HTTPStatus.CREATED) +@satmachineadmin_api_router.post("/api/v1/dca/config", status_code=HTTPStatus.CREATED) async def api_create_lamassu_config( data: CreateLamassuConfigData, wallet: WalletTypeInfo = Depends(require_admin_key), @@ -373,7 +373,7 @@ async def api_create_lamassu_config( return await create_lamassu_config(data) -@myextension_api_router.put("/api/v1/dca/config/{config_id}") +@satmachineadmin_api_router.put("/api/v1/dca/config/{config_id}") async def api_update_lamassu_config( config_id: str, data: UpdateLamassuConfigData, @@ -394,7 +394,7 @@ async def api_update_lamassu_config( return updated_config -@myextension_api_router.delete("/api/v1/dca/config/{config_id}") +@satmachineadmin_api_router.delete("/api/v1/dca/config/{config_id}") async def api_delete_lamassu_config( config_id: str, wallet: WalletTypeInfo = Depends(require_admin_key),