remove pyproject artifacts
import conv fix postgres url fix migration fixup timestamp postgresql fixup more timestamp fix migrations fix migrations fix statement add psycopg2 for conv tests expiry timestamp flaku fix expiry more wait parse to int remove time from filters
This commit is contained in:
@@ -46,7 +46,10 @@ runs:
|
|||||||
|
|
||||||
- name: Install the project dependencies
|
- name: Install the project dependencies
|
||||||
shell: bash
|
shell: bash
|
||||||
run: poetry install
|
run: |
|
||||||
|
poetry install
|
||||||
|
# needed for conv tests
|
||||||
|
poetry add psycopg2-binary
|
||||||
|
|
||||||
- name: Use Node.js ${{ inputs.node-version }}
|
- name: Use Node.js ${{ inputs.node-version }}
|
||||||
if: ${{ (inputs.npm == 'true') }}
|
if: ${{ (inputs.npm == 'true') }}
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ jobs:
|
|||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
python-version: ["3.9", "3.10"]
|
python-version: ["3.9", "3.10"]
|
||||||
db-url: ["", "postgres://lnbits:lnbits@0.0.0.0:5432/lnbits"]
|
db-url: ["", "postgresql+asyncpg://lnbits:lnbits@0.0.0.0:5432/lnbits"]
|
||||||
uses: ./.github/workflows/tests.yml
|
uses: ./.github/workflows/tests.yml
|
||||||
with:
|
with:
|
||||||
custom-pytest: "poetry run pytest tests/api"
|
custom-pytest: "poetry run pytest tests/api"
|
||||||
@@ -31,7 +31,7 @@ jobs:
|
|||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
python-version: ["3.9", "3.10"]
|
python-version: ["3.9", "3.10"]
|
||||||
db-url: ["", "postgres://lnbits:lnbits@0.0.0.0:5432/lnbits"]
|
db-url: ["", "postgresql+asyncpg://lnbits:lnbits@0.0.0.0:5432/lnbits"]
|
||||||
uses: ./.github/workflows/tests.yml
|
uses: ./.github/workflows/tests.yml
|
||||||
with:
|
with:
|
||||||
custom-pytest: "poetry run pytest tests/wallets"
|
custom-pytest: "poetry run pytest tests/wallets"
|
||||||
@@ -45,7 +45,7 @@ jobs:
|
|||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
python-version: ["3.9", "3.10"]
|
python-version: ["3.9", "3.10"]
|
||||||
db-url: ["", "postgres://lnbits:lnbits@0.0.0.0:5432/lnbits"]
|
db-url: ["", "postgresql+asyncpg://lnbits:lnbits@0.0.0.0:5432/lnbits"]
|
||||||
uses: ./.github/workflows/tests.yml
|
uses: ./.github/workflows/tests.yml
|
||||||
with:
|
with:
|
||||||
custom-pytest: "poetry run pytest tests/unit"
|
custom-pytest: "poetry run pytest tests/unit"
|
||||||
|
|||||||
@@ -74,10 +74,10 @@ test-migration:
|
|||||||
timeout 5s poetry run lnbits --host 0.0.0.0 --port 5002 || code=$?; if [[ $code -ne 124 && $code -ne 0 ]]; then exit $code; fi
|
timeout 5s poetry run lnbits --host 0.0.0.0 --port 5002 || code=$?; if [[ $code -ne 124 && $code -ne 0 ]]; then exit $code; fi
|
||||||
HOST=0.0.0.0 \
|
HOST=0.0.0.0 \
|
||||||
PORT=5002 \
|
PORT=5002 \
|
||||||
LNBITS_DATABASE_URL="postgres://lnbits:lnbits@localhost:5432/migration" \
|
LNBITS_DATABASE_URL="postgresql+asyncpg://lnbits:lnbits@localhost:5432/migration" \
|
||||||
timeout 5s poetry run lnbits --host 0.0.0.0 --port 5002 || code=$?; if [[ $code -ne 124 && $code -ne 0 ]]; then exit $code; fi
|
timeout 5s poetry run lnbits --host 0.0.0.0 --port 5002 || code=$?; if [[ $code -ne 124 && $code -ne 0 ]]; then exit $code; fi
|
||||||
LNBITS_DATA_FOLDER="./tests/data" \
|
LNBITS_DATA_FOLDER="./tests/data" \
|
||||||
LNBITS_DATABASE_URL="postgres://lnbits:lnbits@localhost:5432/migration" \
|
LNBITS_DATABASE_URL="postgresql+asyncpg://lnbits:lnbits@localhost:5432/migration" \
|
||||||
poetry run python tools/conv.py
|
poetry run python tools/conv.py
|
||||||
|
|
||||||
migration:
|
migration:
|
||||||
|
|||||||
+29
-29
@@ -53,10 +53,11 @@ async def create_account(
|
|||||||
user_id = user_id or uuid4().hex
|
user_id = user_id or uuid4().hex
|
||||||
extra = json.dumps(dict(user_config)) if user_config else "{}"
|
extra = json.dumps(dict(user_config)) if user_config else "{}"
|
||||||
now = int(time())
|
now = int(time())
|
||||||
|
now_ph = db.timestamp_placeholder("now")
|
||||||
await (conn or db).execute(
|
await (conn or db).execute(
|
||||||
"""
|
f"""
|
||||||
INSERT INTO accounts (id, username, pass, email, extra, created_at, updated_at)
|
INSERT INTO accounts (id, username, pass, email, extra, created_at, updated_at)
|
||||||
VALUES (:user, :username, :password, :email, :extra, :now, :now)
|
VALUES (:user, :username, :password, :email, :extra, {now_ph}, {now_ph})
|
||||||
""",
|
""",
|
||||||
{
|
{
|
||||||
"user": user_id,
|
"user": user_id,
|
||||||
@@ -98,10 +99,11 @@ async def update_account(
|
|||||||
extra = user_config or user.config
|
extra = user_config or user.config
|
||||||
|
|
||||||
now = int(time())
|
now = int(time())
|
||||||
|
now_ph = db.timestamp_placeholder("now")
|
||||||
await db.execute(
|
await db.execute(
|
||||||
"""
|
f"""
|
||||||
UPDATE accounts SET (username, email, extra, updated_at) =
|
UPDATE accounts SET (username, email, extra, updated_at) =
|
||||||
(:username, :email, :extra, :now)
|
(:username, :email, :extra, {now_ph})
|
||||||
WHERE id = :user
|
WHERE id = :user
|
||||||
""",
|
""",
|
||||||
{
|
{
|
||||||
@@ -181,13 +183,13 @@ async def delete_accounts_no_wallets(
|
|||||||
) -> None:
|
) -> None:
|
||||||
delta = int(time()) - time_delta
|
delta = int(time()) - time_delta
|
||||||
await (conn or db).execute(
|
await (conn or db).execute(
|
||||||
"""
|
f"""
|
||||||
DELETE FROM accounts
|
DELETE FROM accounts
|
||||||
WHERE NOT EXISTS (
|
WHERE NOT EXISTS (
|
||||||
SELECT wallets.id FROM wallets WHERE wallets.user = accounts.id
|
SELECT wallets.id FROM wallets WHERE wallets.user = accounts.id
|
||||||
) AND (
|
) AND (
|
||||||
(updated_at is null AND created_at < :delta)
|
(updated_at is null AND created_at < :delta)
|
||||||
OR updated_at < :delta
|
OR updated_at < {db.timestamp_placeholder("delta")}
|
||||||
)
|
)
|
||||||
""",
|
""",
|
||||||
{"delta": delta},
|
{"delta": delta},
|
||||||
@@ -225,9 +227,10 @@ async def update_user_password(data: UpdateUserPassword) -> Optional[User]:
|
|||||||
pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")
|
pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")
|
||||||
|
|
||||||
now = int(time())
|
now = int(time())
|
||||||
|
now_ph = db.timestamp_placeholder("now")
|
||||||
await db.execute(
|
await db.execute(
|
||||||
"""
|
f"""
|
||||||
UPDATE accounts SET pass = :pass, updated_at = :now
|
UPDATE accounts SET pass = :pass, updated_at = {now_ph}
|
||||||
WHERE id = :user
|
WHERE id = :user
|
||||||
""",
|
""",
|
||||||
{
|
{
|
||||||
@@ -517,10 +520,11 @@ async def create_wallet(
|
|||||||
) -> Wallet:
|
) -> Wallet:
|
||||||
wallet_id = uuid4().hex
|
wallet_id = uuid4().hex
|
||||||
now = int(time())
|
now = int(time())
|
||||||
|
now_ph = db.timestamp_placeholder("now")
|
||||||
await (conn or db).execute(
|
await (conn or db).execute(
|
||||||
"""
|
f"""
|
||||||
INSERT INTO wallets (id, name, "user", adminkey, inkey, created_at, updated_at)
|
INSERT INTO wallets (id, name, "user", adminkey, inkey, created_at, updated_at)
|
||||||
VALUES (:wallet, :name, :user, :adminkey, :inkey, :now, :now)
|
VALUES (:wallet, :name, :user, :adminkey, :inkey, {now_ph}, {now_ph})
|
||||||
""",
|
""",
|
||||||
{
|
{
|
||||||
"wallet": wallet_id,
|
"wallet": wallet_id,
|
||||||
@@ -545,7 +549,7 @@ async def update_wallet(
|
|||||||
conn: Optional[Connection] = None,
|
conn: Optional[Connection] = None,
|
||||||
) -> Optional[Wallet]:
|
) -> Optional[Wallet]:
|
||||||
set_clause = []
|
set_clause = []
|
||||||
set_clause.append("updated_at = :now")
|
set_clause.append(f"updated_at = {db.timestamp_placeholder('now')}")
|
||||||
values: dict = {
|
values: dict = {
|
||||||
"wallet": wallet_id,
|
"wallet": wallet_id,
|
||||||
"now": int(time()),
|
"now": int(time()),
|
||||||
@@ -576,9 +580,9 @@ async def delete_wallet(
|
|||||||
) -> None:
|
) -> None:
|
||||||
now = int(time())
|
now = int(time())
|
||||||
await (conn or db).execute(
|
await (conn or db).execute(
|
||||||
"""
|
f"""
|
||||||
UPDATE wallets
|
UPDATE wallets
|
||||||
SET deleted = :deleted, updated_at = :now
|
SET deleted = :deleted, updated_at = {db.timestamp_placeholder('now')}
|
||||||
WHERE id = :wallet AND "user" = :user
|
WHERE id = :wallet AND "user" = :user
|
||||||
""",
|
""",
|
||||||
{"wallet": wallet_id, "user": user_id, "deleted": deleted, "now": now},
|
{"wallet": wallet_id, "user": user_id, "deleted": deleted, "now": now},
|
||||||
@@ -599,9 +603,9 @@ async def delete_wallet_by_id(
|
|||||||
) -> Optional[int]:
|
) -> Optional[int]:
|
||||||
now = int(time())
|
now = int(time())
|
||||||
result = await (conn or db).execute(
|
result = await (conn or db).execute(
|
||||||
"""
|
f"""
|
||||||
UPDATE wallets
|
UPDATE wallets
|
||||||
SET deleted = true, updated_at = :now
|
SET deleted = true, updated_at = {db.timestamp_placeholder('now')}
|
||||||
WHERE id = :wallet
|
WHERE id = :wallet
|
||||||
""",
|
""",
|
||||||
{"wallet": wallet_id, "now": now},
|
{"wallet": wallet_id, "now": now},
|
||||||
@@ -771,7 +775,7 @@ async def get_payments_paginated(
|
|||||||
clause: List[str] = []
|
clause: List[str] = []
|
||||||
|
|
||||||
if since is not None:
|
if since is not None:
|
||||||
clause.append("time > :time")
|
clause.append(f"time > {db.timestamp_placeholder('time')}")
|
||||||
|
|
||||||
if wallet_id:
|
if wallet_id:
|
||||||
clause.append("wallet = :wallet")
|
clause.append("wallet = :wallet")
|
||||||
@@ -858,7 +862,7 @@ async def delete_expired_invoices(
|
|||||||
f"""
|
f"""
|
||||||
DELETE FROM apipayments
|
DELETE FROM apipayments
|
||||||
WHERE status = '{PaymentState.PENDING}' AND amount > 0
|
WHERE status = '{PaymentState.PENDING}' AND amount > 0
|
||||||
AND time < :delta
|
AND time < {db.timestamp_placeholder("delta")}
|
||||||
""",
|
""",
|
||||||
{"delta": int(time() - 2592000)},
|
{"delta": int(time() - 2592000)},
|
||||||
)
|
)
|
||||||
@@ -867,7 +871,7 @@ async def delete_expired_invoices(
|
|||||||
f"""
|
f"""
|
||||||
DELETE FROM apipayments
|
DELETE FROM apipayments
|
||||||
WHERE status = '{PaymentState.PENDING}' AND amount > 0
|
WHERE status = '{PaymentState.PENDING}' AND amount > 0
|
||||||
AND expiry < :now
|
AND expiry < {db.timestamp_placeholder("now")}
|
||||||
""",
|
""",
|
||||||
{"now": int(time())},
|
{"now": int(time())},
|
||||||
)
|
)
|
||||||
@@ -898,13 +902,14 @@ async def create_payment(
|
|||||||
previous_payment = await get_standalone_payment(checking_id, conn=conn)
|
previous_payment = await get_standalone_payment(checking_id, conn=conn)
|
||||||
assert previous_payment is None, "Payment already exists"
|
assert previous_payment is None, "Payment already exists"
|
||||||
|
|
||||||
|
expiry_ph = db.timestamp_placeholder("expiry")
|
||||||
await (conn or db).execute(
|
await (conn or db).execute(
|
||||||
"""
|
f"""
|
||||||
INSERT INTO apipayments
|
INSERT INTO apipayments
|
||||||
(wallet, checking_id, bolt11, hash, preimage,
|
(wallet, checking_id, bolt11, hash, preimage,
|
||||||
amount, status, memo, fee, extra, webhook, expiry, pending)
|
amount, status, memo, fee, extra, webhook, expiry, pending)
|
||||||
VALUES (:wallet, :checking_id, :bolt11, :hash, :preimage,
|
VALUES (:wallet, :checking_id, :bolt11, :hash, :preimage,
|
||||||
:amount, :status, :memo, :fee, :extra, :webhook, :expiry, :pending)
|
:amount, :status, :memo, :fee, :extra, :webhook, {expiry_ph}, :pending)
|
||||||
""",
|
""",
|
||||||
{
|
{
|
||||||
"wallet": wallet_id,
|
"wallet": wallet_id,
|
||||||
@@ -922,7 +927,7 @@ async def create_payment(
|
|||||||
else None
|
else None
|
||||||
),
|
),
|
||||||
"webhook": webhook,
|
"webhook": webhook,
|
||||||
"expiry": db.datetime_to_timestamp(expiry) if expiry else None,
|
"expiry": expiry if expiry else None,
|
||||||
"pending": False, # TODO: remove this in next release
|
"pending": False, # TODO: remove this in next release
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
@@ -1021,12 +1026,6 @@ async def get_payments_history(
|
|||||||
) -> List[PaymentHistoryPoint]:
|
) -> List[PaymentHistoryPoint]:
|
||||||
if not filters:
|
if not filters:
|
||||||
filters = Filters()
|
filters = Filters()
|
||||||
where = [f"(status = '{PaymentState.SUCCESS}' OR amount < 0)"]
|
|
||||||
values: dict = {
|
|
||||||
"wallet": wallet_id,
|
|
||||||
}
|
|
||||||
if wallet_id:
|
|
||||||
where.append("wallet = :wallet")
|
|
||||||
|
|
||||||
if DB_TYPE == SQLITE and group in sqlite_formats:
|
if DB_TYPE == SQLITE and group in sqlite_formats:
|
||||||
date_trunc = f"strftime('{sqlite_formats[group]}', time, 'unixepoch')"
|
date_trunc = f"strftime('{sqlite_formats[group]}', time, 'unixepoch')"
|
||||||
@@ -1041,11 +1040,12 @@ async def get_payments_history(
|
|||||||
SUM(CASE WHEN amount > 0 THEN amount ELSE 0 END) income,
|
SUM(CASE WHEN amount > 0 THEN amount ELSE 0 END) income,
|
||||||
SUM(CASE WHEN amount < 0 THEN abs(amount) + abs(fee) ELSE 0 END) spending
|
SUM(CASE WHEN amount < 0 THEN abs(amount) + abs(fee) ELSE 0 END) spending
|
||||||
FROM apipayments
|
FROM apipayments
|
||||||
{filters.where(where)}
|
WHERE wallet = :wallet AND (status = '{PaymentState.SUCCESS}' OR amount < 0)
|
||||||
GROUP BY date
|
GROUP BY date
|
||||||
ORDER BY date DESC
|
ORDER BY date DESC
|
||||||
""",
|
""",
|
||||||
filters.values(values),
|
{"wallet": wallet_id},
|
||||||
|
# filters.values(values),
|
||||||
)
|
)
|
||||||
if wallet_id:
|
if wallet_id:
|
||||||
wallet = await get_wallet(wallet_id)
|
wallet = await get_wallet(wallet_id)
|
||||||
|
|||||||
+46
-51
@@ -1,4 +1,3 @@
|
|||||||
import datetime
|
|
||||||
from time import time
|
from time import time
|
||||||
|
|
||||||
from loguru import logger
|
from loguru import logger
|
||||||
@@ -102,7 +101,7 @@ async def m002_add_fields_to_apipayments(db):
|
|||||||
|
|
||||||
import json
|
import json
|
||||||
|
|
||||||
rows = await (await db.execute("SELECT * FROM apipayments")).fetchall()
|
rows = await db.fetchall("SELECT * FROM apipayments")
|
||||||
for row in rows:
|
for row in rows:
|
||||||
if not row["memo"] or not row["memo"].startswith("#"):
|
if not row["memo"] or not row["memo"].startswith("#"):
|
||||||
continue
|
continue
|
||||||
@@ -113,15 +112,15 @@ async def m002_add_fields_to_apipayments(db):
|
|||||||
new = row["memo"][len(prefix) :]
|
new = row["memo"][len(prefix) :]
|
||||||
await db.execute(
|
await db.execute(
|
||||||
"""
|
"""
|
||||||
UPDATE apipayments SET extra = ?, memo = ?
|
UPDATE apipayments SET extra = :extra, memo = :memo1
|
||||||
WHERE checking_id = ? AND memo = ?
|
WHERE checking_id = :checking_id AND memo = :memo2
|
||||||
""",
|
""",
|
||||||
(
|
{
|
||||||
json.dumps({"tag": ext}),
|
"extra": json.dumps({"tag": ext}),
|
||||||
new,
|
"memo1": new,
|
||||||
row["checking_id"],
|
"checking_id": row["checking_id"],
|
||||||
row["memo"],
|
"memo2": row["memo"],
|
||||||
),
|
},
|
||||||
)
|
)
|
||||||
break
|
break
|
||||||
except OperationalError:
|
except OperationalError:
|
||||||
@@ -212,19 +211,17 @@ async def m007_set_invoice_expiries(db):
|
|||||||
Precomputes invoice expiry for existing pending incoming payments.
|
Precomputes invoice expiry for existing pending incoming payments.
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
rows = await (
|
rows = await db.fetchall(
|
||||||
await db.execute(
|
f"""
|
||||||
f"""
|
SELECT bolt11, checking_id
|
||||||
SELECT bolt11, checking_id
|
FROM apipayments
|
||||||
FROM apipayments
|
WHERE pending = true
|
||||||
WHERE pending = true
|
AND amount > 0
|
||||||
AND amount > 0
|
AND bolt11 IS NOT NULL
|
||||||
AND bolt11 IS NOT NULL
|
AND expiry IS NULL
|
||||||
AND expiry IS NULL
|
AND time < {db.timestamp_now}
|
||||||
AND time < {db.timestamp_now}
|
"""
|
||||||
"""
|
)
|
||||||
)
|
|
||||||
).fetchall()
|
|
||||||
if len(rows):
|
if len(rows):
|
||||||
logger.info(f"Migration: Checking expiry of {len(rows)} invoices")
|
logger.info(f"Migration: Checking expiry of {len(rows)} invoices")
|
||||||
for i, (
|
for i, (
|
||||||
@@ -236,22 +233,17 @@ async def m007_set_invoice_expiries(db):
|
|||||||
if invoice.expiry is None:
|
if invoice.expiry is None:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
expiration_date = datetime.datetime.fromtimestamp(
|
expiration_date = invoice.date + invoice.expiry
|
||||||
invoice.date + invoice.expiry
|
|
||||||
)
|
|
||||||
logger.info(
|
logger.info(
|
||||||
f"Migration: {i+1}/{len(rows)} setting expiry of invoice"
|
f"Migration: {i+1}/{len(rows)} setting expiry of invoice"
|
||||||
f" {invoice.payment_hash} to {expiration_date}"
|
f" {invoice.payment_hash} to {expiration_date}"
|
||||||
)
|
)
|
||||||
await db.execute(
|
await db.execute(
|
||||||
"""
|
f"""
|
||||||
UPDATE apipayments SET expiry = ?
|
UPDATE apipayments SET expiry = {db.timestamp_placeholder('expiry')}
|
||||||
WHERE checking_id = ? AND amount > 0
|
WHERE checking_id = :checking_id AND amount > 0
|
||||||
""",
|
""",
|
||||||
(
|
{"expiry": expiration_date, "checking_id": checking_id},
|
||||||
db.datetime_to_timestamp(expiration_date),
|
|
||||||
checking_id,
|
|
||||||
),
|
|
||||||
)
|
)
|
||||||
except Exception:
|
except Exception:
|
||||||
continue
|
continue
|
||||||
@@ -347,17 +339,15 @@ async def m014_set_deleted_wallets(db):
|
|||||||
Sets deleted column to wallets.
|
Sets deleted column to wallets.
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
rows = await (
|
rows = await db.fetchall(
|
||||||
await db.execute(
|
"""
|
||||||
"""
|
SELECT *
|
||||||
SELECT *
|
FROM wallets
|
||||||
FROM wallets
|
WHERE user LIKE 'del:%'
|
||||||
WHERE user LIKE 'del:%'
|
AND adminkey LIKE 'del:%'
|
||||||
AND adminkey LIKE 'del:%'
|
AND inkey LIKE 'del:%'
|
||||||
AND inkey LIKE 'del:%'
|
"""
|
||||||
"""
|
)
|
||||||
)
|
|
||||||
).fetchall()
|
|
||||||
|
|
||||||
for row in rows:
|
for row in rows:
|
||||||
try:
|
try:
|
||||||
@@ -367,10 +357,15 @@ async def m014_set_deleted_wallets(db):
|
|||||||
await db.execute(
|
await db.execute(
|
||||||
"""
|
"""
|
||||||
UPDATE wallets SET
|
UPDATE wallets SET
|
||||||
"user" = ?, adminkey = ?, inkey = ?, deleted = true
|
"user" = :user, adminkey = :adminkey, inkey = :inkey, deleted = true
|
||||||
WHERE id = ?
|
WHERE id = :wallet
|
||||||
""",
|
""",
|
||||||
(user, adminkey, inkey, row[0]),
|
{
|
||||||
|
"user": user,
|
||||||
|
"adminkey": adminkey,
|
||||||
|
"inkey": inkey,
|
||||||
|
"wallet": row.get("id"),
|
||||||
|
},
|
||||||
)
|
)
|
||||||
except Exception:
|
except Exception:
|
||||||
continue
|
continue
|
||||||
@@ -456,17 +451,17 @@ async def m017_add_timestamp_columns_to_accounts_and_wallets(db):
|
|||||||
now = int(time())
|
now = int(time())
|
||||||
await db.execute(
|
await db.execute(
|
||||||
f"""
|
f"""
|
||||||
UPDATE wallets SET created_at = {db.timestamp_placeholder}
|
UPDATE wallets SET created_at = {db.timestamp_placeholder('now')}
|
||||||
WHERE created_at IS NULL
|
WHERE created_at IS NULL
|
||||||
""",
|
""",
|
||||||
(now,),
|
{"now": now},
|
||||||
)
|
)
|
||||||
await db.execute(
|
await db.execute(
|
||||||
f"""
|
f"""
|
||||||
UPDATE accounts SET created_at = {db.timestamp_placeholder}
|
UPDATE accounts SET created_at = {db.timestamp_placeholder('now')}
|
||||||
WHERE created_at IS NULL
|
WHERE created_at IS NULL
|
||||||
""",
|
""",
|
||||||
(now,),
|
{"now": now},
|
||||||
)
|
)
|
||||||
|
|
||||||
except OperationalError as exc:
|
except OperationalError as exc:
|
||||||
|
|||||||
+18
-12
@@ -40,13 +40,13 @@ else:
|
|||||||
DB_TYPE = SQLITE
|
DB_TYPE = SQLITE
|
||||||
|
|
||||||
|
|
||||||
def compat_timestamp_placeholder():
|
def compat_timestamp_placeholder(key: str):
|
||||||
if DB_TYPE == POSTGRES:
|
if DB_TYPE == POSTGRES:
|
||||||
return "to_timestamp(?)"
|
return f"to_timestamp(:{key})"
|
||||||
elif DB_TYPE == COCKROACH:
|
elif DB_TYPE == COCKROACH:
|
||||||
return "cast(? AS timestamp)"
|
return f"cast(:{key} AS timestamp)"
|
||||||
else:
|
else:
|
||||||
return "?"
|
return f":{key}"
|
||||||
|
|
||||||
|
|
||||||
def get_placeholder(model: Any, field: str) -> str:
|
def get_placeholder(model: Any, field: str) -> str:
|
||||||
@@ -111,9 +111,8 @@ class Compat:
|
|||||||
return "BIGINT"
|
return "BIGINT"
|
||||||
return "INT"
|
return "INT"
|
||||||
|
|
||||||
@property
|
def timestamp_placeholder(self, key: str) -> str:
|
||||||
def timestamp_placeholder(self) -> str:
|
return compat_timestamp_placeholder(key)
|
||||||
return compat_timestamp_placeholder()
|
|
||||||
|
|
||||||
|
|
||||||
class Connection(Compat):
|
class Connection(Compat):
|
||||||
@@ -257,7 +256,9 @@ class Database(Compat):
|
|||||||
f = "%Y-%m-%d %H:%M:%S.%f"
|
f = "%Y-%m-%d %H:%M:%S.%f"
|
||||||
if "." not in value:
|
if "." not in value:
|
||||||
f = "%Y-%m-%d %H:%M:%S"
|
f = "%Y-%m-%d %H:%M:%S"
|
||||||
return time.mktime(datetime.datetime.strptime(value, f).timetuple())
|
return int(
|
||||||
|
time.mktime(datetime.datetime.strptime(value, f).timetuple())
|
||||||
|
)
|
||||||
|
|
||||||
dbapi_connection.run_async(
|
dbapi_connection.run_async(
|
||||||
lambda connection: connection.set_type_codec(
|
lambda connection: connection.set_type_codec(
|
||||||
@@ -420,7 +421,7 @@ class Filter(BaseModel, Generic[TFilterModel]):
|
|||||||
validated, errors = compare_field.validate(raw_value, {}, loc="none")
|
validated, errors = compare_field.validate(raw_value, {}, loc="none")
|
||||||
if errors:
|
if errors:
|
||||||
raise ValidationError(errors=[errors], model=model)
|
raise ValidationError(errors=[errors], model=model)
|
||||||
values[field](validated)
|
values[field] = validated
|
||||||
else:
|
else:
|
||||||
raise ValueError("Unknown filter field")
|
raise ValueError("Unknown filter field")
|
||||||
|
|
||||||
@@ -428,9 +429,14 @@ class Filter(BaseModel, Generic[TFilterModel]):
|
|||||||
|
|
||||||
@property
|
@property
|
||||||
def statement(self):
|
def statement(self):
|
||||||
if self.op in (Operator.INCLUDE, Operator.EXCLUDE):
|
if self.op in (Operator.INCLUDE, Operator.EXCLUDE) and self.values:
|
||||||
placeholders = ", ".join([])
|
placeholders = []
|
||||||
stmt = [f"{self.field} {self.op.as_sql} ({placeholders})"]
|
for key in self.values.keys():
|
||||||
|
if self.model and self.model.__fields__[key].type_ == datetime.datetime:
|
||||||
|
placeholders.append(compat_timestamp_placeholder(self.field))
|
||||||
|
else:
|
||||||
|
placeholders.append(f":{key}")
|
||||||
|
stmt = [f"{self.field} {self.op.as_sql} ({', '.join(placeholders)})"]
|
||||||
else:
|
else:
|
||||||
stmt = [f"{self.field} {self.op.as_sql} :{self.field}"]
|
stmt = [f"{self.field} {self.op.as_sql} :{self.field}"]
|
||||||
return " OR ".join(stmt)
|
return " OR ".join(stmt)
|
||||||
|
|||||||
@@ -127,7 +127,6 @@ module = [
|
|||||||
"secp256k1.*",
|
"secp256k1.*",
|
||||||
"uvicorn.*",
|
"uvicorn.*",
|
||||||
"sqlalchemy.*",
|
"sqlalchemy.*",
|
||||||
"sqlalchemy_aio.*",
|
|
||||||
"websocket.*",
|
"websocket.*",
|
||||||
"websockets.*",
|
"websockets.*",
|
||||||
"pyqrcode.*",
|
"pyqrcode.*",
|
||||||
@@ -137,7 +136,6 @@ module = [
|
|||||||
"bolt11.*",
|
"bolt11.*",
|
||||||
"bitstring.*",
|
"bitstring.*",
|
||||||
"ecdsa.*",
|
"ecdsa.*",
|
||||||
"psycopg2.*",
|
|
||||||
"pyngrok.*",
|
"pyngrok.*",
|
||||||
"pyln.client.*",
|
"pyln.client.*",
|
||||||
"py_vapid.*",
|
"py_vapid.*",
|
||||||
|
|||||||
@@ -368,10 +368,10 @@ async def test_get_payments_history(client, adminkey_headers_from, fake_payments
|
|||||||
data = response.json()
|
data = response.json()
|
||||||
assert len(data) == 1
|
assert len(data) == 1
|
||||||
assert data[0]["spending"] == sum(
|
assert data[0]["spending"] == sum(
|
||||||
payment.amount * 1000 for payment in fake_data if payment.out
|
int(payment.amount * 1000) for payment in fake_data if payment.out
|
||||||
)
|
)
|
||||||
assert data[0]["income"] == sum(
|
assert data[0]["income"] == sum(
|
||||||
payment.amount * 1000 for payment in fake_data if not payment.out
|
int(payment.amount * 1000) for payment in fake_data if not payment.out
|
||||||
)
|
)
|
||||||
|
|
||||||
response = await client.get(
|
response = await client.get(
|
||||||
|
|||||||
+7
-8
@@ -22,7 +22,7 @@ from lnbits.core.crud import (
|
|||||||
from lnbits.core.models import CreateInvoice, PaymentState
|
from lnbits.core.models import CreateInvoice, PaymentState
|
||||||
from lnbits.core.services import update_wallet_balance
|
from lnbits.core.services import update_wallet_balance
|
||||||
from lnbits.core.views.payment_api import api_payments_create_invoice
|
from lnbits.core.views.payment_api import api_payments_create_invoice
|
||||||
from lnbits.db import DB_TYPE, SQLITE, Database
|
from lnbits.db import Database
|
||||||
from lnbits.settings import settings
|
from lnbits.settings import settings
|
||||||
from tests.helpers import (
|
from tests.helpers import (
|
||||||
get_random_invoice_data,
|
get_random_invoice_data,
|
||||||
@@ -182,9 +182,8 @@ async def invoice(to_wallet):
|
|||||||
async def fake_payments(client, adminkey_headers_from):
|
async def fake_payments(client, adminkey_headers_from):
|
||||||
# Because sqlite only stores timestamps with milliseconds
|
# Because sqlite only stores timestamps with milliseconds
|
||||||
# we have to wait a second to ensure a different timestamp than previous invoices
|
# we have to wait a second to ensure a different timestamp than previous invoices
|
||||||
if DB_TYPE == SQLITE:
|
await asyncio.sleep(1)
|
||||||
await asyncio.sleep(1)
|
ts = int(time())
|
||||||
ts = time()
|
|
||||||
|
|
||||||
fake_data = [
|
fake_data = [
|
||||||
CreateInvoice(amount=10, memo="aaaa", out=False),
|
CreateInvoice(amount=10, memo="aaaa", out=False),
|
||||||
@@ -197,9 +196,9 @@ async def fake_payments(client, adminkey_headers_from):
|
|||||||
"/api/v1/payments", headers=adminkey_headers_from, json=invoice.dict()
|
"/api/v1/payments", headers=adminkey_headers_from, json=invoice.dict()
|
||||||
)
|
)
|
||||||
assert response.is_success
|
assert response.is_success
|
||||||
await update_payment_status(
|
data = response.json()
|
||||||
response.json()["checking_id"], status=PaymentState.SUCCESS
|
assert data["checking_id"]
|
||||||
)
|
await update_payment_status(data["checking_id"], status=PaymentState.SUCCESS)
|
||||||
|
|
||||||
params = {"time[ge]": ts, "time[le]": time()}
|
params = {"time[ge]": ts, "time[le]": int(time())}
|
||||||
return fake_data, params
|
return fake_data, params
|
||||||
|
|||||||
@@ -14,8 +14,8 @@ from lnbits.db import POSTGRES
|
|||||||
@pytest.mark.asyncio
|
@pytest.mark.asyncio
|
||||||
async def test_date_conversion(db):
|
async def test_date_conversion(db):
|
||||||
if db.type == POSTGRES:
|
if db.type == POSTGRES:
|
||||||
row = await db.fetchone("SELECT now()::date")
|
row = await db.fetchone("SELECT now()::date as now")
|
||||||
assert row and isinstance(row[0], date)
|
assert row and isinstance(row.get("now"), date)
|
||||||
|
|
||||||
|
|
||||||
# make test to create wallet and delete wallet
|
# make test to create wallet and delete wallet
|
||||||
|
|||||||
+1
-1
@@ -12,7 +12,7 @@ from typing import List, Optional
|
|||||||
from lnbits.settings import settings
|
from lnbits.settings import settings
|
||||||
|
|
||||||
try:
|
try:
|
||||||
import psycopg2
|
import psycopg2 # type: ignore
|
||||||
except ImportError:
|
except ImportError:
|
||||||
print("Please install psycopg2")
|
print("Please install psycopg2")
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|||||||
Reference in New Issue
Block a user