Merge branch 'main' into feature/extension-info-card-159
This commit is contained in:
commit
1f9f037fb9
4 changed files with 144 additions and 28 deletions
5
.gitignore
vendored
5
.gitignore
vendored
|
|
@ -22,4 +22,7 @@ node_modules
|
|||
*.swp
|
||||
*.pyo
|
||||
*.pyc
|
||||
*.env
|
||||
*.env
|
||||
|
||||
# Claude Code config
|
||||
CLAUDE.md
|
||||
104
CLAUDE.md
Normal file
104
CLAUDE.md
Normal file
|
|
@ -0,0 +1,104 @@
|
|||
# CLAUDE.md
|
||||
|
||||
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
|
||||
|
||||
## Project Overview
|
||||
|
||||
Nostr Market is an LNbits extension implementing NIP-15 (decentralized marketplace protocol) on Nostr. It enables merchants to create webshops (stalls) and sell products with Lightning Network payments, featuring encrypted customer-merchant communication via NIP-04.
|
||||
|
||||
**Prerequisites:** Requires the LNbits [nostrclient](https://github.com/lnbits/nostrclient) extension to be installed and configured.
|
||||
|
||||
## Common Commands
|
||||
|
||||
All commands are in the Makefile:
|
||||
|
||||
```bash
|
||||
make format # Run prettier, black, and ruff formatters
|
||||
make check # Run mypy, pyright, black check, ruff check, prettier check
|
||||
make test # Run pytest with debug mode
|
||||
make all # Run format and check
|
||||
```
|
||||
|
||||
Individual tools:
|
||||
|
||||
```bash
|
||||
make black # Format Python files
|
||||
make ruff # Check and fix Python linting
|
||||
make mypy # Static type checking
|
||||
make pyright # Python static type checker
|
||||
make prettier # Format JS/HTML/CSS files
|
||||
```
|
||||
|
||||
## Local Development Setup
|
||||
|
||||
To run checks locally, install dependencies:
|
||||
|
||||
```bash
|
||||
# Install Python autotools dependencies (needed for secp256k1)
|
||||
sudo apt-get install -y automake autoconf libtool
|
||||
|
||||
# Install Python dependencies
|
||||
uv sync
|
||||
|
||||
# Install Node dependencies (for prettier)
|
||||
npm install
|
||||
|
||||
# Run all checks
|
||||
make check
|
||||
```
|
||||
|
||||
## Architecture
|
||||
|
||||
### Core Layers
|
||||
|
||||
1. **API Layer** (`views_api.py`) - REST endpoints for merchants, stalls, products, zones, orders, direct messages
|
||||
2. **Business Logic** (`services.py`) - Order processing, Nostr event signing/publishing, message routing, invoice handling
|
||||
3. **Data Layer** (`crud.py`) - Async SQLite operations via LNbits db module
|
||||
4. **Models** (`models.py`) - Pydantic models for all entities
|
||||
|
||||
### Nostr Integration (`nostr/`)
|
||||
|
||||
- `nostr_client.py` - WebSocket client connecting to nostrclient extension for relay communication
|
||||
- `event.py` - Nostr event model, serialization, ID computation (SHA256), Schnorr signatures
|
||||
|
||||
### Background Tasks (`__init__.py`, `tasks.py`)
|
||||
|
||||
Three permanent async tasks:
|
||||
|
||||
- `wait_for_paid_invoices()` - Lightning payment listener
|
||||
- `wait_for_nostr_events()` - Incoming Nostr message processor
|
||||
- `_subscribe_to_nostr_client()` - WebSocket connection manager
|
||||
|
||||
### Frontend (`static/`, `templates/`)
|
||||
|
||||
- Merchant dashboard: `templates/nostrmarket/index.html`
|
||||
- Customer marketplace: `templates/nostrmarket/market.html` with Vue.js/Quasar in `static/market/`
|
||||
- Use Quasar UI components when possible: https://quasar.dev/components
|
||||
|
||||
### Key Data Models
|
||||
|
||||
- **Merchant** - Shop owner with Nostr keypair, handles event signing and DM encryption
|
||||
- **Stall** - Individual shop with products and shipping zones (kind 30017)
|
||||
- **Product** - Items for sale with categories, images, quantity (kind 30018)
|
||||
- **Zone** - Shipping configuration by region
|
||||
- **Order** - Customer purchases with Lightning invoice tracking
|
||||
- **DirectMessage** - Encrypted chat (NIP-04)
|
||||
- **Customer** - Buyer profile with Nostr pubkey
|
||||
|
||||
### Key Patterns
|
||||
|
||||
- **Nostrable Interface** - Base class for models convertible to Nostr events (`to_nostr_event()`, `to_nostr_delete_event()`)
|
||||
- **Parameterized Replaceable Events** - Stalls (kind 30017) and Products (kind 30018) per NIP-33
|
||||
- **AES-256 Encryption** - Customer-merchant DMs use shared secret from ECDH
|
||||
- **JSON Meta Fields** - Complex data (zones, items, config) stored as JSON in database
|
||||
|
||||
### Cryptography (`helpers.py`)
|
||||
|
||||
- Schnorr signatures for Nostr events
|
||||
- NIP-04 encryption/decryption
|
||||
- Key derivation and bech32 encoding (npub/nsec)
|
||||
|
||||
## Workflow
|
||||
|
||||
- Always check GitHub Actions after pushing to verify CI passes
|
||||
- Run `make check` locally before pushing to catch issues early
|
||||
|
|
@ -19,7 +19,6 @@ window.app.component('shipping-zones', {
|
|||
currencies: [],
|
||||
shippingZoneOptions: [
|
||||
'Free (digital)',
|
||||
'Flat rate',
|
||||
'Worldwide',
|
||||
'Europe',
|
||||
'Australia',
|
||||
|
|
@ -27,6 +26,7 @@ window.app.component('shipping-zones', {
|
|||
'Belgium',
|
||||
'Brazil',
|
||||
'Canada',
|
||||
'China',
|
||||
'Denmark',
|
||||
'Finland',
|
||||
'France',
|
||||
|
|
@ -34,8 +34,8 @@ window.app.component('shipping-zones', {
|
|||
'Greece',
|
||||
'Hong Kong',
|
||||
'Hungary',
|
||||
'Ireland',
|
||||
'Indonesia',
|
||||
'Ireland',
|
||||
'Israel',
|
||||
'Italy',
|
||||
'Japan',
|
||||
|
|
@ -59,10 +59,9 @@ window.app.component('shipping-zones', {
|
|||
'Thailand',
|
||||
'Turkey',
|
||||
'Ukraine',
|
||||
'United Kingdom**',
|
||||
'United States***',
|
||||
'Vietnam',
|
||||
'China'
|
||||
'United Kingdom',
|
||||
'United States',
|
||||
'Vietnam'
|
||||
]
|
||||
}
|
||||
},
|
||||
|
|
|
|||
|
|
@ -48,26 +48,36 @@
|
|||
label="Countries"
|
||||
v-model="zoneDialog.data.countries"
|
||||
></q-select>
|
||||
<q-select
|
||||
:disabled="!!zoneDialog.data.id"
|
||||
:readonly="!!zoneDialog.data.id"
|
||||
filled
|
||||
dense
|
||||
v-model="zoneDialog.data.currency"
|
||||
type="text"
|
||||
label="Unit"
|
||||
:options="currencies"
|
||||
></q-select>
|
||||
<q-input
|
||||
filled
|
||||
dense
|
||||
:label="'Cost of Shipping (' + zoneDialog.data.currency + ') *'"
|
||||
fill-mask="0"
|
||||
reverse-fill-mask
|
||||
:step="zoneDialog.data.currency != 'sat' ? '0.01' : '1'"
|
||||
type="number"
|
||||
v-model.trim="zoneDialog.data.cost"
|
||||
></q-input>
|
||||
<div class="row items-start">
|
||||
<div class="col q-mr-sm">
|
||||
<q-input
|
||||
filled
|
||||
dense
|
||||
label="Default shipping cost"
|
||||
fill-mask="0"
|
||||
reverse-fill-mask
|
||||
:step="zoneDialog.data.currency != 'sat' ? '0.01' : '1'"
|
||||
type="number"
|
||||
v-model.trim="zoneDialog.data.cost"
|
||||
:error="(zoneDialog.data.currency === 'sat' && zoneDialog.data.cost % 1 !== 0) || (zoneDialog.data.currency !== 'sat' && (zoneDialog.data.cost * 100) % 1 !== 0)"
|
||||
:error-message="zoneDialog.data.currency === 'sat' ? 'Satoshis must be whole numbers' : 'Maximum 2 decimal places allowed'"
|
||||
hint="Additional costs can be set per product"
|
||||
></q-input>
|
||||
</div>
|
||||
<div class="col-auto">
|
||||
<q-select
|
||||
:disabled="!!zoneDialog.data.id"
|
||||
:readonly="!!zoneDialog.data.id"
|
||||
filled
|
||||
dense
|
||||
v-model="zoneDialog.data.currency"
|
||||
type="text"
|
||||
label="Currency"
|
||||
:options="currencies"
|
||||
style="min-width: 100px"
|
||||
></q-select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row q-mt-lg">
|
||||
<div v-if="zoneDialog.data.id">
|
||||
<q-btn unelevated color="primary" type="submit">Update</q-btn>
|
||||
|
|
@ -83,7 +93,7 @@
|
|||
<q-btn
|
||||
unelevated
|
||||
color="primary"
|
||||
:disable="!zoneDialog.data.countries || !zoneDialog.data.countries.length"
|
||||
:disable="!zoneDialog.data.name || !zoneDialog.data.countries || !zoneDialog.data.countries.length || (zoneDialog.data.currency === 'sat' && zoneDialog.data.cost % 1 !== 0) || (zoneDialog.data.currency !== 'sat' && (zoneDialog.data.cost * 100) % 1 !== 0)"
|
||||
type="submit"
|
||||
>Create Shipping Zone</q-btn
|
||||
>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue