From ca75b328a1332f4752ce9670e270f25a37c36a87 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?dni=20=E2=9A=A1?= Date: Mon, 20 Feb 2023 15:34:18 +0100 Subject: [PATCH] remove invoices (#1515) * remove invoices * remove invoices tests * remove unused tests/extensions dir * fix issue * formatting * test passing locally. and i dont know why * fix _extension_dir loading * gitkeep extension dir * formatting * remove bundle from check * do not prettier compliled css * do not prettier compliled css * create extension dir, if it does not exists, just to be sure * Update lnbits/extension_manager.py --------- Co-authored-by: calle <93376500+callebtc@users.noreply.github.com> --- .gitignore | 4 +- .prettierignore | 4 +- lnbits/extension_manager.py | 18 +- .../__init__.py => lnbits/extensions/.gitkeep | 0 lnbits/extensions/invoices/README.md | 19 - lnbits/extensions/invoices/__init__.py | 36 -- lnbits/extensions/invoices/config.json | 6 - lnbits/extensions/invoices/crud.py | 210 ------- lnbits/extensions/invoices/migrations.py | 55 -- lnbits/extensions/invoices/models.py | 104 ---- lnbits/extensions/invoices/static/css/pay.css | 67 -- .../invoices/static/image/invoices.png | Bin 8773 -> 0 bytes lnbits/extensions/invoices/tasks.py | 52 -- .../templates/invoices/_api_docs.html | 153 ----- .../invoices/templates/invoices/index.html | 571 ------------------ .../invoices/templates/invoices/pay.html | 433 ------------- lnbits/extensions/invoices/views.py | 57 -- lnbits/extensions/invoices/views_api.py | 133 ---- lnbits/static/scss/base.scss | 220 ++++--- tests/core/views/__init__.py | 1 + tests/extensions/invoices/__init__.py | 0 tests/extensions/invoices/conftest.py | 36 -- .../extensions/invoices/test_invoices_api.py | 133 ---- 23 files changed, 152 insertions(+), 2160 deletions(-) rename tests/extensions/__init__.py => lnbits/extensions/.gitkeep (100%) delete mode 100644 lnbits/extensions/invoices/README.md delete mode 100644 lnbits/extensions/invoices/__init__.py delete mode 100644 lnbits/extensions/invoices/config.json delete mode 100644 lnbits/extensions/invoices/crud.py delete mode 100644 lnbits/extensions/invoices/migrations.py delete mode 100644 lnbits/extensions/invoices/models.py delete mode 100644 lnbits/extensions/invoices/static/css/pay.css delete mode 100644 lnbits/extensions/invoices/static/image/invoices.png delete mode 100644 lnbits/extensions/invoices/tasks.py delete mode 100644 lnbits/extensions/invoices/templates/invoices/_api_docs.html delete mode 100644 lnbits/extensions/invoices/templates/invoices/index.html delete mode 100644 lnbits/extensions/invoices/templates/invoices/pay.html delete mode 100644 lnbits/extensions/invoices/views.py delete mode 100644 lnbits/extensions/invoices/views_api.py delete mode 100644 tests/extensions/invoices/__init__.py delete mode 100644 tests/extensions/invoices/conftest.py delete mode 100644 tests/extensions/invoices/test_invoices_api.py diff --git a/.gitignore b/.gitignore index 79ff36abf..74da5d599 100644 --- a/.gitignore +++ b/.gitignore @@ -44,5 +44,5 @@ docker fly.toml # Ignore extensions (post installable extension PR) -extensions/ -upgrades/ \ No newline at end of file +extensions/* +upgrades/ diff --git a/.prettierignore b/.prettierignore index 776b0b093..2844476d4 100644 --- a/.prettierignore +++ b/.prettierignore @@ -5,4 +5,6 @@ *.yml -**/lnbits/static +**/lnbits/static/vendor +**/lnbits/static/bundle.* +**/lnbits/static/css/* diff --git a/lnbits/extension_manager.py b/lnbits/extension_manager.py index 7eff9480b..f9b5ce011 100644 --- a/lnbits/extension_manager.py +++ b/lnbits/extension_manager.py @@ -51,13 +51,16 @@ class Extension(NamedTuple): ) +# All subdirectories in the current directory, not recursive. + + class ExtensionManager: def __init__(self, include_disabled_exts=False): self._disabled: List[str] = settings.lnbits_disabled_extensions self._admin_only: List[str] = settings.lnbits_admin_extensions - self._extension_folders: List[str] = [ - x[1] for x in os.walk(os.path.join(settings.lnbits_path, "extensions")) - ][0] + p = Path(settings.lnbits_path, "extensions") + os.makedirs(p, exist_ok=True) + self._extension_folders: List[Path] = [f for f in p.iterdir() if f.is_dir()] @property def extensions(self) -> List[Extension]: @@ -70,11 +73,7 @@ class ExtensionManager: ext for ext in self._extension_folders if ext not in self._disabled ]: try: - with open( - os.path.join( - settings.lnbits_path, "extensions", extension, "config.json" - ) - ) as json_file: + with open(extension / "config.json") as json_file: config = json.load(json_file) is_valid = True is_admin_only = True if extension in self._admin_only else False @@ -83,9 +82,10 @@ class ExtensionManager: is_valid = False is_admin_only = False + *_, extension_code = extension.parts output.append( Extension( - extension, + extension_code, is_valid, is_admin_only, config.get("name"), diff --git a/tests/extensions/__init__.py b/lnbits/extensions/.gitkeep similarity index 100% rename from tests/extensions/__init__.py rename to lnbits/extensions/.gitkeep diff --git a/lnbits/extensions/invoices/README.md b/lnbits/extensions/invoices/README.md deleted file mode 100644 index cf3e8be02..000000000 --- a/lnbits/extensions/invoices/README.md +++ /dev/null @@ -1,19 +0,0 @@ -# Invoices - -## Create invoices that you can send to your client to pay online over Lightning. - -This extension allows users to create "traditional" invoices (not in the lightning sense) that contain one or more line items. Line items are denominated in a user-configurable fiat currency. Each invoice contains one or more payments up to the total of the invoice. Each invoice creates a public link that can be shared with a customer that they can use to (partially or in full) pay the invoice. - -## Usage - -1. Create an invoice by clicking "NEW INVOICE"\ - ![create new invoice](https://imgur.com/a/Dce3wrr.png) -2. Fill the options for your INVOICE - - select the wallet - - select the fiat currency the invoice will be denominated in - - select a status for the invoice (default is draft) - - enter a company name, first name, last name, email, phone & address (optional) - - add one or more line items - - enter a name & price for each line item -3. You can then use share your invoice link with your customer to receive payment\ - ![invoice link](https://imgur.com/a/L0JOj4T.png) diff --git a/lnbits/extensions/invoices/__init__.py b/lnbits/extensions/invoices/__init__.py deleted file mode 100644 index 735e95d81..000000000 --- a/lnbits/extensions/invoices/__init__.py +++ /dev/null @@ -1,36 +0,0 @@ -import asyncio - -from fastapi import APIRouter -from starlette.staticfiles import StaticFiles - -from lnbits.db import Database -from lnbits.helpers import template_renderer -from lnbits.tasks import catch_everything_and_restart - -db = Database("ext_invoices") - -invoices_static_files = [ - { - "path": "/invoices/static", - "app": StaticFiles(directory="lnbits/extensions/invoices/static"), - "name": "invoices_static", - } -] - -invoices_ext: APIRouter = APIRouter(prefix="/invoices", tags=["invoices"]) - - -def invoices_renderer(): - return template_renderer(["lnbits/extensions/invoices/templates"]) - - -from .tasks import wait_for_paid_invoices - - -def invoices_start(): - loop = asyncio.get_event_loop() - loop.create_task(catch_everything_and_restart(wait_for_paid_invoices)) - - -from .views import * # noqa: F401,F403 -from .views_api import * # noqa: F401,F403 diff --git a/lnbits/extensions/invoices/config.json b/lnbits/extensions/invoices/config.json deleted file mode 100644 index 1f0e4cded..000000000 --- a/lnbits/extensions/invoices/config.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "name": "Invoices", - "short_description": "Create invoices for your clients.", - "tile": "/invoices/static/image/invoices.png", - "contributors": ["leesalminen"] -} diff --git a/lnbits/extensions/invoices/crud.py b/lnbits/extensions/invoices/crud.py deleted file mode 100644 index 396528025..000000000 --- a/lnbits/extensions/invoices/crud.py +++ /dev/null @@ -1,210 +0,0 @@ -from typing import List, Optional, Union - -from lnbits.helpers import urlsafe_short_hash - -from . import db -from .models import ( - CreateInvoiceData, - CreateInvoiceItemData, - Invoice, - InvoiceItem, - Payment, - UpdateInvoiceData, - UpdateInvoiceItemData, -) - - -async def get_invoice(invoice_id: str) -> Optional[Invoice]: - row = await db.fetchone( - "SELECT * FROM invoices.invoices WHERE id = ?", (invoice_id,) - ) - return Invoice.from_row(row) if row else None - - -async def get_invoice_items(invoice_id: str) -> List[InvoiceItem]: - rows = await db.fetchall( - "SELECT * FROM invoices.invoice_items WHERE invoice_id = ?", (invoice_id,) - ) - - return [InvoiceItem.from_row(row) for row in rows] - - -async def get_invoice_item(item_id: str) -> Optional[InvoiceItem]: - row = await db.fetchone( - "SELECT * FROM invoices.invoice_items WHERE id = ?", (item_id,) - ) - return InvoiceItem.from_row(row) if row else None - - -async def get_invoice_total(items: List[InvoiceItem]) -> int: - return sum(item.amount for item in items) - - -async def get_invoices(wallet_ids: Union[str, List[str]]) -> List[Invoice]: - if isinstance(wallet_ids, str): - wallet_ids = [wallet_ids] - - q = ",".join(["?"] * len(wallet_ids)) - rows = await db.fetchall( - f"SELECT * FROM invoices.invoices WHERE wallet IN ({q})", (*wallet_ids,) - ) - - return [Invoice.from_row(row) for row in rows] - - -async def get_invoice_payments(invoice_id: str) -> List[Payment]: - rows = await db.fetchall( - "SELECT * FROM invoices.payments WHERE invoice_id = ?", (invoice_id,) - ) - - return [Payment.from_row(row) for row in rows] - - -async def get_invoice_payment(payment_id: str) -> Optional[Payment]: - row = await db.fetchone( - "SELECT * FROM invoices.payments WHERE id = ?", (payment_id,) - ) - return Payment.from_row(row) if row else None - - -async def get_payments_total(payments: List[Payment]) -> int: - return sum(item.amount for item in payments) - - -async def create_invoice_internal(wallet_id: str, data: CreateInvoiceData) -> Invoice: - invoice_id = urlsafe_short_hash() - await db.execute( - """ - INSERT INTO invoices.invoices (id, wallet, status, currency, company_name, first_name, last_name, email, phone, address) - VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?) - """, - ( - invoice_id, - wallet_id, - data.status, - data.currency, - data.company_name, - data.first_name, - data.last_name, - data.email, - data.phone, - data.address, - ), - ) - - invoice = await get_invoice(invoice_id) - assert invoice, "Newly created invoice couldn't be retrieved" - return invoice - - -async def create_invoice_items( - invoice_id: str, data: List[CreateInvoiceItemData] -) -> List[InvoiceItem]: - for item in data: - item_id = urlsafe_short_hash() - await db.execute( - """ - INSERT INTO invoices.invoice_items (id, invoice_id, description, amount) - VALUES (?, ?, ?, ?) - """, - ( - item_id, - invoice_id, - item.description, - int(item.amount * 100), - ), - ) - - invoice_items = await get_invoice_items(invoice_id) - return invoice_items - - -async def update_invoice_internal( - wallet_id: str, data: Union[UpdateInvoiceData, Invoice] -) -> Invoice: - await db.execute( - """ - UPDATE invoices.invoices - SET wallet = ?, currency = ?, status = ?, company_name = ?, first_name = ?, last_name = ?, email = ?, phone = ?, address = ? - WHERE id = ? - """, - ( - wallet_id, - data.currency, - data.status, - data.company_name, - data.first_name, - data.last_name, - data.email, - data.phone, - data.address, - data.id, - ), - ) - - invoice = await get_invoice(data.id) - assert invoice, "Newly updated invoice couldn't be retrieved" - return invoice - - -async def update_invoice_items( - invoice_id: str, data: List[UpdateInvoiceItemData] -) -> List[InvoiceItem]: - updated_items = [] - for item in data: - if item.id: - updated_items.append(item.id) - await db.execute( - """ - UPDATE invoices.invoice_items - SET description = ?, amount = ? - WHERE id = ? - """, - (item.description, int(item.amount * 100), item.id), - ) - - placeholders = ",".join("?" for _ in range(len(updated_items))) - if not placeholders: - placeholders = "?" - updated_items = ["skip"] - - await db.execute( - f""" - DELETE FROM invoices.invoice_items - WHERE invoice_id = ? - AND id NOT IN ({placeholders}) - """, - ( - invoice_id, - *tuple(updated_items), - ), - ) - - for item in data: - if not item: - await create_invoice_items( - invoice_id=invoice_id, - data=[CreateInvoiceItemData(description=item.description)], - ) - - invoice_items = await get_invoice_items(invoice_id) - return invoice_items - - -async def create_invoice_payment(invoice_id: str, amount: int) -> Payment: - payment_id = urlsafe_short_hash() - await db.execute( - """ - INSERT INTO invoices.payments (id, invoice_id, amount) - VALUES (?, ?, ?) - """, - ( - payment_id, - invoice_id, - amount, - ), - ) - - payment = await get_invoice_payment(payment_id) - assert payment, "Newly created payment couldn't be retrieved" - return payment diff --git a/lnbits/extensions/invoices/migrations.py b/lnbits/extensions/invoices/migrations.py deleted file mode 100644 index 74a0fdbad..000000000 --- a/lnbits/extensions/invoices/migrations.py +++ /dev/null @@ -1,55 +0,0 @@ -async def m001_initial_invoices(db): - - # STATUS COLUMN OPTIONS: 'draft', 'open', 'paid', 'canceled' - - await db.execute( - f""" - CREATE TABLE invoices.invoices ( - id TEXT PRIMARY KEY, - wallet TEXT NOT NULL, - - status TEXT NOT NULL DEFAULT 'draft', - - currency TEXT NOT NULL, - - company_name TEXT DEFAULT NULL, - first_name TEXT DEFAULT NULL, - last_name TEXT DEFAULT NULL, - email TEXT DEFAULT NULL, - phone TEXT DEFAULT NULL, - address TEXT DEFAULT NULL, - - - time TIMESTAMP NOT NULL DEFAULT {db.timestamp_now} - ); - """ - ) - - await db.execute( - f""" - CREATE TABLE invoices.invoice_items ( - id TEXT PRIMARY KEY, - invoice_id TEXT NOT NULL, - - description TEXT NOT NULL, - amount INTEGER NOT NULL, - - FOREIGN KEY(invoice_id) REFERENCES {db.references_schema}invoices(id) - ); - """ - ) - - await db.execute( - f""" - CREATE TABLE invoices.payments ( - id TEXT PRIMARY KEY, - invoice_id TEXT NOT NULL, - - amount {db.big_int} NOT NULL, - - time TIMESTAMP NOT NULL DEFAULT {db.timestamp_now}, - - FOREIGN KEY(invoice_id) REFERENCES {db.references_schema}invoices(id) - ); - """ - ) diff --git a/lnbits/extensions/invoices/models.py b/lnbits/extensions/invoices/models.py deleted file mode 100644 index 6f0e63cb5..000000000 --- a/lnbits/extensions/invoices/models.py +++ /dev/null @@ -1,104 +0,0 @@ -from enum import Enum -from sqlite3 import Row -from typing import List, Optional - -from fastapi import Query -from pydantic import BaseModel - - -class InvoiceStatusEnum(str, Enum): - draft = "draft" - open = "open" - paid = "paid" - canceled = "canceled" - - -class CreateInvoiceItemData(BaseModel): - description: str - amount: float = Query(..., ge=0.01) - - -class CreateInvoiceData(BaseModel): - status: InvoiceStatusEnum = InvoiceStatusEnum.draft - currency: str - company_name: Optional[str] - first_name: Optional[str] - last_name: Optional[str] - email: Optional[str] - phone: Optional[str] - address: Optional[str] - items: List[CreateInvoiceItemData] - - class Config: - use_enum_values = True - - -class UpdateInvoiceItemData(BaseModel): - id: Optional[str] - description: str - amount: float = Query(..., ge=0.01) - - -class UpdateInvoiceData(BaseModel): - id: str - wallet: str - status: InvoiceStatusEnum = InvoiceStatusEnum.draft - currency: str - company_name: Optional[str] - first_name: Optional[str] - last_name: Optional[str] - email: Optional[str] - phone: Optional[str] - address: Optional[str] - items: List[UpdateInvoiceItemData] - - -class Invoice(BaseModel): - id: str - wallet: str - status: InvoiceStatusEnum = InvoiceStatusEnum.draft - currency: str - company_name: Optional[str] - first_name: Optional[str] - last_name: Optional[str] - email: Optional[str] - phone: Optional[str] - address: Optional[str] - time: int - - class Config: - use_enum_values = True - - @classmethod - def from_row(cls, row: Row) -> "Invoice": - return cls(**dict(row)) - - -class InvoiceItem(BaseModel): - id: str - invoice_id: str - description: str - amount: int - - class Config: - orm_mode = True - - @classmethod - def from_row(cls, row: Row) -> "InvoiceItem": - return cls(**dict(row)) - - -class Payment(BaseModel): - id: str - invoice_id: str - amount: int - time: int - - @classmethod - def from_row(cls, row: Row) -> "Payment": - return cls(**dict(row)) - - -class CreatePaymentData(BaseModel): - invoice_id: str - amount: int diff --git a/lnbits/extensions/invoices/static/css/pay.css b/lnbits/extensions/invoices/static/css/pay.css deleted file mode 100644 index 75ccd112e..000000000 --- a/lnbits/extensions/invoices/static/css/pay.css +++ /dev/null @@ -1,67 +0,0 @@ -#invoicePage > .row:first-child > .col-md-6 { - display: flex; -} - -#invoicePage > .row:first-child > .col-md-6 > .q-card { - flex: 1; -} - -#invoicePage .clear { - margin-bottom: 25px; -} - -#printQrCode { - display: none; -} - -@media (min-width: 1024px) { - #invoicePage > .row:first-child > .col-md-6:first-child > div { - margin-right: 5px; - } - - #invoicePage > .row:first-child > .col-md-6:nth-child(2) > div { - margin-left: 5px; - } -} - -@media print { - * { - color: black !important; - } - - header, - button, - #payButtonContainer { - display: none !important; - } - - main, - .q-page-container { - padding-top: 0px !important; - } - - .q-card { - box-shadow: none !important; - border: 1px solid black; - } - - .q-item { - padding: 5px; - } - - .q-card__section { - padding: 5px; - } - - #printQrCode { - display: block; - } - - p { - margin-bottom: 0px !important; - } - - #invoicePage .clear { - margin-bottom: 10px !important; - } -} diff --git a/lnbits/extensions/invoices/static/image/invoices.png b/lnbits/extensions/invoices/static/image/invoices.png deleted file mode 100644 index 823f9dee2b9f64a7ebe85a134827d75c9bc06181..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 8773 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 None: - if payment.extra.get("tag") != "invoices": - return - - invoice_id = payment.extra.get("invoice_id") - assert invoice_id - - amount = payment.extra.get("famount") - assert amount - - await create_invoice_payment(invoice_id=invoice_id, amount=amount) - - invoice = await get_invoice(invoice_id) - assert invoice - - invoice_items = await get_invoice_items(invoice_id) - invoice_total = await get_invoice_total(invoice_items) - - invoice_payments = await get_invoice_payments(invoice_id) - payments_total = await get_payments_total(invoice_payments) - - if payments_total >= invoice_total: - invoice.status = InvoiceStatusEnum.paid - await update_invoice_internal(invoice.wallet, invoice) - - return diff --git a/lnbits/extensions/invoices/templates/invoices/_api_docs.html b/lnbits/extensions/invoices/templates/invoices/_api_docs.html deleted file mode 100644 index 6e2a63554..000000000 --- a/lnbits/extensions/invoices/templates/invoices/_api_docs.html +++ /dev/null @@ -1,153 +0,0 @@ - - - - - GET /invoices/api/v1/invoices -
Headers
- {"X-Api-Key": <invoice_key>}
-
Body (application/json)
-
- Returns 200 OK (application/json) -
- [<invoice_object>, ...] -
Curl example
- curl -X GET {{ request.base_url }}invoices/api/v1/invoices -H - "X-Api-Key: <invoice_key>" - -
-
-
- - - - - GET - /invoices/api/v1/invoice/{invoice_id} -
Headers
- {"X-Api-Key": <invoice_key>}
-
Body (application/json)
-
- Returns 200 OK (application/json) -
- {invoice_object} -
Curl example
- curl -X GET {{ request.base_url - }}invoices/api/v1/invoice/{invoice_id} -H "X-Api-Key: - <invoice_key>" - -
-
-
- - - - - POST /invoices/api/v1/invoice -
Headers
- {"X-Api-Key": <invoice_key>}
-
Body (application/json)
-
- Returns 200 OK (application/json) -
- {invoice_object} -
Curl example
- curl -X POST {{ request.base_url }}invoices/api/v1/invoice -H - "X-Api-Key: <invoice_key>" - -
-
-
- - - - - POST - /invoices/api/v1/invoice/{invoice_id} -
Headers
- {"X-Api-Key": <invoice_key>}
-
Body (application/json)
-
- Returns 200 OK (application/json) -
- {invoice_object} -
Curl example
- curl -X POST {{ request.base_url - }}invoices/api/v1/invoice/{invoice_id} -H "X-Api-Key: - <invoice_key>" - -
-
-
- - - - - POST - /invoices/api/v1/invoice/{invoice_id}/payments -
Headers
-
Body (application/json)
-
- Returns 200 OK (application/json) -
- {payment_object} -
Curl example
- curl -X POST {{ request.base_url - }}invoices/api/v1/invoice/{invoice_id}/payments -H "X-Api-Key: - <invoice_key>" - -
-
-
- - - - - GET - /invoices/api/v1/invoice/{invoice_id}/payments/{payment_hash} -
Headers
-
Body (application/json)
-
- Returns 200 OK (application/json) -
-
Curl example
- curl -X GET {{ request.base_url - }}invoices/api/v1/invoice/{invoice_id}/payments/{payment_hash} -H - "X-Api-Key: <invoice_key>" - -
-
-
-
diff --git a/lnbits/extensions/invoices/templates/invoices/index.html b/lnbits/extensions/invoices/templates/invoices/index.html deleted file mode 100644 index 4ef3b7f1c..000000000 --- a/lnbits/extensions/invoices/templates/invoices/index.html +++ /dev/null @@ -1,571 +0,0 @@ -{% extends "base.html" %} {% from "macros.jinja" import window_vars with context -%} {% block page %} -
-
- - - New Invoice - - - - - -
-
-
Invoices
-
-
- Export to CSV -
-
- - {% raw %} - - - - {% endraw %} - -
-
-
- -
- - -
- {{SITE_TITLE}} Invoices extension -
-
- - - {% include "invoices/_api_docs.html" %} - -
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Add Line Item - - - - -
- Create Invoice - Save Invoice - Cancel -
-
-
-
-
-{% endblock %} {% block scripts %} {{ window_vars(user) }} - -{% endblock %} diff --git a/lnbits/extensions/invoices/templates/invoices/pay.html b/lnbits/extensions/invoices/templates/invoices/pay.html deleted file mode 100644 index 82f1765e7..000000000 --- a/lnbits/extensions/invoices/templates/invoices/pay.html +++ /dev/null @@ -1,433 +0,0 @@ -{% extends "public.html" %} {% block toolbar_title %} Invoice - - -{% endblock %} {% from "macros.jinja" import window_vars with context %} {% -block page %} - -
-
-
- - -

- Invoice -

- - - - ID - {{ invoice_id }} - - - - Created At - {{ datetime.utcfromtimestamp(invoice.time).strftime('%Y-%m-%d - %H:%M') }} - - - - Status - - - {{ invoice.status }} - - - - - - Total - - {{ "{:0,.2f}".format(invoice_total / 100) }} {{ invoice.currency - }} - - - - - Paid - -
-
- {{ "{:0,.2f}".format(payments_total / 100) }} {{ - invoice.currency }} -
-
- {% if payments_total < invoice_total %} - - Pay Invoice - - {% endif %} -
-
-
-
-
-
-
-
- -
- - -

- Bill To -

- - - - Company Name - {{ invoice.company_name }} - - - - Name - {{ invoice.first_name }} {{ invoice.last_name - }} - - - - Address - {{ invoice.address }} - - - - Email - {{ invoice.email }} - - - - Phone - {{ invoice.phone }} - - -
-
-
-
- -
- -
-
- - -

- Items -

- - - {% if invoice_items %} - - Item - Amount - - {% endif %} {% for item in invoice_items %} - - {{item.description}} - - {{ "{:0,.2f}".format(item.amount / 100) }} {{ invoice.currency - }} - - - {% endfor %} {% if not invoice_items %} No Invoice Items {% endif %} - -
-
-
-
- -
- -
-
- - -

- Payments -

- - - {% if invoice_payments %} - - Date - Amount - - {% endif %} {% for item in invoice_payments %} - - {{ datetime.utcfromtimestamp(item.time).strftime('%Y-%m-%d - %H:%M') }} - - {{ "{:0,.2f}".format(item.amount / 100) }} {{ invoice.currency - }} - - - {% endfor %} {% if not invoice_payments %} No Invoice Payments {% - endif %} - -
-
-
-
- -
- -
-
-
-

Scan to View & Pay Online!

- -
-
-
- - - - - - - - -
- Create Payment - Cancel -
-
-
-
- - - - - - - - -
- Copy Invoice -
-
- - - - - - -
-

{{ request.url }}

-
-
- Copy URL - Close -
-
-
-
-{% endblock %} {% block scripts %} - -{% endblock %} diff --git a/lnbits/extensions/invoices/views.py b/lnbits/extensions/invoices/views.py deleted file mode 100644 index cc35b3517..000000000 --- a/lnbits/extensions/invoices/views.py +++ /dev/null @@ -1,57 +0,0 @@ -from datetime import datetime -from http import HTTPStatus - -from fastapi import Depends, HTTPException, Request -from fastapi.templating import Jinja2Templates -from starlette.responses import HTMLResponse - -from lnbits.core.models import User -from lnbits.decorators import check_user_exists - -from . import invoices_ext, invoices_renderer -from .crud import ( - get_invoice, - get_invoice_items, - get_invoice_payments, - get_invoice_total, - get_payments_total, -) - -templates = Jinja2Templates(directory="templates") - - -@invoices_ext.get("/", response_class=HTMLResponse) -async def index(request: Request, user: User = Depends(check_user_exists)): - return invoices_renderer().TemplateResponse( - "invoices/index.html", {"request": request, "user": user.dict()} - ) - - -@invoices_ext.get("/pay/{invoice_id}", response_class=HTMLResponse) -async def pay(request: Request, invoice_id: str): - invoice = await get_invoice(invoice_id) - - if not invoice: - raise HTTPException( - status_code=HTTPStatus.NOT_FOUND, detail="Invoice does not exist." - ) - - invoice_items = await get_invoice_items(invoice_id) - invoice_total = await get_invoice_total(invoice_items) - - invoice_payments = await get_invoice_payments(invoice_id) - payments_total = await get_payments_total(invoice_payments) - - return invoices_renderer().TemplateResponse( - "invoices/pay.html", - { - "request": request, - "invoice_id": invoice_id, - "invoice": invoice.dict(), - "invoice_items": invoice_items, - "invoice_total": invoice_total, - "invoice_payments": invoice_payments, - "payments_total": payments_total, - "datetime": datetime, - }, - ) diff --git a/lnbits/extensions/invoices/views_api.py b/lnbits/extensions/invoices/views_api.py deleted file mode 100644 index 1a7762a81..000000000 --- a/lnbits/extensions/invoices/views_api.py +++ /dev/null @@ -1,133 +0,0 @@ -from http import HTTPStatus - -from fastapi import Depends, HTTPException, Query -from loguru import logger - -from lnbits.core.crud import get_user -from lnbits.core.services import create_invoice -from lnbits.core.views.api import api_payment -from lnbits.decorators import WalletTypeInfo, get_key_type -from lnbits.utils.exchange_rates import fiat_amount_as_satoshis - -from . import invoices_ext -from .crud import ( - create_invoice_internal, - create_invoice_items, - get_invoice, - get_invoice_items, - get_invoice_payments, - get_invoice_total, - get_invoices, - get_payments_total, - update_invoice_internal, - update_invoice_items, -) -from .models import CreateInvoiceData, UpdateInvoiceData - - -@invoices_ext.get("/api/v1/invoices", status_code=HTTPStatus.OK) -async def api_invoices( - all_wallets: bool = Query(None), wallet: WalletTypeInfo = Depends(get_key_type) -): - wallet_ids = [wallet.wallet.id] - if all_wallets: - user = await get_user(wallet.wallet.user) - wallet_ids = user.wallet_ids if user else [] - - return [invoice.dict() for invoice in await get_invoices(wallet_ids)] - - -@invoices_ext.get("/api/v1/invoice/{invoice_id}", status_code=HTTPStatus.OK) -async def api_invoice(invoice_id: str): - invoice = await get_invoice(invoice_id) - if not invoice: - raise HTTPException( - status_code=HTTPStatus.NOT_FOUND, detail="Invoice does not exist." - ) - invoice_items = await get_invoice_items(invoice_id) - - invoice_payments = await get_invoice_payments(invoice_id) - payments_total = await get_payments_total(invoice_payments) - - invoice_dict = invoice.dict() - invoice_dict["items"] = invoice_items - invoice_dict["payments"] = payments_total - return invoice_dict - - -@invoices_ext.post("/api/v1/invoice", status_code=HTTPStatus.CREATED) -async def api_invoice_create( - data: CreateInvoiceData, wallet: WalletTypeInfo = Depends(get_key_type) -): - invoice = await create_invoice_internal(wallet_id=wallet.wallet.id, data=data) - items = await create_invoice_items(invoice_id=invoice.id, data=data.items) - invoice_dict = invoice.dict() - invoice_dict["items"] = items - return invoice_dict - - -@invoices_ext.post("/api/v1/invoice/{invoice_id}", status_code=HTTPStatus.OK) -async def api_invoice_update( - data: UpdateInvoiceData, - invoice_id: str, - wallet: WalletTypeInfo = Depends(get_key_type), -): - invoice = await update_invoice_internal(wallet_id=wallet.wallet.id, data=data) - items = await update_invoice_items(invoice_id=invoice.id, data=data.items) - invoice_dict = invoice.dict() - invoice_dict["items"] = items - return invoice_dict - - -@invoices_ext.post( - "/api/v1/invoice/{invoice_id}/payments", status_code=HTTPStatus.CREATED -) -async def api_invoices_create_payment(invoice_id: str, famount: int = Query(..., ge=1)): - invoice = await get_invoice(invoice_id) - invoice_items = await get_invoice_items(invoice_id) - invoice_total = await get_invoice_total(invoice_items) - - invoice_payments = await get_invoice_payments(invoice_id) - payments_total = await get_payments_total(invoice_payments) - - if payments_total + famount > invoice_total: - raise HTTPException( - status_code=HTTPStatus.BAD_REQUEST, detail="Amount exceeds invoice due." - ) - - if not invoice: - raise HTTPException( - status_code=HTTPStatus.NOT_FOUND, detail="Invoice does not exist." - ) - - price_in_sats = await fiat_amount_as_satoshis(famount / 100, invoice.currency) - - try: - payment_hash, payment_request = await create_invoice( - wallet_id=invoice.wallet, - amount=price_in_sats, - memo=f"Payment for invoice {invoice_id}", - extra={"tag": "invoices", "invoice_id": invoice_id, "famount": famount}, - ) - except Exception as e: - raise HTTPException(status_code=HTTPStatus.INTERNAL_SERVER_ERROR, detail=str(e)) - - return {"payment_hash": payment_hash, "payment_request": payment_request} - - -@invoices_ext.get( - "/api/v1/invoice/{invoice_id}/payments/{payment_hash}", status_code=HTTPStatus.OK -) -async def api_invoices_check_payment(invoice_id: str, payment_hash: str): - invoice = await get_invoice(invoice_id) - if not invoice: - raise HTTPException( - status_code=HTTPStatus.NOT_FOUND, detail="Invoice does not exist." - ) - try: - status = await api_payment(payment_hash) - - except Exception as exc: - logger.error(exc) - return {"paid": False} - return status diff --git a/lnbits/static/scss/base.scss b/lnbits/static/scss/base.scss index f6b0762ef..0730d03e9 100644 --- a/lnbits/static/scss/base.scss +++ b/lnbits/static/scss/base.scss @@ -1,153 +1,207 @@ -$themes: ( 'classic': ( primary: #673ab7, secondary: #9c27b0, dark: #1f2234, info: #333646, marginal-bg: #1f2234, marginal-text: #fff), 'bitcoin': ( primary: #ff9853, secondary: #ff7353, dark: #2d293b, info: #333646, marginal-bg: #2d293b, marginal-text: #fff), 'freedom': ( primary: #e22156, secondary: #b91a45, dark: #0a0a0a, info: #1b1b1b, marginal-bg: #2d293b, marginal-text: #fff), 'mint': ( primary: #3ab77d, secondary: #27b065, dark: #1f342b, info: #334642, marginal-bg: #1f342b, marginal-text: #fff), 'autumn': ( primary: #b7763a, secondary: #b07927, dark: #34291f, info: #463f33, marginal-bg: #342a1f, marginal-text: rgb(255, 255, 255)), 'flamingo': ( primary: #d11d53, secondary: #db3e6d, dark: #803a45, info: #ec7599, marginal-bg: #803a45, marginal-text: rgb(255, 255, 255)), 'monochrome': ( primary: #494949, secondary: #6b6b6b, dark: #000, info: rgb(39, 39, 39), marginal-bg: #000, marginal-text: rgb(255, 255, 255))); -@each $theme, -$colors in $themes { - @each $name, - $color in $colors { - @if $name=='dark' { - [data-theme='#{$theme}'] .q-drawer--dark, - body[data-theme='#{$theme}'].body--dark, - [data-theme='#{$theme}'] .q-menu--dark { - background: $color !important; - } - /* IF WANTING TO SET A DARKER BG COLOR IN THE FUTURE +$themes: ( + 'classic': ( + primary: #673ab7, + secondary: #9c27b0, + dark: #1f2234, + info: #333646, + marginal-bg: #1f2234, + marginal-text: #fff + ), + 'bitcoin': ( + primary: #ff9853, + secondary: #ff7353, + dark: #2d293b, + info: #333646, + marginal-bg: #2d293b, + marginal-text: #fff + ), + 'freedom': ( + primary: #e22156, + secondary: #b91a45, + dark: #0a0a0a, + info: #1b1b1b, + marginal-bg: #2d293b, + marginal-text: #fff + ), + 'mint': ( + primary: #3ab77d, + secondary: #27b065, + dark: #1f342b, + info: #334642, + marginal-bg: #1f342b, + marginal-text: #fff + ), + 'autumn': ( + primary: #b7763a, + secondary: #b07927, + dark: #34291f, + info: #463f33, + marginal-bg: #342a1f, + marginal-text: rgb(255, 255, 255) + ), + 'flamingo': ( + primary: #d11d53, + secondary: #db3e6d, + dark: #803a45, + info: #ec7599, + marginal-bg: #803a45, + marginal-text: rgb(255, 255, 255) + ), + 'monochrome': ( + primary: #494949, + secondary: #6b6b6b, + dark: #000, + info: rgb(39, 39, 39), + marginal-bg: #000, + marginal-text: rgb(255, 255, 255) + ) +); +@each $theme, $colors in $themes { + @each $name, $color in $colors { + @if $name== 'dark' { + [data-theme='#{$theme}'] .q-drawer--dark, + body[data-theme='#{$theme}'].body--dark, + [data-theme='#{$theme}'] .q-menu--dark { + background: $color !important; + } + /* IF WANTING TO SET A DARKER BG COLOR IN THE FUTURE // set a darker body bg for all themes, when in "dark mode" body[data-theme='#{$theme}'].body--dark { background: scale-color($color, $lightness: -60%); } */ - } - @if $name=='info' { - [data-theme='#{$theme}'] .q-card--dark, - [data-theme='#{$theme}'] .q-stepper--dark { - background: $color !important; - } - } } - [data-theme='#{$theme}'] { - @each $name, - $color in $colors { - .bg-#{$name} { - background: $color !important; - } - .text-#{$name} { - color: $color !important; - } - } + @if $name== 'info' { + [data-theme='#{$theme}'] .q-card--dark, + [data-theme='#{$theme}'] .q-stepper--dark { + background: $color !important; + } } + } + [data-theme='#{$theme}'] { + @each $name, $color in $colors { + .bg-#{$name} { + background: $color !important; + } + .text-#{$name} { + color: $color !important; + } + } + } } [data-theme='freedom'] .q-drawer--dark { - background: #0a0a0a !important; + background: #0a0a0a !important; } [data-theme='freedom'] .q-header { - background: #0a0a0a !important; + background: #0a0a0a !important; } [data-theme='salvador'] .q-drawer--dark { - background: #242424 !important; + background: #242424 !important; } [data-theme='salvador'] .q-header { - background: #0f47af !important; + background: #0f47af !important; } [data-theme='flamingo'] .q-drawer--dark { - background: #e75480 !important; + background: #e75480 !important; } [data-theme='flamingo'] .q-header { - background: #e75480 !important; + background: #e75480 !important; } [v-cloak] { - display: none; + display: none; } body.body--dark .q-table--dark { - background: transparent; + background: transparent; } body.body--dark .q-field--error { - .text-negative, - .q-field__messages { - color: yellow !important; - } + .text-negative, + .q-field__messages { + color: yellow !important; + } } .lnbits-drawer__q-list .q-item { - padding-top: 5px !important; - padding-bottom: 5px !important; - border-top-right-radius: 3px; - border-bottom-right-radius: 3px; - &.q-item--active { - color: inherit; - font-weight: bold; - } + padding-top: 5px !important; + padding-bottom: 5px !important; + border-top-right-radius: 3px; + border-bottom-right-radius: 3px; + &.q-item--active { + color: inherit; + font-weight: bold; + } } .lnbits__dialog-card { - width: 500px; + width: 500px; } .q-table--dense { - th:first-child, - td:first-child, - .q-table__bottom { - padding-left: 6px !important; - } - th:last-child, - td:last-child, - .q-table__bottom { - padding-right: 6px !important; - } + th:first-child, + td:first-child, + .q-table__bottom { + padding-left: 6px !important; + } + th:last-child, + td:last-child, + .q-table__bottom { + padding-right: 6px !important; + } } a.inherit { - color: inherit; - text-decoration: none; + color: inherit; + text-decoration: none; } // QR video video { - border-radius: 3px; + border-radius: 3px; } // Material icons font @font-face { - font-family: 'Material Icons'; - font-style: normal; - font-weight: 400; - src: url(../fonts/material-icons-v50.woff2) format('woff2'); + font-family: 'Material Icons'; + font-style: normal; + font-weight: 400; + src: url(../fonts/material-icons-v50.woff2) format('woff2'); } .material-icons { - font-family: 'Material Icons'; - font-weight: normal; - font-style: normal; - font-size: 24px; - line-height: 1; - letter-spacing: normal; - text-transform: none; - display: inline-block; - white-space: nowrap; - word-wrap: normal; - direction: ltr; - -moz-font-feature-settings: 'liga'; - -moz-osx-font-smoothing: grayscale; + font-family: 'Material Icons'; + font-weight: normal; + font-style: normal; + font-size: 24px; + line-height: 1; + letter-spacing: normal; + text-transform: none; + display: inline-block; + white-space: nowrap; + word-wrap: normal; + direction: ltr; + -moz-font-feature-settings: 'liga'; + -moz-osx-font-smoothing: grayscale; } .q-rating__icon { - font-size: 1em; + font-size: 1em; } // text-wrap .text-wrap { - word-break: break-word; + word-break: break-word; } .q-card { code { overflow-wrap: break-word; } -} \ No newline at end of file +} diff --git a/tests/core/views/__init__.py b/tests/core/views/__init__.py index e69de29bb..4bc64062d 100644 --- a/tests/core/views/__init__.py +++ b/tests/core/views/__init__.py @@ -0,0 +1 @@ +from tests.mocks import WALLET # noqa: F401 diff --git a/tests/extensions/invoices/__init__.py b/tests/extensions/invoices/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/tests/extensions/invoices/conftest.py b/tests/extensions/invoices/conftest.py deleted file mode 100644 index 522ba81fc..000000000 --- a/tests/extensions/invoices/conftest.py +++ /dev/null @@ -1,36 +0,0 @@ -import pytest_asyncio - -from lnbits.core.crud import create_account, create_wallet -from lnbits.extensions.invoices.crud import ( - create_invoice_internal, - create_invoice_items, -) -from lnbits.extensions.invoices.models import CreateInvoiceData - - -@pytest_asyncio.fixture -async def invoices_wallet(): - user = await create_account() - wallet = await create_wallet(user_id=user.id, wallet_name="invoices_test") - - return wallet - - -@pytest_asyncio.fixture -async def accounting_invoice(invoices_wallet): - invoice_data = CreateInvoiceData( - status="open", - currency="USD", - company_name="LNbits, Inc", - first_name="Ben", - last_name="Arc", - items=[{"amount": 10.20, "description": "Item costs 10.20"}], - ) - invoice = await create_invoice_internal( - wallet_id=invoices_wallet.id, data=invoice_data - ) - items = await create_invoice_items(invoice_id=invoice.id, data=invoice_data.items) - - invoice_dict = invoice.dict() - invoice_dict["items"] = items - return invoice_dict diff --git a/tests/extensions/invoices/test_invoices_api.py b/tests/extensions/invoices/test_invoices_api.py deleted file mode 100644 index 3c337d7e8..000000000 --- a/tests/extensions/invoices/test_invoices_api.py +++ /dev/null @@ -1,133 +0,0 @@ -import pytest -import pytest_asyncio # noqa: F401 -from loguru import logger # noqa: F401 - -from lnbits.core.crud import get_wallet # noqa: F401 -from tests.helpers import credit_wallet # noqa: F401 -from tests.mocks import WALLET # noqa: F401 - - -@pytest.mark.asyncio -async def test_invoices_unknown_invoice(client): - response = await client.get("/invoices/pay/u") - assert response.json() == {"detail": "Invoice does not exist."} - - -@pytest.mark.asyncio -async def test_invoices_api_create_invoice_valid(client, invoices_wallet): - query = { - "status": "open", - "currency": "EUR", - "company_name": "LNbits, Inc.", - "first_name": "Ben", - "last_name": "Arc", - "email": "ben@legend.arc", - "items": [ - {"amount": 2.34, "description": "Item 1"}, - {"amount": 0.98, "description": "Item 2"}, - ], - } - - status = query["status"] - currency = query["currency"] - fname = query["first_name"] - total = sum(d["amount"] for d in query["items"]) - - response = await client.post( - "/invoices/api/v1/invoice", - json=query, - headers={"X-Api-Key": invoices_wallet.inkey}, - ) - - assert response.status_code == 201 - data = response.json() - - assert data["status"] == status - assert data["wallet"] == invoices_wallet.id - assert data["currency"] == currency - assert data["first_name"] == fname - assert sum(d["amount"] / 100 for d in data["items"]) == total - - -@pytest.mark.asyncio -async def test_invoices_api_partial_pay_invoice( - client, accounting_invoice, adminkey_headers_from -): - invoice_id = accounting_invoice["id"] - amount_to_pay = int(5.05 * 100) # mock invoice total amount is 10 USD - - # ask for an invoice - response = await client.post( - f"/invoices/api/v1/invoice/{invoice_id}/payments?famount={amount_to_pay}" - ) - assert response.status_code < 300 - data = response.json() - payment_hash = data["payment_hash"] - - # pay the invoice - data = {"out": True, "bolt11": data["payment_request"]} - response = await client.post( - "/api/v1/payments", json=data, headers=adminkey_headers_from - ) - assert response.status_code < 300 - assert len(response.json()["payment_hash"]) == 64 - assert len(response.json()["checking_id"]) > 0 - - # check invoice is paid - response = await client.get( - f"/invoices/api/v1/invoice/{invoice_id}/payments/{payment_hash}" - ) - assert response.status_code == 200 - assert response.json()["paid"] is True - - # check invoice status - response = await client.get(f"/invoices/api/v1/invoice/{invoice_id}") - assert response.status_code == 200 - data = response.json() - - assert data["status"] == "open" - - -#### -# -# TEST FAILS FOR NOW, AS LISTENERS ARE NOT WORKING ON TESTING -# -### - -# @pytest.mark.asyncio -# async def test_invoices_api_full_pay_invoice(client, accounting_invoice, adminkey_headers_to): -# invoice_id = accounting_invoice["id"] -# print(accounting_invoice["id"]) -# amount_to_pay = int(10.20 * 100) - -# # ask for an invoice -# response = await client.post( -# f"/invoices/api/v1/invoice/{invoice_id}/payments?famount={amount_to_pay}" -# ) -# assert response.status_code == 201 -# data = response.json() -# payment_hash = data["payment_hash"] - -# # pay the invoice -# data = {"out": True, "bolt11": data["payment_request"]} -# response = await client.post( -# "/api/v1/payments", json=data, headers=adminkey_headers_to -# ) -# assert response.status_code < 300 -# assert len(response.json()["payment_hash"]) == 64 -# assert len(response.json()["checking_id"]) > 0 - -# # check invoice is paid -# response = await client.get( -# f"/invoices/api/v1/invoice/{invoice_id}/payments/{payment_hash}" -# ) -# assert response.status_code == 200 -# assert response.json()["paid"] is True - -# # check invoice status -# response = await client.get(f"/invoices/api/v1/invoice/{invoice_id}") -# assert response.status_code == 200 -# data = response.json() - -# print(data) -# assert data["status"] == "paid"