Skip to content

Commit

Permalink
feat: upload works
Browse files Browse the repository at this point in the history
- add public upload page
- add print helpers
  • Loading branch information
dni committed Dec 26, 2024
1 parent deb3ab3 commit f253d4d
Show file tree
Hide file tree
Showing 16 changed files with 593 additions and 77 deletions.
18 changes: 14 additions & 4 deletions __init__.py
Original file line number Diff line number Diff line change
@@ -1,15 +1,14 @@
import asyncio

from fastapi import APIRouter
from lnbits.db import Database
from loguru import logger

from .crud import db
from .helpers import print_file, setup_upload_folder
from .tasks import wait_for_paid_invoices
from .views import pay2print_ext_generic
from .views_api import pay2print_ext_api

db = Database("ext_pay2print")

scheduled_tasks: list[asyncio.Task] = []

pay2print_ext: APIRouter = APIRouter(prefix="/pay2print", tags=["pay2print"])
Expand All @@ -35,5 +34,16 @@ def pay2print_stop():
def pay2print_start():
from lnbits.tasks import create_permanent_unique_task

task = create_permanent_unique_task("ext_testing", wait_for_paid_invoices)
setup_upload_folder()
task = create_permanent_unique_task("ext_pay2print", wait_for_paid_invoices)
scheduled_tasks.append(task)


__all__ = [
"db",
"pay2print_ext",
"pay2print_static_files",
"pay2print_start",
"pay2print_stop",
"print_file",
]
2 changes: 1 addition & 1 deletion config.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"id": "pay2print",
"name": "Pay 2 Print",
"short_description": "pay sats to print a document",
"tile": "https://raw.githubusercontent.com/lnbits/pay2print/main/static/bitcoin-extension.png",
"tile": "https://raw.githubusercontent.com/lnbits/pay2print/main/static/printer.png",
"min_lnbits_version": "1.0.0",
"donate": "[email protected]",
"contributors": [
Expand Down
13 changes: 8 additions & 5 deletions crud.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,16 @@

from lnbits.db import Database

from .models import CreatePrint, CreatePrinter, Print, Printer
from .models import CreatePrinter, Print, Printer

db = Database("ext_pay2print")


async def create_print(payment_hash: str, data: CreatePrint) -> Print:
async def create_print(payment_hash: str, printer_id: str, file_name: str) -> Print:
_print = Print(
payment_hash=payment_hash,
file=data.file,
printer=data.printer,
file=file_name,
printer=printer_id,
)
await db.insert("pay2print.print", _print)
return _print
Expand All @@ -37,14 +37,17 @@ async def create_printer(user_id: str, data: CreatePrinter) -> Printer:
user_id=user_id,
wallet=data.wallet,
host=data.host,
amount=data.amount,
width=data.width,
height=data.height,
name=data.name or "My Printer",
)
await db.insert("pay2print.printer", printer)
return printer


async def update_printer(printer: Printer) -> Printer:
await db.update("pay2print.print", printer)
await db.update("pay2print.printer", printer)
return printer


Expand Down
56 changes: 56 additions & 0 deletions helpers.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
from os import path
from pathlib import Path, PurePath
from subprocess import PIPE, Popen
from time import time

from lnbits.settings import settings
from loguru import logger

upload_dir = Path(settings.lnbits_data_folder, "uploads")

cmd_check_default_printer = ["lpstat", "-d"]
cmd_print = "lpr"


class PrinterError(Exception):
"""Error is thrown we we cannot connect to the printer."""


def setup_upload_folder():
upload_dir.mkdir(parents=True, exist_ok=True)


def safe_file_name(file_name: str) -> Path:
# strip leading path from file name to avoid directory traversal attacks
safe_name = PurePath(file_name).name
if path.exists(safe_name):
safe_name = f"{int(time())}_{safe_name}"
return Path(upload_dir, file_name)


def print_file_path(file_name: str) -> Path:
return Path(upload_dir, file_name)


def check_printer():
try:
lines = run_command(cmd_check_default_printer)
if len(lines) == 0:
raise PrinterError("No default printer found")
logger.debug(f"Default printer: {lines[0]}")
except Exception as e:
raise PrinterError(f"Error checking default printer: {e}") from e


def print_file(file_name: str):
path = print_file_path(file_name)
run_command([cmd_print, str(path)])


def run_command(command: list[str]) -> list[str]:
"""Run a command and return the output as list of lines."""
stdout = Popen(command, stdout=PIPE).stdout
if not stdout:
return []
out = stdout.read().decode()
return out.splitlines()
3 changes: 3 additions & 0 deletions migrations.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,9 @@ async def m001_initial(db: Connection):
wallet TEXT NOT NULL,
name TEXT NOT NULL,
host TEXT NOT NULL,
amount INTEGER NOT NULL,
width INTEGER NOT NULL,
height INTEGER NOT NULL,
created_at TIMESTAMP NOT NULL DEFAULT {db.timestamp_now}
);
"""
Expand Down
11 changes: 6 additions & 5 deletions models.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,12 +23,18 @@ class Printer(BaseModel):
wallet: str
host: str
name: str
amount: int
width: int
height: int
created_at: datetime = Field(default_factory=lambda: datetime.now(timezone.utc))


class CreatePrinter(BaseModel):
wallet: str
host: str
amount: int
width: int
height: int
name: Optional[str] = None


Expand All @@ -41,8 +47,3 @@ class Print(BaseModel):
print_status: PrintStatus = PrintStatus.WAITING
updated_at: datetime = Field(default_factory=lambda: datetime.now(timezone.utc))
created_at: datetime = Field(default_factory=lambda: datetime.now(timezone.utc))


class CreatePrint(BaseModel):
file: str
printer: str
13 changes: 12 additions & 1 deletion poetry.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ authors = ["Alan Bits <[email protected]>"]
[tool.poetry.dependencies]
python = "^3.10 | ^3.9"
lnbits = {version = "*", allow-prereleases = true}
python-multipart = "^0.0.20"

[tool.poetry.group.dev.dependencies]
black = "^24.3.0"
Expand Down
138 changes: 125 additions & 13 deletions static/js/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,38 +3,150 @@ window.app = Vue.createApp({
mixins: [windowMixin],
data() {
return {
pay2printDialog: {
printerUrl: '/pay2print/api/v1/printer',
printUrl: '/pay2print/api/v1/print',
printer: null,
printerLabel: null,
printers: [],
prints: [],
printsTable: {
columns: [
{
name: 'file',
align: 'left',
label: 'file',
field: 'file'
},
{
name: 'id',
align: 'left',
label: 'id',
field: 'id'
},
{
name: 'print_status',
align: 'left',
label: 'print_status',
field: 'print_status'
},
{
name: 'payment_status',
align: 'left',
label: 'payment_status',
field: 'payment_status'
}
],
pagination: {
rowsPerPage: 10
}
},
printersTable: {
columns: [
{
name: 'name',
align: 'left',
label: 'name',
field: 'name'
},
{
name: 'host',
align: 'left',
label: 'host',
field: 'host'
},
{
name: 'id',
align: 'left',
label: 'id',
field: 'id'
}
],
pagination: {
rowsPerPage: 10
}
},
printerDialog: {
show: false,
data: {}
},
pay2printData: []
}
}
},
methods: {
submitForm() {
submitPrinterForm() {
const method = this.printerDialog.data.id ? 'PUT' : 'POST'
const url = this.printerDialog.data.id
? `${this.printerUrl}/${this.printerDialog.data.id}`
: this.printerUrl
LNbits.api
.request(
this.pay2printDialog.data.id ? 'PUT' : 'POST',
'/pay2print/api/v1/print',
method,
url,
this.g.user.wallets[0].adminkey,
this.pay2printDialog.data
this.printerDialog.data
)
.then(_ => {
this.getPay2Prints()
this.pay2printDialog.show = false
this.getPrinters()
this.printerDialog.show = false
this.printerDialog.data = {}
})
.catch(LNbits.utils.notifyApiError)
},
getPay2Prints() {
openUpdatePrinter(printer_id) {
const printer = this.printers.find(printer => printer.id === printer_id)
this.printerDialog.data = {...printer}
this.printerDialog.show = true
},
openFile(file_name) {
const print = this.prints.find(print => print.file === file_name)
return `/pay2print/api/v1/file/${print.id}`
},
getPrints(printer_id) {
LNbits.api
.request('GET', '/pay2print/api/v1/print', this.g.user.wallets[0].inkey)
.request(
'GET',
`${this.printUrl}/${printer_id}`,
this.g.user.wallets[0].inkey
)
.then(response => {
this.pay2printData = response.data
this.prints = response.data
})
.catch(LNbits.utils.notifyApiError)
},
getPrinters() {
LNbits.api
.request('GET', this.printerUrl, this.g.user.wallets[0].inkey)
.then(response => {
this.printers = response.data
if (this.printers.length > 0) {
this.printer = this.printers[0].id
this.printerLabel = this.printers[0].name
}
})
.catch(LNbits.utils.notifyApiError)
},
deletePrinter(id) {
LNbits.api
.request(
'DELETE',
`${this.printerUrl}/${id}`,
this.g.user.wallets[0].adminkey
)
.then(_ => {
this.getPrinters()
})
.catch(LNbits.utils.notifyApiError)
}
},
watch: {
printer(val) {
if (val) {
const printer = this.printers.find(printer => printer.id === val)
this.printerLabel = printer.name
this.getPrints(val)
}
}
},
created() {
this.getPay2Prints()
this.getPrinters()
}
})
18 changes: 18 additions & 0 deletions static/js/public.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
window.app = Vue.createApp({
el: '#vue',
mixins: [window.windowMixin],
data() {
return {
invoice: null,
invoiceAmount: amount + ' sat',
paid: false
}
},
methods: {
uploaded(e) {
console.log('uploaded', e.xhr)
this.invoice = e.xhr.response
}
},
created() {}
})
Binary file added static/printer.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading

0 comments on commit f253d4d

Please sign in to comment.