Merge pull request #791 from lnbits/fix/offlineshop_fiat_decimals

Offlineshop - allow for decimals in fiat unit
This commit is contained in:
Arc 2022-07-28 12:47:02 +01:00 committed by GitHub
commit 2ef9548b05
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 57 additions and 19 deletions

View file

@ -52,14 +52,20 @@ async def set_method(shop: int, method: str, wordlist: str = "") -> Optional[Sho
async def add_item( async def add_item(
shop: int, name: str, description: str, image: Optional[str], price: int, unit: str shop: int,
name: str,
description: str,
image: Optional[str],
price: int,
unit: str,
fiat_base_multiplier: int,
) -> int: ) -> int:
result = await db.execute( result = await db.execute(
""" """
INSERT INTO offlineshop.items (shop, name, description, image, price, unit) INSERT INTO offlineshop.items (shop, name, description, image, price, unit, fiat_base_multiplier)
VALUES (?, ?, ?, ?, ?, ?) VALUES (?, ?, ?, ?, ?, ?, ?)
""", """,
(shop, name, description, image, price, unit), (shop, name, description, image, price, unit, fiat_base_multiplier),
) )
return result._result_proxy.lastrowid return result._result_proxy.lastrowid
@ -72,6 +78,7 @@ async def update_item(
image: Optional[str], image: Optional[str],
price: int, price: int,
unit: str, unit: str,
fiat_base_multiplier: int,
) -> int: ) -> int:
await db.execute( await db.execute(
""" """
@ -80,10 +87,11 @@ async def update_item(
description = ?, description = ?,
image = ?, image = ?,
price = ?, price = ?,
unit = ? unit = ?,
fiat_base_multiplier = ?
WHERE shop = ? AND id = ? WHERE shop = ? AND id = ?
""", """,
(name, description, image, price, unit, shop, item_id), (name, description, image, price, unit, fiat_base_multiplier, shop, item_id),
) )
return item_id return item_id
@ -92,12 +100,12 @@ async def get_item(id: int) -> Optional[Item]:
row = await db.fetchone( row = await db.fetchone(
"SELECT * FROM offlineshop.items WHERE id = ? LIMIT 1", (id,) "SELECT * FROM offlineshop.items WHERE id = ? LIMIT 1", (id,)
) )
return Item(**dict(row)) if row else None return Item.from_row(row) if row else None
async def get_items(shop: int) -> List[Item]: async def get_items(shop: int) -> List[Item]:
rows = await db.fetchall("SELECT * FROM offlineshop.items WHERE shop = ?", (shop,)) rows = await db.fetchall("SELECT * FROM offlineshop.items WHERE shop = ?", (shop,))
return [Item(**dict(row)) for row in rows] return [Item.from_row(row) for row in rows]
async def delete_item_from_shop(shop: int, item_id: int): async def delete_item_from_shop(shop: int, item_id: int):

View file

@ -27,3 +27,13 @@ async def m001_initial(db):
); );
""" """
) )
async def m002_fiat_base_multiplier(db):
"""
Store the multiplier for fiat prices. We store the price in cents and
remember to multiply by 100 when we use it to convert to Dollars.
"""
await db.execute(
"ALTER TABLE offlineshop.items ADD COLUMN fiat_base_multiplier INTEGER DEFAULT 1;"
)

View file

@ -2,6 +2,7 @@ import base64
import hashlib import hashlib
import json import json
from collections import OrderedDict from collections import OrderedDict
from sqlite3 import Row
from typing import Dict, List, Optional from typing import Dict, List, Optional
from lnurl import encode as lnurl_encode # type: ignore from lnurl import encode as lnurl_encode # type: ignore
@ -87,8 +88,16 @@ class Item(BaseModel):
description: str description: str
image: Optional[str] image: Optional[str]
enabled: bool enabled: bool
price: int price: float
unit: str unit: str
fiat_base_multiplier: int
@classmethod
def from_row(cls, row: Row) -> "Item":
data = dict(row)
if data["unit"] != "sat" and data["fiat_base_multiplier"]:
data["price"] /= data["fiat_base_multiplier"]
return cls(**data)
def lnurl(self, req: Request) -> str: def lnurl(self, req: Request) -> str:
return lnurl_encode(req.url_for("offlineshop.lnurl_response", item_id=self.id)) return lnurl_encode(req.url_for("offlineshop.lnurl_response", item_id=self.id))

View file

@ -124,7 +124,8 @@ new Vue({
description, description,
image, image,
price, price,
unit unit,
fiat_base_multiplier: unit == 'sat' ? 1 : 100
} }
try { try {

View file

@ -1,6 +1,7 @@
from http import HTTPStatus from http import HTTPStatus
from typing import Optional from typing import Optional
from fastapi import Query
from fastapi.params import Depends from fastapi.params import Depends
from lnurl.exceptions import InvalidUrl as LnurlInvalidUrl from lnurl.exceptions import InvalidUrl as LnurlInvalidUrl
from pydantic.main import BaseModel from pydantic.main import BaseModel
@ -34,7 +35,6 @@ async def api_shop_from_wallet(
): ):
shop = await get_or_create_shop_by_wallet(wallet.wallet.id) shop = await get_or_create_shop_by_wallet(wallet.wallet.id)
items = await get_items(shop.id) items = await get_items(shop.id)
try: try:
return { return {
**shop.dict(), **shop.dict(),
@ -51,8 +51,9 @@ class CreateItemsData(BaseModel):
name: str name: str
description: str description: str
image: Optional[str] image: Optional[str]
price: int price: float
unit: str unit: str
fiat_base_multiplier: int = Query(100, ge=1)
@offlineshop_ext.post("/api/v1/offlineshop/items") @offlineshop_ext.post("/api/v1/offlineshop/items")
@ -61,9 +62,18 @@ async def api_add_or_update_item(
data: CreateItemsData, item_id=None, wallet: WalletTypeInfo = Depends(get_key_type) data: CreateItemsData, item_id=None, wallet: WalletTypeInfo = Depends(get_key_type)
): ):
shop = await get_or_create_shop_by_wallet(wallet.wallet.id) shop = await get_or_create_shop_by_wallet(wallet.wallet.id)
if data.unit != "sat":
data.price = data.price * 100
if item_id == None: if item_id == None:
await add_item( await add_item(
shop.id, data.name, data.description, data.image, data.price, data.unit shop.id,
data.name,
data.description,
data.image,
data.price,
data.unit,
data.fiat_base_multiplier,
) )
return HTMLResponse(status_code=HTTPStatus.CREATED) return HTMLResponse(status_code=HTTPStatus.CREATED)
else: else:
@ -75,6 +85,7 @@ async def api_add_or_update_item(
data.image, data.image,
data.price, data.price,
data.unit, data.unit,
data.fiat_base_multiplier,
) )

Binary file not shown.

View file

@ -1,9 +1,8 @@
import psycopg2
import sqlite3
import os
import argparse import argparse
import os
import sqlite3
import psycopg2
from environs import Env # type: ignore from environs import Env # type: ignore
env = Env() env = Env()
@ -540,8 +539,8 @@ def migrate_ext(sqlite_db_file, schema, ignore_missing=True):
# ITEMS # ITEMS
res = sq.execute("SELECT * FROM items;") res = sq.execute("SELECT * FROM items;")
q = f""" q = f"""
INSERT INTO offlineshop.items (shop, id, name, description, image, enabled, price, unit) INSERT INTO offlineshop.items (shop, id, name, description, image, enabled, price, unit, fiat_base_multiplier)
VALUES (%s, %s, %s, %s, %s, %s::boolean, %s, %s); VALUES (%s, %s, %s, %s, %s, %s::boolean, %s, %s, %s);
""" """
items = res.fetchall() items = res.fetchall()
insert_to_pg(q, items) insert_to_pg(q, items)