diff --git a/lnbits/core/crud.py b/lnbits/core/crud.py index a80fadf29..e5d4424fa 100644 --- a/lnbits/core/crud.py +++ b/lnbits/core/crud.py @@ -4,12 +4,14 @@ from typing import Any, Dict, List, Optional from urllib.parse import urlparse from uuid import uuid4 +import shortuuid + from lnbits import bolt11 from lnbits.db import COCKROACH, POSTGRES, Connection from lnbits.settings import AdminSettings, EditableSettings, SuperSettings, settings from . import db -from .models import BalanceCheck, Payment, User, Wallet +from .models import BalanceCheck, Payment, TinyURL, User, Wallet # accounts # -------- @@ -620,3 +622,44 @@ async def create_admin_settings(super_user: str, new_settings: dict): sql = f"INSERT INTO settings (super_user, editable_settings) VALUES (?, ?)" await db.execute(sql, (super_user, json.dumps(new_settings))) return await get_super_settings() + + +# tinyurl +# ------- + + +async def create_tinyurl(domain: str, endless: bool, wallet: str): + tinyurl_id = shortuuid.uuid()[:8] + await db.execute( + f"INSERT INTO tiny_url (id, url, endless, wallet) VALUES (?, ?, ?, ?)", + ( + tinyurl_id, + domain, + endless, + wallet, + ), + ) + return await get_tinyurl(tinyurl_id) + + +async def get_tinyurl(tinyurl_id: str) -> Optional[TinyURL]: + row = await db.fetchone( + f"SELECT * FROM tiny_url WHERE id = ?", + (tinyurl_id,), + ) + return TinyURL.from_row(row) if row else None + + +async def get_tinyurl_by_url(url: str) -> List[TinyURL]: + rows = await db.fetchall( + f"SELECT * FROM tiny_url WHERE url = ?", + (url,), + ) + return [TinyURL.from_row(row) for row in rows] + + +async def delete_tinyurl(tinyurl_id: str): + row = await db.execute( + f"DELETE FROM tiny_url WHERE id = ?", + (tinyurl_id,), + ) diff --git a/lnbits/core/migrations.py b/lnbits/core/migrations.py index 66254d11d..ccbd78717 100644 --- a/lnbits/core/migrations.py +++ b/lnbits/core/migrations.py @@ -269,3 +269,17 @@ async def m008_create_admin_settings_table(db): ); """ ) + + +async def m009_create_tinyurl_table(db): + await db.execute( + f""" + CREATE TABLE IF NOT EXISTS tiny_url ( + id TEXT PRIMARY KEY, + url TEXT, + endless BOOL NOT NULL DEFAULT false, + wallet TEXT, + time TIMESTAMP NOT NULL DEFAULT {db.timestamp_now} + ); + """ + ) diff --git a/lnbits/core/models.py b/lnbits/core/models.py index eca1bf50a..4def937e2 100644 --- a/lnbits/core/models.py +++ b/lnbits/core/models.py @@ -213,3 +213,15 @@ class BalanceCheck(BaseModel): @classmethod def from_row(cls, row: Row): return cls(wallet=row["wallet"], service=row["service"], url=row["url"]) + + +class TinyURL(BaseModel): + id: str + url: str + endless: bool + wallet: str + time: float + + @classmethod + def from_row(cls, row: Row): + return cls(**dict(row)) diff --git a/lnbits/core/views/api.py b/lnbits/core/views/api.py index d545df9a6..f614ba023 100644 --- a/lnbits/core/views/api.py +++ b/lnbits/core/views/api.py @@ -26,7 +26,7 @@ from loguru import logger from pydantic import BaseModel from pydantic.fields import Field from sse_starlette.sse import EventSourceResponse -from starlette.responses import StreamingResponse +from starlette.responses import RedirectResponse, StreamingResponse from lnbits import bolt11, lnurl from lnbits.core.models import Payment, Wallet @@ -47,8 +47,12 @@ from lnbits.utils.exchange_rates import ( from .. import core_app, db from ..crud import ( + create_tinyurl, + delete_tinyurl, get_payments, get_standalone_payment, + get_tinyurl, + get_tinyurl_by_url, get_total_balance, get_wallet_for_key, save_balance_check, @@ -706,3 +710,75 @@ async def websocket_update_get(item_id: str, data: str): return {"sent": True, "data": data} except: return {"sent": False, "data": data} + + +############################TINYURL################################## + + +@core_app.post("/api/v1/tinyurl") +async def api_create_tinyurl( + url: str, endless: bool = False, wallet: WalletTypeInfo = Depends(get_key_type) +): + tinyurls = await get_tinyurl_by_url(url) + try: + for tinyurl in tinyurls: + if tinyurl: + if tinyurl.wallet == wallet.wallet.inkey: + return tinyurl + return await create_tinyurl(url, endless, wallet.wallet.inkey) + except: + raise HTTPException( + status_code=HTTPStatus.BAD_REQUEST, detail="Unable to create tinyurl" + ) + + +@core_app.get("/api/v1/tinyurl/{tinyurl_id}") +async def api_get_tinyurl( + tinyurl_id: str, wallet: WalletTypeInfo = Depends(get_key_type) +): + try: + tinyurl = await get_tinyurl(tinyurl_id) + if tinyurl: + if tinyurl.wallet == wallet.wallet.inkey: + return tinyurl + raise HTTPException( + status_code=HTTPStatus.FORBIDDEN, detail="Wrong key provided." + ) + except: + raise HTTPException( + status_code=HTTPStatus.NOT_FOUND, detail="Unable to fetch tinyurl" + ) + + +@core_app.delete("/api/v1/tinyurl/{tinyurl_id}") +async def api_delete_tinyurl( + tinyurl_id: str, wallet: WalletTypeInfo = Depends(get_key_type) +): + try: + tinyurl = await get_tinyurl(tinyurl_id) + if tinyurl: + if tinyurl.wallet == wallet.wallet.inkey: + await delete_tinyurl(tinyurl_id) + return {"deleted": True} + raise HTTPException( + status_code=HTTPStatus.FORBIDDEN, detail="Wrong key provided." + ) + except: + raise HTTPException( + status_code=HTTPStatus.BAD_REQUEST, detail="Unable to delete" + ) + + +@core_app.get("/t/{tinyurl_id}") +async def api_tinyurl(tinyurl_id: str): + try: + tinyurl = await get_tinyurl(tinyurl_id) + if tinyurl: + response = RedirectResponse(url=tinyurl.url) + return response + else: + return + except: + raise HTTPException( + status_code=HTTPStatus.NOT_FOUND, detail="unable to find tinyurl" + )