diff --git a/lnbits/extensions/paywall/README.md b/lnbits/extensions/paywall/README.md deleted file mode 100644 index 738485e28..000000000 --- a/lnbits/extensions/paywall/README.md +++ /dev/null @@ -1,22 +0,0 @@ -# Paywall - -## Hide content behind a paywall, a user has to pay some amount to access your hidden content - -A Paywall is a way of restricting to content via a purchase or paid subscription. For example to read a determined blog post, or to continue reading further, to access a downloads area, etc... - -## Usage - -1. Create a paywall by clicking "NEW PAYWALL"\ - ![create new paywall](https://i.imgur.com/q0ZIekC.png) -2. Fill the options for your PAYWALL - - select the wallet - - set the link that will be unlocked after a successful payment - - give your paywall a _Title_ - - an optional small description - - and set an amount a user must pay to access the hidden content. Note this is the minimum amount, a user can over pay if they wish - - if _Remember payments_ is checked, a returning paying user won't have to pay again for the same content.\ - ![paywall config](https://i.imgur.com/CBW48F6.png) -3. You can then use your paywall link to secure your awesome content\ - ![paywall link](https://i.imgur.com/hDQmCDf.png) -4. When a user wants to access your hidden content, he can use the minimum amount or increase and click the "_Check icon_" to generate an invoice, user will then be redirected to the content page\ - ![user paywall view](https://i.imgur.com/3pLywkZ.png) diff --git a/lnbits/extensions/paywall/__init__.py b/lnbits/extensions/paywall/__init__.py deleted file mode 100644 index 5565a9349..000000000 --- a/lnbits/extensions/paywall/__init__.py +++ /dev/null @@ -1,25 +0,0 @@ -from fastapi import APIRouter -from starlette.staticfiles import StaticFiles - -from lnbits.db import Database -from lnbits.helpers import template_renderer - -db = Database("ext_paywall") - -paywall_ext: APIRouter = APIRouter(prefix="/paywall", tags=["Paywall"]) - -paywall_static_files = [ - { - "path": "/paywall/static", - "app": StaticFiles(directory="lnbits/extensions/paywall/static"), - "name": "paywall_static", - } -] - - -def paywall_renderer(): - return template_renderer(["lnbits/extensions/paywall/templates"]) - - -from .views import * # noqa: F401,F403 -from .views_api import * # noqa: F401,F403 diff --git a/lnbits/extensions/paywall/config.json b/lnbits/extensions/paywall/config.json deleted file mode 100644 index 749d19891..000000000 --- a/lnbits/extensions/paywall/config.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "name": "Paywall", - "short_description": "Create paywalls for content", - "tile": "/paywall/static/image/paywall.png", - "contributors": ["eillarra"] -} diff --git a/lnbits/extensions/paywall/crud.py b/lnbits/extensions/paywall/crud.py deleted file mode 100644 index cb9e210d5..000000000 --- a/lnbits/extensions/paywall/crud.py +++ /dev/null @@ -1,52 +0,0 @@ -from typing import List, Optional, Union - -from lnbits.helpers import urlsafe_short_hash - -from . import db -from .models import CreatePaywall, Paywall - - -async def create_paywall(wallet_id: str, data: CreatePaywall) -> Paywall: - paywall_id = urlsafe_short_hash() - await db.execute( - """ - INSERT INTO paywall.paywalls (id, wallet, url, memo, description, amount, remembers) - VALUES (?, ?, ?, ?, ?, ?, ?) - """, - ( - paywall_id, - wallet_id, - data.url, - data.memo, - data.description, - data.amount, - int(data.remembers), - ), - ) - - paywall = await get_paywall(paywall_id) - assert paywall, "Newly created paywall couldn't be retrieved" - return paywall - - -async def get_paywall(paywall_id: str) -> Optional[Paywall]: - row = await db.fetchone( - "SELECT * FROM paywall.paywalls WHERE id = ?", (paywall_id,) - ) - return Paywall.from_row(row) if row else None - - -async def get_paywalls(wallet_ids: Union[str, List[str]]) -> List[Paywall]: - if isinstance(wallet_ids, str): - wallet_ids = [wallet_ids] - - q = ",".join(["?"] * len(wallet_ids)) - rows = await db.fetchall( - f"SELECT * FROM paywall.paywalls WHERE wallet IN ({q})", (*wallet_ids,) - ) - - return [Paywall.from_row(row) for row in rows] - - -async def delete_paywall(paywall_id: str) -> None: - await db.execute("DELETE FROM paywall.paywalls WHERE id = ?", (paywall_id,)) diff --git a/lnbits/extensions/paywall/migrations.py b/lnbits/extensions/paywall/migrations.py deleted file mode 100644 index 9b3341fd4..000000000 --- a/lnbits/extensions/paywall/migrations.py +++ /dev/null @@ -1,63 +0,0 @@ -async def m001_initial(db): - """ - Initial paywalls table. - """ - await db.execute( - f""" - CREATE TABLE paywall.paywalls ( - id TEXT PRIMARY KEY, - wallet TEXT NOT NULL, - secret TEXT NOT NULL, - url TEXT NOT NULL, - memo TEXT NOT NULL, - amount {db.big_int} NOT NULL, - time TIMESTAMP NOT NULL DEFAULT """ - + db.timestamp_now - + """ - ); - """ - ) - - -async def m002_redux(db): - """ - Creates an improved paywalls table and migrates the existing data. - """ - await db.execute("ALTER TABLE paywall.paywalls RENAME TO paywalls_old") - await db.execute( - f""" - CREATE TABLE paywall.paywalls ( - id TEXT PRIMARY KEY, - wallet TEXT NOT NULL, - url TEXT NOT NULL, - memo TEXT NOT NULL, - description TEXT NULL, - amount {db.big_int} DEFAULT 0, - time TIMESTAMP NOT NULL DEFAULT """ - + db.timestamp_now - + """, - remembers INTEGER DEFAULT 0, - extras TEXT NULL - ); - """ - ) - - for row in [ - list(row) for row in await db.fetchall("SELECT * FROM paywall.paywalls_old") - ]: - await db.execute( - """ - INSERT INTO paywall.paywalls ( - id, - wallet, - url, - memo, - amount, - time - ) - VALUES (?, ?, ?, ?, ?, ?) - """, - (row[0], row[1], row[3], row[4], row[5], row[6]), - ) - - await db.execute("DROP TABLE paywall.paywalls_old") diff --git a/lnbits/extensions/paywall/models.py b/lnbits/extensions/paywall/models.py deleted file mode 100644 index 7082c5412..000000000 --- a/lnbits/extensions/paywall/models.py +++ /dev/null @@ -1,41 +0,0 @@ -import json -from sqlite3 import Row -from typing import Optional - -from fastapi import Query -from pydantic import BaseModel - - -class CreatePaywall(BaseModel): - url: str = Query(...) - memo: str = Query(...) - description: str = Query(None) - amount: int = Query(..., ge=0) - remembers: bool = Query(...) - - -class CreatePaywallInvoice(BaseModel): - amount: int = Query(..., ge=1) - - -class CheckPaywallInvoice(BaseModel): - payment_hash: str = Query(...) - - -class Paywall(BaseModel): - id: str - wallet: str - url: str - memo: str - description: Optional[str] - amount: int - time: int - remembers: bool - extras: Optional[dict] - - @classmethod - def from_row(cls, row: Row) -> "Paywall": - data = dict(row) - data["remembers"] = bool(data["remembers"]) - data["extras"] = json.loads(data["extras"]) if data["extras"] else None - return cls(**data) diff --git a/lnbits/extensions/paywall/static/image/paywall.png b/lnbits/extensions/paywall/static/image/paywall.png deleted file mode 100644 index 0331a953c..000000000 Binary files a/lnbits/extensions/paywall/static/image/paywall.png and /dev/null differ diff --git a/lnbits/extensions/paywall/templates/paywall/_api_docs.html b/lnbits/extensions/paywall/templates/paywall/_api_docs.html deleted file mode 100644 index 0fd8bdd3a..000000000 --- a/lnbits/extensions/paywall/templates/paywall/_api_docs.html +++ /dev/null @@ -1,148 +0,0 @@ - - - - - - GET /paywall/api/v1/paywalls -
Headers
- {"X-Api-Key": <invoice_key>}
-
Body (application/json)
-
- Returns 200 OK (application/json) -
- [<paywall_object>, ...] -
Curl example
- curl -X GET {{ request.base_url }}paywall/api/v1/paywalls -H - "X-Api-Key: {{ user.wallets[0].inkey }}" - -
-
-
- - - - POST /paywall/api/v1/paywalls -
Headers
- {"X-Api-Key": <admin_key>}
-
Body (application/json)
- {"amount": <integer>, "description": <string>, "memo": - <string>, "remembers": <boolean>, "url": - <string>} -
- Returns 201 CREATED (application/json) -
- {"amount": <integer>, "description": <string>, "id": - <string>, "memo": <string>, "remembers": <boolean>, - "time": <int>, "url": <string>, "wallet": - <string>} -
Curl example
- curl -X POST {{ request.base_url }}paywall/api/v1/paywalls -d - '{"url": <string>, "memo": <string>, "description": - <string>, "amount": <integer>, "remembers": - <boolean>}' -H "Content-type: application/json" -H "X-Api-Key: - {{ user.wallets[0].adminkey }}" - -
-
-
- - - - POST - /paywall/api/v1/paywalls/<paywall_id>/invoice -
Body (application/json)
- {"amount": <integer>} -
- Returns 201 CREATED (application/json) -
- {"payment_hash": <string>, "payment_request": - <string>} -
Curl example
- curl -X POST {{ request.base_url - }}paywall/api/v1/paywalls/<paywall_id>/invoice -d '{"amount": - <integer>}' -H "Content-type: application/json" - -
-
-
- - - - POST - /paywall/api/v1/paywalls/<paywall_id>/check_invoice -
Body (application/json)
- {"payment_hash": <string>} -
- Returns 200 OK (application/json) -
- {"paid": false}
- {"paid": true, "url": <string>, "remembers": - <boolean>} -
Curl example
- curl -X POST {{ request.base_url - }}paywall/api/v1/paywalls/<paywall_id>/check_invoice -d - '{"payment_hash": <string>}' -H "Content-type: application/json" - -
-
-
- - - - DELETE - /paywall/api/v1/paywalls/<paywall_id> -
Headers
- {"X-Api-Key": <admin_key>}
-
Returns 204 NO CONTENT
- -
Curl example
- curl -X DELETE {{ request.base_url - }}paywall/api/v1/paywalls/<paywall_id> -H "X-Api-Key: {{ - user.wallets[0].adminkey }}" - -
-
-
-
diff --git a/lnbits/extensions/paywall/templates/paywall/display.html b/lnbits/extensions/paywall/templates/paywall/display.html deleted file mode 100644 index a5edd1025..000000000 --- a/lnbits/extensions/paywall/templates/paywall/display.html +++ /dev/null @@ -1,168 +0,0 @@ -{% extends "public.html" %} {% block page %} -
-
- - -
{{ paywall.memo }}
- {% if paywall.description %} -

{{ paywall.description }}

- {% endif %} -
- - - -
- Send -
-
-
- - - - - -
- Copy invoice - Cancel -
-
-
-
- -

- You can access the URL behind this paywall:
- {% raw %}{{ redirectUrl }}{% endraw %} -

-
- Open URL -
-
-
-
-
-
-{% endblock %} {% block scripts %} - -{% endblock %} diff --git a/lnbits/extensions/paywall/templates/paywall/index.html b/lnbits/extensions/paywall/templates/paywall/index.html deleted file mode 100644 index 482d1465e..000000000 --- a/lnbits/extensions/paywall/templates/paywall/index.html +++ /dev/null @@ -1,312 +0,0 @@ -{% extends "base.html" %} {% from "macros.jinja" import window_vars with context -%} {% block page %} -
-
- - - New paywall - - - - - -
-
-
Paywalls
-
-
- Export to CSV -
-
- - {% raw %} - - - {% endraw %} - -
-
-
- -
- - -
- {{SITE_TITLE}} paywall extension -
-
- - - {% include "paywall/_api_docs.html" %} - -
-
- - - - - - - - - - - - - - - - - Remember payments - A succesful payment will be registered in the browser's - storage, so the user doesn't need to pay again to access the - URL. - - - -
- Create paywall - Cancel -
-
-
-
-
-{% endblock %} {% block scripts %} {{ window_vars(user) }} - -{% endblock %} diff --git a/lnbits/extensions/paywall/views.py b/lnbits/extensions/paywall/views.py deleted file mode 100644 index 340f23c39..000000000 --- a/lnbits/extensions/paywall/views.py +++ /dev/null @@ -1,30 +0,0 @@ -from http import HTTPStatus - -from fastapi import Depends -from starlette.exceptions import HTTPException -from starlette.requests import Request - -from lnbits.core.models import User -from lnbits.decorators import check_user_exists - -from . import paywall_ext, paywall_renderer -from .crud import get_paywall - - -@paywall_ext.get("/") -async def index(request: Request, user: User = Depends(check_user_exists)): - return paywall_renderer().TemplateResponse( - "paywall/index.html", {"request": request, "user": user.dict()} - ) - - -@paywall_ext.get("/{paywall_id}") -async def display(request: Request, paywall_id): - paywall = await get_paywall(paywall_id) - if not paywall: - raise HTTPException( - status_code=HTTPStatus.NOT_FOUND, detail="Paywall does not exist." - ) - return paywall_renderer().TemplateResponse( - "paywall/display.html", {"request": request, "paywall": paywall} - ) diff --git a/lnbits/extensions/paywall/views_api.py b/lnbits/extensions/paywall/views_api.py deleted file mode 100644 index 26480e016..000000000 --- a/lnbits/extensions/paywall/views_api.py +++ /dev/null @@ -1,105 +0,0 @@ -from http import HTTPStatus - -from fastapi import Depends, Query -from starlette.exceptions import HTTPException - -from lnbits.core.crud import get_user, get_wallet -from lnbits.core.services import check_transaction_status, create_invoice -from lnbits.decorators import WalletTypeInfo, get_key_type - -from . import paywall_ext -from .crud import create_paywall, delete_paywall, get_paywall, get_paywalls -from .models import CheckPaywallInvoice, CreatePaywall, CreatePaywallInvoice - - -@paywall_ext.get("/api/v1/paywalls") -async def api_paywalls( - wallet: WalletTypeInfo = Depends(get_key_type), all_wallets: bool = Query(False) -): - wallet_ids = [wallet.wallet.id] - - if all_wallets: - user = await get_user(wallet.wallet.user) - wallet_ids = user.wallet_ids if user else [] - - return [paywall.dict() for paywall in await get_paywalls(wallet_ids)] - - -@paywall_ext.post("/api/v1/paywalls") -async def api_paywall_create( - data: CreatePaywall, wallet: WalletTypeInfo = Depends(get_key_type) -): - paywall = await create_paywall(wallet_id=wallet.wallet.id, data=data) - return paywall.dict() - - -@paywall_ext.delete("/api/v1/paywalls/{paywall_id}") -async def api_paywall_delete( - paywall_id, wallet: WalletTypeInfo = Depends(get_key_type) -): - paywall = await get_paywall(paywall_id) - - if not paywall: - raise HTTPException( - status_code=HTTPStatus.NOT_FOUND, detail="Paywall does not exist." - ) - - if paywall.wallet != wallet.wallet.id: - raise HTTPException( - status_code=HTTPStatus.FORBIDDEN, detail="Not your paywall." - ) - - await delete_paywall(paywall_id) - return "", HTTPStatus.NO_CONTENT - - -@paywall_ext.post("/api/v1/paywalls/invoice/{paywall_id}") -async def api_paywall_create_invoice( - data: CreatePaywallInvoice, paywall_id: str = Query(None) -): - paywall = await get_paywall(paywall_id) - assert paywall - if data.amount < paywall.amount: - raise HTTPException( - status_code=HTTPStatus.BAD_REQUEST, - detail=f"Minimum amount is {paywall.amount} sat.", - ) - try: - amount = data.amount if data.amount > paywall.amount else paywall.amount - payment_hash, payment_request = await create_invoice( - wallet_id=paywall.wallet, - amount=amount, - memo=f"{paywall.memo}", - extra={"tag": "paywall"}, - ) - except Exception as e: - raise HTTPException(status_code=HTTPStatus.INTERNAL_SERVER_ERROR, detail=str(e)) - - return {"payment_hash": payment_hash, "payment_request": payment_request} - - -@paywall_ext.post("/api/v1/paywalls/check_invoice/{paywall_id}") -async def api_paywal_check_invoice( - data: CheckPaywallInvoice, paywall_id: str = Query(None) -): - paywall = await get_paywall(paywall_id) - payment_hash = data.payment_hash - if not paywall: - raise HTTPException( - status_code=HTTPStatus.NOT_FOUND, detail="Paywall does not exist." - ) - try: - status = await check_transaction_status(paywall.wallet, payment_hash) - is_paid = not status.pending - except Exception: - return {"paid": False} - - if is_paid: - wallet = await get_wallet(paywall.wallet) - assert wallet - payment = await wallet.get_payment(payment_hash) - assert payment - await payment.set_pending(False) - - return {"paid": True, "url": paywall.url, "remembers": paywall.remembers} - return {"paid": False}