Skip to content

Commit

Permalink
feat: improve codequality and CI (#25)
Browse files Browse the repository at this point in the history
* feat: improve codequality and CI
  • Loading branch information
dni authored Aug 30, 2024
1 parent 2812118 commit cc67520
Show file tree
Hide file tree
Showing 28 changed files with 3,113 additions and 291 deletions.
34 changes: 34 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
name: CI
on:
push:
branches:
- main
pull_request:

jobs:
lint:
uses: lnbits/lnbits/.github/workflows/lint.yml@dev
tests:
runs-on: ubuntu-latest
needs: [lint]
strategy:
matrix:
python-version: ['3.9', '3.10']
steps:
- uses: actions/checkout@v4
- uses: lnbits/lnbits/.github/actions/prepare@dev
with:
python-version: ${{ matrix.python-version }}
- name: Run pytest
uses: pavelzw/pytest-action@v2
env:
LNBITS_BACKEND_WALLET_CLASS: FakeWallet
PYTHONUNBUFFERED: 1
DEBUG: true
with:
verbose: true
job-summary: true
emoji: false
click-to-expand: true
custom-pytest: poetry run pytest
report-title: 'test (${{ matrix.python-version }})'
15 changes: 7 additions & 8 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
on:
push:
tags:
- "v[0-9]+.[0-9]+.[0-9]+"
- 'v[0-9]+.[0-9]+.[0-9]+'

jobs:

release:
runs-on: ubuntu-latest
steps:
Expand Down Expand Up @@ -34,12 +33,12 @@ jobs:
- name: Create pull request in extensions repo
env:
GH_TOKEN: ${{ secrets.EXT_GITHUB }}
repo_name: "${{ github.event.repository.name }}"
tag: "${{ github.ref_name }}"
branch: "update-${{ github.event.repository.name }}-${{ github.ref_name }}"
title: "[UPDATE] ${{ github.event.repository.name }} to ${{ github.ref_name }}"
body: "https://github.com/lnbits/${{ github.event.repository.name }}/releases/${{ github.ref_name }}"
archive: "https://github.com/lnbits/${{ github.event.repository.name }}/archive/refs/tags/${{ github.ref_name }}.zip"
repo_name: '${{ github.event.repository.name }}'
tag: '${{ github.ref_name }}'
branch: 'update-${{ github.event.repository.name }}-${{ github.ref_name }}'
title: '[UPDATE] ${{ github.event.repository.name }} to ${{ github.ref_name }}'
body: 'https://github.com/lnbits/${{ github.event.repository.name }}/releases/${{ github.ref_name }}'
archive: 'https://github.com/lnbits/${{ github.event.repository.name }}/archive/refs/tags/${{ github.ref_name }}.zip'
run: |
cd lnbits-extensions
git checkout -b $branch
Expand Down
5 changes: 4 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1 +1,4 @@
__pycache__
__pycache__
node_modules
.venv
.mypy_cache
12 changes: 12 additions & 0 deletions .prettierrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{
"semi": false,
"arrowParens": "avoid",
"insertPragma": false,
"printWidth": 80,
"proseWrap": "preserve",
"singleQuote": true,
"trailingComma": "none",
"useTabs": false,
"bracketSameLine": false,
"bracketSpacing": false
}
48 changes: 48 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
all: format check

format: prettier black ruff

check: mypy pyright checkblack checkruff checkprettier

prettier:
poetry run ./node_modules/.bin/prettier --write .
pyright:
poetry run ./node_modules/.bin/pyright

mypy:
poetry run mypy .

black:
poetry run black .

ruff:
poetry run ruff check . --fix

checkruff:
poetry run ruff check .

checkprettier:
poetry run ./node_modules/.bin/prettier --check .

checkblack:
poetry run black --check .

checkeditorconfig:
editorconfig-checker

test:
PYTHONUNBUFFERED=1 \
DEBUG=true \
poetry run pytest

install-pre-commit-hook:
@echo "Installing pre-commit hook to git"
@echo "Uninstall the hook with poetry run pre-commit uninstall"
poetry run pre-commit install

pre-commit:
poetry run pre-commit run --all-files


checkbundle:
@echo "skipping checkbundle"
39 changes: 20 additions & 19 deletions __init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,16 @@
from fastapi import APIRouter
from loguru import logger

from lnbits.db import Database
from lnbits.helpers import template_renderer
from lnbits.tasks import create_permanent_unique_task
from .relay.client_manager import NostrClientManager

db = Database("ext_nostrrelay")
from .client_manager import client_manager
from .crud import db
from .tasks import wait_for_paid_invoices
from .views import nostrrelay_generic_router
from .views_api import nostrrelay_api_router

nostrrelay_ext: APIRouter = APIRouter(prefix="/nostrrelay", tags=["NostrRelay"])
nostrrelay_ext.include_router(nostrrelay_generic_router)
nostrrelay_ext.include_router(nostrrelay_api_router)

client_manager: NostrClientManager = NostrClientManager()

nostrrelay_static_files = [
{
Expand All @@ -29,30 +29,31 @@
}
]


def nostrrelay_renderer():
return template_renderer(["nostrrelay/templates"])


from .tasks import wait_for_paid_invoices
from .views import * # noqa
from .views_api import * # noqa


scheduled_tasks: list[asyncio.Task] = []

async def nostrrelay_stop():

def nostrrelay_stop():
for task in scheduled_tasks:
try:
task.cancel()
except Exception as ex:
logger.warning(ex)
try:
await client_manager.stop()
asyncio.run(client_manager.stop())
except Exception as ex:
logger.warning(ex)


def nostrrelay_start():
from lnbits.tasks import create_permanent_unique_task

task = create_permanent_unique_task("ext_nostrrelay", wait_for_paid_invoices)
scheduled_tasks.append(task)


__all__ = [
"db",
"nostrrelay_ext",
"nostrrelay_start",
"nostrrelay_stop",
]
3 changes: 3 additions & 0 deletions client_manager.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
from .relay.client_manager import NostrClientManager

client_manager: NostrClientManager = NostrClientManager()
69 changes: 43 additions & 26 deletions crud.py
Original file line number Diff line number Diff line change
@@ -1,19 +1,23 @@
import json
from typing import List, Optional, Tuple

from . import db
from lnbits.db import Database

from .models import NostrAccount
from .relay.event import NostrEvent
from .relay.filter import NostrFilter
from .relay.relay import NostrRelay, RelayPublicSpec, RelaySpec

db = Database("ext_nostrrelay")

########################## RELAYS ####################


async def create_relay(user_id: str, r: NostrRelay) -> NostrRelay:
await db.execute(
"""
INSERT INTO nostrrelay.relays (user_id, id, name, description, pubkey, contact, meta)
INSERT INTO nostrrelay.relays
(user_id, id, name, description, pubkey, contact, meta)
VALUES (?, ?, ?, ?, ?, ?, ?)
""",
(
Expand Down Expand Up @@ -167,9 +171,9 @@ async def create_event(relay_id: str, e: NostrEvent, publisher: Optional[str]):


async def get_events(
relay_id: str, filter: NostrFilter, include_tags=True
relay_id: str, nostr_filter: NostrFilter, include_tags=True
) -> List[NostrEvent]:
query, values = build_select_events_query(relay_id, filter)
query, values = build_select_events_query(relay_id, nostr_filter)

rows = await db.fetchall(query, tuple(values))

Expand All @@ -183,27 +187,33 @@ async def get_events(
return events


async def get_event(relay_id: str, id: str) -> Optional[NostrEvent]:
async def get_event(relay_id: str, event_id: str) -> Optional[NostrEvent]:
row = await db.fetchone(
"SELECT * FROM nostrrelay.events WHERE relay_id = ? AND id = ?",
(
relay_id,
id,
event_id,
),
)
if not row:
return None

event = NostrEvent.from_row(row)
event.tags = await get_event_tags(relay_id, id)
event.tags = await get_event_tags(relay_id, event_id)
return event


async def get_storage_for_public_key(relay_id: str, publisher_pubkey: str) -> int:
"""Returns the storage space in bytes for all the events of a public key. Deleted events are also counted"""
"""
Returns the storage space in bytes for all the events of a public key.
Deleted events are also counted
"""

row = await db.fetchone(
"SELECT SUM(size) as sum FROM nostrrelay.events WHERE relay_id = ? AND publisher = ? GROUP BY publisher",
"""
SELECT SUM(size) as sum FROM nostrrelay.events
WHERE relay_id = ? AND publisher = ? GROUP BY publisher
""",
(
relay_id,
publisher_pubkey,
Expand All @@ -216,7 +226,10 @@ async def get_storage_for_public_key(relay_id: str, publisher_pubkey: str) -> in


async def get_prunable_events(relay_id: str, pubkey: str) -> List[Tuple[str, int]]:
"""Return the oldest 10 000 events. Only the `id` and the size are returned, so the data size should be small"""
"""
Return the oldest 10 000 events. Only the `id` and the size are returned,
so the data size should be small
"""
query = """
SELECT id, size FROM nostrrelay.events
WHERE relay_id = ? AND pubkey = ?
Expand All @@ -228,21 +241,21 @@ async def get_prunable_events(relay_id: str, pubkey: str) -> List[Tuple[str, int
return [(r["id"], r["size"]) for r in rows]


async def mark_events_deleted(relay_id: str, filter: NostrFilter):
if filter.is_empty():
async def mark_events_deleted(relay_id: str, nostr_filter: NostrFilter):
if nostr_filter.is_empty():
return None
_, where, values = filter.to_sql_components(relay_id)
_, where, values = nostr_filter.to_sql_components(relay_id)

await db.execute(
f"""UPDATE nostrrelay.events SET deleted=true WHERE {" AND ".join(where)}""",
tuple(values),
)


async def delete_events(relay_id: str, filter: NostrFilter):
if filter.is_empty():
async def delete_events(relay_id: str, nostr_filter: NostrFilter):
if nostr_filter.is_empty():
return None
_, where, values = filter.to_sql_components(relay_id)
_, where, values = nostr_filter.to_sql_components(relay_id)

query = f"""DELETE from nostrrelay.events WHERE {" AND ".join(where)}"""
await db.execute(query, tuple(values))
Expand Down Expand Up @@ -309,20 +322,20 @@ async def get_event_tags(relay_id: str, event_id: str) -> List[List[str]]:
return tags


def build_select_events_query(relay_id: str, filter: NostrFilter):
inner_joins, where, values = filter.to_sql_components(relay_id)
def build_select_events_query(relay_id: str, nostr_filter: NostrFilter):
inner_joins, where, values = nostr_filter.to_sql_components(relay_id)

query = f"""
SELECT id, pubkey, created_at, kind, content, sig
FROM nostrrelay.events
{" ".join(inner_joins)}
SELECT id, pubkey, created_at, kind, content, sig
FROM nostrrelay.events
{" ".join(inner_joins)}
WHERE { " AND ".join(where)}
ORDER BY created_at DESC
"""

# todo: check & enforce range
if filter.limit and filter.limit > 0:
query += f" LIMIT {filter.limit}"
if nostr_filter.limit and nostr_filter.limit > 0:
query += f" LIMIT {nostr_filter.limit}"

return query, values

Expand All @@ -333,7 +346,8 @@ def build_select_events_query(relay_id: str, filter: NostrFilter):
async def create_account(relay_id: str, a: NostrAccount) -> NostrAccount:
await db.execute(
"""
INSERT INTO nostrrelay.accounts (relay_id, pubkey, sats, storage, paid_to_join, allowed, blocked)
INSERT INTO nostrrelay.accounts
(relay_id, pubkey, sats, storage, paid_to_join, allowed, blocked)
VALUES (?, ?, ?, ?, ?, ?, ?)
""",
(
Expand Down Expand Up @@ -394,9 +408,12 @@ async def get_accounts(

if not allowed and not blocked:
return []

rows = await db.fetchall(
"SELECT * FROM nostrrelay.accounts WHERE relay_id = ? AND allowed = ? OR blocked = ?",
"""
SELECT * FROM nostrrelay.accounts
WHERE relay_id = ? AND allowed = ? OR blocked = ?"
""",
(relay_id, allowed, blocked),
)
return [NostrAccount.from_row(row) for row in rows]
9 changes: 5 additions & 4 deletions description.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
Create a Nostr relay in just 2 steps!

Optional settings include:
* Charging for storage
* Charging for joining
* Npub allow/ban list (for restricting access)
* Pruning and filtering

- Charging for storage
- Charging for joining
- Npub allow/ban list (for restricting access)
- Pruning and filtering
Loading

0 comments on commit cc67520

Please sign in to comment.