Skip to content

Commit

Permalink
feat: free nip5 (#23)
Browse files Browse the repository at this point in the history
* feat: free identifier flag

* feat: generate random number
  • Loading branch information
motorina0 authored Nov 29, 2024
1 parent 9af4bd9 commit 6876936
Show file tree
Hide file tree
Showing 5 changed files with 82 additions and 9 deletions.
10 changes: 10 additions & 0 deletions crud.py
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,16 @@ async def get_addresses_for_owner(owner_id: str) -> List[Address]:
return [Address.from_row(row) for row in rows]


async def get_free_addresses_for_owner(owner_id: str, domain_id: str) -> List[Address]:
rows = await db.fetchall(
"SELECT * FROM nostrnip5.addresses"
" WHERE owner_id = ? and domain_id = ? AND is_free = true",
(owner_id, domain_id),
)

return [Address.from_row(row) for row in rows]


async def get_all_addresses(wallet_ids: Union[str, List[str]]) -> List[Address]:
if isinstance(wallet_ids, str):
wallet_ids = [wallet_ids]
Expand Down
12 changes: 12 additions & 0 deletions migrations.py
Original file line number Diff line number Diff line change
Expand Up @@ -105,3 +105,15 @@ async def m007_add_cost_extra_column_to_addresses(db):
"ALTER TABLE nostrnip5.addresses ADD COLUMN "
"reimburse_amount REAL NOT NULL DEFAULT 0"
)


async def m008_add_is_free_column(db):
"""
Adds is_free flag for addresses.
"""
await db.execute(
"""
ALTER TABLE nostrnip5.addresses
ADD COLUMN is_free BOOLEAN NOT NULL DEFAULT false
"""
)
2 changes: 2 additions & 0 deletions models.py
Original file line number Diff line number Diff line change
Expand Up @@ -336,6 +336,7 @@ class Address(FromRowModel):
local_part: str
pubkey: str
active: bool
is_free: bool
time: int
reimburse_amount: int = 0
expires_at: Optional[float]
Expand All @@ -358,6 +359,7 @@ def from_row(cls, row: Row) -> "Address":

class AddressStatus(BaseModel):
identifier: str
free_identifier_number: Optional[str] = None
available: bool = False
price: Optional[float] = None
price_in_sats: Optional[float] = None
Expand Down
51 changes: 44 additions & 7 deletions services.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
from random import randint
from typing import List, Optional, Tuple

import httpx
Expand All @@ -19,6 +20,7 @@
get_all_addresses_paginated,
get_domain_by_id,
get_domains,
get_free_addresses_for_owner,
get_identifier_ranking,
get_settings,
update_address,
Expand Down Expand Up @@ -116,6 +118,25 @@ async def get_identifier_price_data(
return await domain.price_for_identifier(identifier, years, rank, promo_code)


async def get_next_free_identifier(domain_id: str, identifier: str):
free_identifier_number = str(randint(0, 999999)).zfill(6)
free_identifier = identifier + "." + free_identifier_number
active_address = await get_active_address_by_local_part(domain_id, free_identifier)
if not active_address:
return free_identifier_number
return await get_next_free_identifier(domain_id, identifier)


async def get_user_free_identifier(
user_id: str, domain_id: str, identifier: str
) -> Optional[str]:
owner = owner_id_from_user_id(user_id)
free_addresses = await get_free_addresses_for_owner(owner, domain_id)
if free_addresses:
return None
return await get_next_free_identifier(domain_id, identifier)


async def request_user_address(
domain: Domain,
address_data: CreateAddressData,
Expand All @@ -125,6 +146,11 @@ async def request_user_address(
address = await create_address(
domain, address_data, wallet_id, user_id, address_data.promo_code
)
if is_free_identifier(address.local_part):
await activate_address(domain.id, address.id)
address = await update_address(domain.id, address.id, is_free=True)
return dict(address)

assert (
address.config.price_in_sats
), f"Cannot compute price for '{address_data.local_part}'."
Expand Down Expand Up @@ -179,6 +205,18 @@ async def create_invoice_for_identifier(
return payment_hash, payment_request


def is_free_identifier(identifier: str) -> bool:
"Local Part without the suffix added for free addresses."
if len(identifier) < 7:
return False
if identifier[-7] != ".":
return False
if not identifier[-6:].isdigit():
return False

return True


async def create_address(
domain: Domain,
data: CreateAddressData,
Expand All @@ -193,17 +231,16 @@ async def create_address(
data.pubkey = validate_pub_key(data.pubkey)

owner_id = owner_id_from_user_id(user_id)
addresss = await get_address_for_owner(owner_id, domain.id, identifier)
address = await get_address_for_owner(owner_id, domain.id, identifier)

promo_code = promo_code or (addresss.config.promo_code if addresss else None)
promo_code = promo_code or (address.config.promo_code if address else None)
identifier_status = await get_identifier_status(
domain, identifier, data.years, promo_code
)

assert identifier_status.available, f"Identifier '{identifier}' not available."
assert identifier_status.price, f"Cannot compute price for '{identifier}'."

config = addresss.config if addresss else AddressConfig()
config = address.config if address else AddressConfig()
config.price = identifier_status.price
config.price_in_sats = identifier_status.price_in_sats
config.currency = domain.currency
Expand All @@ -213,10 +250,10 @@ async def create_address(
config.max_years = domain.cost_config.max_years
config.ln_address.wallet = wallet_id or ""

if addresss:
assert not addresss.active, f"Identifier '{data.local_part}' already activated."
if address:
assert not address.active, f"Identifier '{data.local_part}' already activated."
address = await update_address(
domain.id, addresss.id, config=config, pubkey=data.pubkey
domain.id, address.id, config=config, pubkey=data.pubkey
)
else:
address = await create_address_internal(data, owner_id, config=config)
Expand Down
16 changes: 14 additions & 2 deletions views_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -60,10 +60,12 @@
check_address_payment,
create_address,
get_identifier_status,
get_next_free_identifier,
get_reimburse_wallet_id,
get_user_addresses,
get_user_addresses_paginated,
get_user_domains,
get_user_free_identifier,
get_valid_addresses_for_owner,
refresh_buckets,
request_user_address,
Expand Down Expand Up @@ -171,7 +173,10 @@ async def api_get_nostr_json(
"/api/v1/domain/{domain_id}/search", status_code=HTTPStatus.OK
)
async def api_search_identifier(
domain_id: str, q: Optional[str] = None, years: Optional[int] = None
domain_id: str,
q: Optional[str] = None,
years: Optional[int] = None,
user_id: Optional[str] = Depends(optional_user_id),
) -> AddressStatus:

if not q:
Expand All @@ -180,7 +185,14 @@ async def api_search_identifier(
domain = await get_domain_by_id(domain_id)
assert domain, "Unknown domain id."

return await get_identifier_status(domain, q, years or 1)
address_status = await get_identifier_status(domain, q, years or 1)
address_status.free_identifier_number = (
await get_user_free_identifier(user_id, domain.id, address_status.identifier)
if user_id
else await get_next_free_identifier(domain.id, address_status.identifier)
)

return address_status


@http_try_except
Expand Down

0 comments on commit 6876936

Please sign in to comment.