diff --git a/crud.py b/crud.py index 53ffdc4..1f965b1 100644 --- a/crud.py +++ b/crud.py @@ -2,9 +2,10 @@ import json from typing import List, Optional, Tuple from . import db -from .models import NostrAccount, NostrRelay, RelayPublicSpec, RelaySpec +from .models import NostrAccount from .relay.event import NostrEvent from .relay.filter import NostrFilter +from .relay.relay import NostrRelay, RelayPublicSpec, RelaySpec ########################## RELAYS #################### diff --git a/models.py b/models.py index 266e35c..c1804af 100644 --- a/models.py +++ b/models.py @@ -1,134 +1,7 @@ -import json from sqlite3 import Row -from typing import Any, List, Optional, Tuple - -from pydantic import BaseModel, Field - -from .relay.event import NostrEvent - - -class Spec(BaseModel): - class Config: - allow_population_by_field_name = True - - -class FilterSpec(Spec): - max_client_filters = Field(0, alias="maxClientFilters") - limit_per_filter = Field(1000, alias="limitPerFilter") - - -class EventSpec(Spec): - max_events_per_hour = Field(0, alias="maxEventsPerHour") - - created_at_days_past = Field(0, alias="createdAtDaysPast") - created_at_hours_past = Field(0, alias="createdAtHoursPast") - created_at_minutes_past = Field(0, alias="createdAtMinutesPast") - created_at_seconds_past = Field(0, alias="createdAtSecondsPast") - - created_at_days_future = Field(0, alias="createdAtDaysFuture") - created_at_hours_future = Field(0, alias="createdAtHoursFuture") - created_at_minutes_future = Field(0, alias="createdAtMinutesFuture") - created_at_seconds_future = Field(0, alias="createdAtSecondsFuture") - - @property - def created_at_in_past(self) -> int: - return ( - self.created_at_days_past * 86400 - + self.created_at_hours_past * 3600 - + self.created_at_minutes_past * 60 - + self.created_at_seconds_past - ) - - @property - def created_at_in_future(self) -> int: - return ( - self.created_at_days_future * 86400 - + self.created_at_hours_future * 3600 - + self.created_at_minutes_future * 60 - + self.created_at_seconds_future - ) - - -class StorageSpec(Spec): - free_storage_value = Field(1, alias="freeStorageValue") - free_storage_unit = Field("MB", alias="freeStorageUnit") - full_storage_action = Field("prune", alias="fullStorageAction") - - @property - def free_storage_bytes_value(self): - value = self.free_storage_value * 1024 - if self.free_storage_unit == "MB": - value *= 1024 - return value - - -class AuthSpec(BaseModel): - require_auth_events = Field(False, alias="requireAuthEvents") - skiped_auth_events = Field([], alias="skipedAuthEvents") - forced_auth_events = Field([], alias="forcedAuthEvents") - require_auth_filter = Field(False, alias="requireAuthFilter") - - def event_requires_auth(self, kind: int) -> bool: - if self.require_auth_events: - return kind not in self.skiped_auth_events - return kind in self.forced_auth_events - - -class PaymentSpec(BaseModel): - is_paid_relay = Field(False, alias="isPaidRelay") - cost_to_join = Field(0, alias="costToJoin") - - storage_cost_value = Field(0, alias="storageCostValue") - storage_cost_unit = Field("MB", alias="storageCostUnit") - - -class WalletSpec(Spec): - wallet = Field("") - - -class RelayPublicSpec(FilterSpec, EventSpec, StorageSpec, PaymentSpec): - domain: str = "" - - @property - def is_read_only_relay(self): - self.free_storage_value == 0 and not self.is_paid_relay - - -class RelaySpec(RelayPublicSpec, WalletSpec, AuthSpec): - pass - - -class NostrRelay(BaseModel): - id: str - name: str - description: Optional[str] - pubkey: Optional[str] - contact: Optional[str] - active: bool = False - - config: "RelaySpec" = RelaySpec() - - @property - def is_free_to_join(self): - return not self.config.is_paid_relay or self.config.cost_to_join == 0 - - @classmethod - def from_row(cls, row: Row) -> "NostrRelay": - relay = cls(**dict(row)) - relay.config = RelaySpec(**json.loads(row["meta"])) - return relay - - @classmethod - def info( - cls, - ) -> dict: - return { - "contact": "https://t.me/lnbits", - "supported_nips": [1, 9, 11, 15, 20, 22, 42], - "software": "LNbits", - "version": "", - } +from typing import Optional +from pydantic import BaseModel class BuyOrder(BaseModel): diff --git a/relay/client_connection.py b/relay/client_connection.py index 9a82859..5af1206 100644 --- a/relay/client_connection.py +++ b/relay/client_connection.py @@ -14,7 +14,7 @@ from ..crud import ( get_events, mark_events_deleted, ) -from ..models import RelaySpec +from .relay import RelaySpec from .event import NostrEvent, NostrEventType from .event_validator import EventValidator from .filter import NostrFilter diff --git a/relay/client_manager.py b/relay/client_manager.py index bf8e07e..33ecd96 100644 --- a/relay/client_manager.py +++ b/relay/client_manager.py @@ -1,7 +1,7 @@ from typing import List from ..crud import get_config_for_all_active_relays -from ..models import RelaySpec +from .relay import RelaySpec from .client_connection import NostrClientConnection from .event import NostrEvent diff --git a/relay/event_validator.py b/relay/event_validator.py index e8e0e9f..02c1c61 100644 --- a/relay/event_validator.py +++ b/relay/event_validator.py @@ -3,8 +3,9 @@ from typing import Callable, Optional, Tuple from ..crud import get_account, get_storage_for_public_key, prune_old_events from ..helpers import extract_domain -from ..models import NostrAccount, RelaySpec +from ..models import NostrAccount from .event import NostrEvent +from .relay import RelaySpec class EventValidator: diff --git a/relay/relay.py b/relay/relay.py new file mode 100644 index 0000000..2d7e876 --- /dev/null +++ b/relay/relay.py @@ -0,0 +1,130 @@ +import json +from sqlite3 import Row +from typing import Optional + +from pydantic import BaseModel, Field + + + +class Spec(BaseModel): + class Config: + allow_population_by_field_name = True + + +class FilterSpec(Spec): + max_client_filters = Field(0, alias="maxClientFilters") + limit_per_filter = Field(1000, alias="limitPerFilter") + + +class EventSpec(Spec): + max_events_per_hour = Field(0, alias="maxEventsPerHour") + + created_at_days_past = Field(0, alias="createdAtDaysPast") + created_at_hours_past = Field(0, alias="createdAtHoursPast") + created_at_minutes_past = Field(0, alias="createdAtMinutesPast") + created_at_seconds_past = Field(0, alias="createdAtSecondsPast") + + created_at_days_future = Field(0, alias="createdAtDaysFuture") + created_at_hours_future = Field(0, alias="createdAtHoursFuture") + created_at_minutes_future = Field(0, alias="createdAtMinutesFuture") + created_at_seconds_future = Field(0, alias="createdAtSecondsFuture") + + @property + def created_at_in_past(self) -> int: + return ( + self.created_at_days_past * 86400 + + self.created_at_hours_past * 3600 + + self.created_at_minutes_past * 60 + + self.created_at_seconds_past + ) + + @property + def created_at_in_future(self) -> int: + return ( + self.created_at_days_future * 86400 + + self.created_at_hours_future * 3600 + + self.created_at_minutes_future * 60 + + self.created_at_seconds_future + ) + + +class StorageSpec(Spec): + free_storage_value = Field(1, alias="freeStorageValue") + free_storage_unit = Field("MB", alias="freeStorageUnit") + full_storage_action = Field("prune", alias="fullStorageAction") + + @property + def free_storage_bytes_value(self): + value = self.free_storage_value * 1024 + if self.free_storage_unit == "MB": + value *= 1024 + return value + + +class AuthSpec(BaseModel): + require_auth_events = Field(False, alias="requireAuthEvents") + skiped_auth_events = Field([], alias="skipedAuthEvents") + forced_auth_events = Field([], alias="forcedAuthEvents") + require_auth_filter = Field(False, alias="requireAuthFilter") + + def event_requires_auth(self, kind: int) -> bool: + if self.require_auth_events: + return kind not in self.skiped_auth_events + return kind in self.forced_auth_events + + +class PaymentSpec(BaseModel): + is_paid_relay = Field(False, alias="isPaidRelay") + cost_to_join = Field(0, alias="costToJoin") + + storage_cost_value = Field(0, alias="storageCostValue") + storage_cost_unit = Field("MB", alias="storageCostUnit") + + +class WalletSpec(Spec): + wallet = Field("") + + +class RelayPublicSpec(FilterSpec, EventSpec, StorageSpec, PaymentSpec): + domain: str = "" + + @property + def is_read_only_relay(self): + self.free_storage_value == 0 and not self.is_paid_relay + + +class RelaySpec(RelayPublicSpec, WalletSpec, AuthSpec): + pass + + +class NostrRelay(BaseModel): + id: str + name: str + description: Optional[str] + pubkey: Optional[str] + contact: Optional[str] + active: bool = False + + config: "RelaySpec" = RelaySpec() + + @property + def is_free_to_join(self): + return not self.config.is_paid_relay or self.config.cost_to_join == 0 + + @classmethod + def from_row(cls, row: Row) -> "NostrRelay": + relay = cls(**dict(row)) + relay.config = RelaySpec(**json.loads(row["meta"])) + return relay + + @classmethod + def info( + cls, + ) -> dict: + return { + "contact": "https://t.me/lnbits", + "supported_nips": [1, 9, 11, 15, 20, 22, 42], + "software": "LNbits", + "version": "", + } + diff --git a/tests/test_clients.py b/tests/test_clients.py index 61fe83d..b26ec26 100644 --- a/tests/test_clients.py +++ b/tests/test_clients.py @@ -6,13 +6,9 @@ import pytest from fastapi import WebSocket from loguru import logger -from lnbits.extensions.nostrrelay.models import RelaySpec # type: ignore -from lnbits.extensions.nostrrelay.relay.client_connection import ( - NostrClientConnection, # type: ignore -) -from lnbits.extensions.nostrrelay.relay.client_manager import ( - NostrClientManager, # type: ignore -) +from lnbits.extensions.nostrrelay.relay.relay import RelaySpec # type: ignore +from lnbits.extensions.nostrrelay.relay.client_connection import NostrClientConnection # type: ignore +from lnbits.extensions.nostrrelay.relay.client_manager import NostrClientManager # type: ignore from .helpers import get_fixtures diff --git a/views_api.py b/views_api.py index 4b31a43..d30d523 100644 --- a/views_api.py +++ b/views_api.py @@ -30,8 +30,9 @@ from .crud import ( update_relay, ) from .helpers import extract_domain, normalize_public_key -from .models import BuyOrder, NostrAccount, NostrPartialAccount, NostrRelay +from .models import BuyOrder, NostrAccount, NostrPartialAccount from .relay.client_manager import NostrClientConnection, NostrClientManager +from .relay.relay import NostrRelay client_manager = NostrClientManager()