feat: add created_at and updated_at to wallets and accounts (#2177)
* feat: add `created_at` and `updated_at` to wallets and accounts the title says it all :) * fixup! * nitpicks :) * fixup! * sqlite fix * sqlite compat * fixup! * mypy * revert db py * motorinas suggestions * int(time()) proper default values in migration * uncomment migration * use now = int(time()) idiom to make code more readable also this fixes the issue where time() is called multiple times providing different return values for multiple invocations --------- Co-authored-by: Pavol Rusnak <pavol@rusnak.io>
This commit is contained in:
parent
4e55ea18e5
commit
815c3e61e4
4 changed files with 137 additions and 20 deletions
|
|
@ -1,5 +1,6 @@
|
||||||
import datetime
|
import datetime
|
||||||
import json
|
import json
|
||||||
|
from time import time
|
||||||
from typing import Any, Dict, List, Literal, Optional
|
from typing import Any, Dict, List, Literal, Optional
|
||||||
from urllib.parse import urlparse
|
from urllib.parse import urlparse
|
||||||
from uuid import UUID, uuid4
|
from uuid import UUID, uuid4
|
||||||
|
|
@ -51,10 +52,13 @@ async def create_user(
|
||||||
pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")
|
pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")
|
||||||
|
|
||||||
user_id = uuid4().hex
|
user_id = uuid4().hex
|
||||||
|
tsph = db.timestamp_placeholder
|
||||||
|
now = int(time())
|
||||||
await db.execute(
|
await db.execute(
|
||||||
"""
|
f"""
|
||||||
INSERT INTO accounts (id, email, username, pass, extra)
|
INSERT INTO accounts
|
||||||
VALUES (?, ?, ?, ?, ?)
|
(id, email, username, pass, extra, created_at, updated_at)
|
||||||
|
VALUES (?, ?, ?, ?, ?, {tsph}, {tsph})
|
||||||
""",
|
""",
|
||||||
(
|
(
|
||||||
user_id,
|
user_id,
|
||||||
|
|
@ -62,6 +66,8 @@ async def create_user(
|
||||||
data.username,
|
data.username,
|
||||||
pwd_context.hash(data.password),
|
pwd_context.hash(data.password),
|
||||||
json.dumps(dict(user_config)) if user_config else "{}",
|
json.dumps(dict(user_config)) if user_config else "{}",
|
||||||
|
now,
|
||||||
|
now,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
new_account = await get_account(user_id=user_id)
|
new_account = await get_account(user_id=user_id)
|
||||||
|
|
@ -82,9 +88,13 @@ async def create_account(
|
||||||
user_id = uuid4().hex
|
user_id = 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())
|
||||||
await (conn or db).execute(
|
await (conn or db).execute(
|
||||||
"INSERT INTO accounts (id, email, extra) VALUES (?, ?, ?)",
|
f"""
|
||||||
(user_id, email, extra),
|
INSERT INTO accounts (id, email, extra, created_at, updated_at)
|
||||||
|
VALUES (?, ?, ?, {db.timestamp_placeholder}, {db.timestamp_placeholder})
|
||||||
|
""",
|
||||||
|
(user_id, email, extra, now, now),
|
||||||
)
|
)
|
||||||
|
|
||||||
new_account = await get_account(user_id=user_id, conn=conn)
|
new_account = await get_account(user_id=user_id, conn=conn)
|
||||||
|
|
@ -116,12 +126,20 @@ async def update_account(
|
||||||
email = user.email or email
|
email = user.email or email
|
||||||
extra = user_config or user.config
|
extra = user_config or user.config
|
||||||
|
|
||||||
|
now = int(time())
|
||||||
await db.execute(
|
await db.execute(
|
||||||
"""
|
f"""
|
||||||
UPDATE accounts SET (username, email, extra) = (?, ?, ?)
|
UPDATE accounts SET (username, email, extra, updated_at) =
|
||||||
|
(?, ?, ?, {db.timestamp_placeholder})
|
||||||
WHERE id = ?
|
WHERE id = ?
|
||||||
""",
|
""",
|
||||||
(username, email, json.dumps(dict(extra)) if extra else "{}", user_id),
|
(
|
||||||
|
username,
|
||||||
|
email,
|
||||||
|
json.dumps(dict(extra)) if extra else "{}",
|
||||||
|
now,
|
||||||
|
user_id,
|
||||||
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
user = await get_user(user_id)
|
user = await get_user(user_id)
|
||||||
|
|
@ -133,7 +151,7 @@ async def get_account(
|
||||||
user_id: str, conn: Optional[Connection] = None
|
user_id: str, conn: Optional[Connection] = None
|
||||||
) -> Optional[User]:
|
) -> Optional[User]:
|
||||||
row = await (conn or db).fetchone(
|
row = await (conn or db).fetchone(
|
||||||
"SELECT id, email, username FROM accounts WHERE id = ?",
|
"SELECT id, email, username, created_at, updated_at FROM accounts WHERE id = ?",
|
||||||
(user_id,),
|
(user_id,),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -172,10 +190,15 @@ 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())
|
||||||
await db.execute(
|
await db.execute(
|
||||||
"UPDATE accounts SET pass = ? WHERE id = ?",
|
f"""
|
||||||
|
UPDATE accounts SET pass = ?, updated_at = {db.timestamp_placeholder}
|
||||||
|
WHERE id = ?
|
||||||
|
""",
|
||||||
(
|
(
|
||||||
pwd_context.hash(data.password),
|
pwd_context.hash(data.password),
|
||||||
|
now,
|
||||||
data.user_id,
|
data.user_id,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
@ -189,7 +212,10 @@ async def get_account_by_username(
|
||||||
username: str, conn: Optional[Connection] = None
|
username: str, conn: Optional[Connection] = None
|
||||||
) -> Optional[User]:
|
) -> Optional[User]:
|
||||||
row = await (conn or db).fetchone(
|
row = await (conn or db).fetchone(
|
||||||
"SELECT id, username, email FROM accounts WHERE username = ?",
|
"""
|
||||||
|
SELECT id, username, email, created_at, updated_at
|
||||||
|
FROM accounts WHERE username = ?
|
||||||
|
""",
|
||||||
(username,),
|
(username,),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -200,7 +226,10 @@ async def get_account_by_email(
|
||||||
email: str, conn: Optional[Connection] = None
|
email: str, conn: Optional[Connection] = None
|
||||||
) -> Optional[User]:
|
) -> Optional[User]:
|
||||||
row = await (conn or db).fetchone(
|
row = await (conn or db).fetchone(
|
||||||
"SELECT id, username, email FROM accounts WHERE email = ?",
|
"""
|
||||||
|
SELECT id, username, email, created_at, updated_at
|
||||||
|
FROM accounts WHERE email = ?
|
||||||
|
""",
|
||||||
(email,),
|
(email,),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -218,7 +247,11 @@ async def get_account_by_username_or_email(
|
||||||
|
|
||||||
async def get_user(user_id: str, conn: Optional[Connection] = None) -> Optional[User]:
|
async def get_user(user_id: str, conn: Optional[Connection] = None) -> Optional[User]:
|
||||||
user = await (conn or db).fetchone(
|
user = await (conn or db).fetchone(
|
||||||
"SELECT id, email, username, pass, extra FROM accounts WHERE id = ?", (user_id,)
|
"""
|
||||||
|
SELECT id, email, username, pass, extra, created_at, updated_at
|
||||||
|
FROM accounts WHERE id = ?
|
||||||
|
""",
|
||||||
|
(user_id,),
|
||||||
)
|
)
|
||||||
|
|
||||||
if user:
|
if user:
|
||||||
|
|
@ -392,10 +425,11 @@ async def create_wallet(
|
||||||
conn: Optional[Connection] = None,
|
conn: Optional[Connection] = None,
|
||||||
) -> Wallet:
|
) -> Wallet:
|
||||||
wallet_id = uuid4().hex
|
wallet_id = uuid4().hex
|
||||||
|
now = int(time())
|
||||||
await (conn or db).execute(
|
await (conn or db).execute(
|
||||||
"""
|
f"""
|
||||||
INSERT INTO wallets (id, name, "user", adminkey, inkey)
|
INSERT INTO wallets (id, name, "user", adminkey, inkey, created_at, updated_at)
|
||||||
VALUES (?, ?, ?, ?, ?)
|
VALUES (?, ?, ?, ?, ?, {db.timestamp_placeholder}, {db.timestamp_placeholder})
|
||||||
""",
|
""",
|
||||||
(
|
(
|
||||||
wallet_id,
|
wallet_id,
|
||||||
|
|
@ -403,6 +437,8 @@ async def create_wallet(
|
||||||
user_id,
|
user_id,
|
||||||
uuid4().hex,
|
uuid4().hex,
|
||||||
uuid4().hex,
|
uuid4().hex,
|
||||||
|
now,
|
||||||
|
now,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -419,7 +455,10 @@ async def update_wallet(
|
||||||
conn: Optional[Connection] = None,
|
conn: Optional[Connection] = None,
|
||||||
) -> Optional[Wallet]:
|
) -> Optional[Wallet]:
|
||||||
set_clause = []
|
set_clause = []
|
||||||
values = []
|
values: list = []
|
||||||
|
set_clause.append(f"updated_at = {db.timestamp_placeholder}")
|
||||||
|
now = int(time())
|
||||||
|
values.append(now)
|
||||||
if name:
|
if name:
|
||||||
set_clause.append("name = ?")
|
set_clause.append("name = ?")
|
||||||
values.append(name)
|
values.append(name)
|
||||||
|
|
@ -441,13 +480,14 @@ async def update_wallet(
|
||||||
async def delete_wallet(
|
async def delete_wallet(
|
||||||
*, user_id: str, wallet_id: str, conn: Optional[Connection] = None
|
*, user_id: str, wallet_id: str, conn: Optional[Connection] = None
|
||||||
) -> None:
|
) -> None:
|
||||||
|
now = int(time())
|
||||||
await (conn or db).execute(
|
await (conn or db).execute(
|
||||||
"""
|
f"""
|
||||||
UPDATE wallets
|
UPDATE wallets
|
||||||
SET deleted = true
|
SET deleted = true, updated_at = {db.timestamp_placeholder}
|
||||||
WHERE id = ? AND "user" = ?
|
WHERE id = ? AND "user" = ?
|
||||||
""",
|
""",
|
||||||
(wallet_id, user_id),
|
(now, wallet_id, user_id),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
import datetime
|
import datetime
|
||||||
|
from time import time
|
||||||
|
|
||||||
from loguru import logger
|
from loguru import logger
|
||||||
from sqlalchemy.exc import OperationalError
|
from sqlalchemy.exc import OperationalError
|
||||||
|
|
@ -404,3 +405,69 @@ async def m016_add_username_column_to_accounts(db):
|
||||||
await db.execute("ALTER TABLE accounts ADD COLUMN extra TEXT")
|
await db.execute("ALTER TABLE accounts ADD COLUMN extra TEXT")
|
||||||
except OperationalError:
|
except OperationalError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
async def m017_add_timestamp_columns_to_accounts_and_wallets(db):
|
||||||
|
"""
|
||||||
|
Adds created_at and updated_at column to accounts and wallets.
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
await db.execute(
|
||||||
|
"ALTER TABLE accounts "
|
||||||
|
f"ADD COLUMN created_at TIMESTAMP DEFAULT {db.timestamp_column_default}"
|
||||||
|
)
|
||||||
|
await db.execute(
|
||||||
|
"ALTER TABLE accounts "
|
||||||
|
f"ADD COLUMN updated_at TIMESTAMP DEFAULT {db.timestamp_column_default}"
|
||||||
|
)
|
||||||
|
await db.execute(
|
||||||
|
"ALTER TABLE wallets "
|
||||||
|
f"ADD COLUMN created_at TIMESTAMP DEFAULT {db.timestamp_column_default}"
|
||||||
|
)
|
||||||
|
await db.execute(
|
||||||
|
"ALTER TABLE wallets "
|
||||||
|
f"ADD COLUMN updated_at TIMESTAMP DEFAULT {db.timestamp_column_default}"
|
||||||
|
)
|
||||||
|
|
||||||
|
# set their wallets created_at with the first payment
|
||||||
|
await db.execute(
|
||||||
|
"""
|
||||||
|
UPDATE wallets SET created_at = (
|
||||||
|
SELECT time FROM apipayments
|
||||||
|
WHERE apipayments.wallet = wallets.id
|
||||||
|
ORDER BY time ASC LIMIT 1
|
||||||
|
)
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
|
||||||
|
# then set their accounts created_at with the wallet
|
||||||
|
await db.execute(
|
||||||
|
"""
|
||||||
|
UPDATE accounts SET created_at = (
|
||||||
|
SELECT created_at FROM wallets
|
||||||
|
WHERE wallets.user = accounts.id
|
||||||
|
ORDER BY created_at ASC LIMIT 1
|
||||||
|
)
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
|
||||||
|
# set all to now where they are null
|
||||||
|
now = int(time())
|
||||||
|
await db.execute(
|
||||||
|
f"""
|
||||||
|
UPDATE wallets SET created_at = {db.timestamp_placeholder}
|
||||||
|
WHERE created_at IS NULL
|
||||||
|
""",
|
||||||
|
(now,),
|
||||||
|
)
|
||||||
|
await db.execute(
|
||||||
|
f"""
|
||||||
|
UPDATE accounts SET created_at = {db.timestamp_placeholder}
|
||||||
|
WHERE created_at IS NULL
|
||||||
|
""",
|
||||||
|
(now,),
|
||||||
|
)
|
||||||
|
|
||||||
|
except OperationalError as exc:
|
||||||
|
logger.error(f"Migration 17 failed: {exc}")
|
||||||
|
pass
|
||||||
|
|
|
||||||
|
|
@ -30,6 +30,8 @@ class Wallet(BaseModel):
|
||||||
currency: Optional[str]
|
currency: Optional[str]
|
||||||
balance_msat: int
|
balance_msat: int
|
||||||
deleted: bool
|
deleted: bool
|
||||||
|
created_at: Optional[int] = None
|
||||||
|
updated_at: Optional[int] = None
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def balance(self) -> int:
|
def balance(self) -> int:
|
||||||
|
|
@ -98,6 +100,8 @@ class User(BaseModel):
|
||||||
super_user: bool = False
|
super_user: bool = False
|
||||||
has_password: bool = False
|
has_password: bool = False
|
||||||
config: Optional[UserConfig] = None
|
config: Optional[UserConfig] = None
|
||||||
|
created_at: Optional[int] = None
|
||||||
|
updated_at: Optional[int] = None
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def wallet_ids(self) -> List[str]:
|
def wallet_ids(self) -> List[str]:
|
||||||
|
|
|
||||||
|
|
@ -91,6 +91,12 @@ class Compat:
|
||||||
return "(strftime('%s', 'now'))"
|
return "(strftime('%s', 'now'))"
|
||||||
return "<nothing>"
|
return "<nothing>"
|
||||||
|
|
||||||
|
@property
|
||||||
|
def timestamp_column_default(self) -> str:
|
||||||
|
if self.type in {POSTGRES, COCKROACH}:
|
||||||
|
return self.timestamp_now
|
||||||
|
return "NULL"
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def serial_primary_key(self) -> str:
|
def serial_primary_key(self) -> str:
|
||||||
if self.type in {POSTGRES, COCKROACH}:
|
if self.type in {POSTGRES, COCKROACH}:
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue