diff --git a/crud.py b/crud.py
index 8f41bff..96c5bf0 100644
--- a/crud.py
+++ b/crud.py
@@ -1,10 +1,12 @@
import json
-from typing import Optional
+from typing import List, Optional
from lnbits.helpers import urlsafe_short_hash
from . import db
-from .models import Merchant, PartialMerchant
+from .models import Merchant, PartialMerchant, PartialZone, Zone
+
+######################################## MERCHANT ########################################
async def create_merchant(user_id: str, m: PartialMerchant) -> Merchant:
@@ -40,3 +42,66 @@ async def get_merchant_for_user(user_id: str) -> Optional[Merchant]:
)
return Merchant.from_row(row) if row else None
+
+
+######################################## ZONES ########################################
+
+
+async def create_zone(user_id: str, data: PartialZone) -> Zone:
+ zone_id = urlsafe_short_hash()
+ await db.execute(
+ f"""
+ INSERT INTO nostrmarket.zones (
+ id,
+ user_id,
+ name,
+ currency,
+ cost,
+ regions
+
+ )
+ VALUES (?, ?, ?, ?, ?, ?)
+ """,
+ (
+ zone_id,
+ user_id,
+ data.name,
+ data.currency,
+ data.cost,
+ json.dumps(data.countries),
+ ),
+ )
+
+ zone = await get_zone(user_id, zone_id)
+ assert zone, "Newly created zone couldn't be retrieved"
+ return zone
+
+
+async def update_zone(user_id: str, z: Zone) -> Optional[Zone]:
+ await db.execute(
+ f"UPDATE nostrmarket.zones SET name = ?, cost = ?, regions = ? WHERE id = ? AND user_id = ?",
+ (z.name, z.cost, json.dumps(z.countries), z.id, user_id),
+ )
+ return await get_zone(user_id, z.id)
+
+
+async def get_zone(user_id: str, zone_id: str) -> Optional[Zone]:
+ row = await db.fetchone(
+ "SELECT * FROM nostrmarket.zones WHERE user_id = ? AND id = ?",
+ (
+ user_id,
+ zone_id,
+ ),
+ )
+ return Zone.from_row(row) if row else None
+
+
+async def get_zones(user_id: str) -> List[Zone]:
+ rows = await db.fetchall(
+ "SELECT * FROM nostrmarket.zones WHERE user_id = ?", (user_id,)
+ )
+ return [Zone.from_row(row) for row in rows]
+
+
+async def delete_zone(zone_id: str) -> None:
+ await db.execute("DELETE FROM nostrmarket.zones WHERE id = ?", (zone_id,))
diff --git a/migrations.py b/migrations.py
index b3fafbe..04a4fe1 100644
--- a/migrations.py
+++ b/migrations.py
@@ -59,8 +59,9 @@ async def m001_initial(db):
id TEXT PRIMARY KEY,
user_id TEXT NOT NULL,
name TEXT NOT NULL,
+ currency TEXT NOT NULL,
cost REAL NOT NULL,
- countries TEXT NOT NULL
+ regions TEXT NOT NULL DEFAULT '[]'
);
"""
)
diff --git a/models.py b/models.py
index e6a4e5e..40bb57f 100644
--- a/models.py
+++ b/models.py
@@ -1,10 +1,12 @@
import json
from sqlite3 import Row
-from typing import Optional
+from typing import List, Optional
+from fastapi import Query
from pydantic import BaseModel
+######################################## MERCHANT ########################################
class MerchantConfig(BaseModel):
name: Optional[str]
@@ -23,3 +25,21 @@ class Merchant(PartialMerchant):
merchant = cls(**dict(row))
merchant.config = MerchantConfig(**json.loads(row["meta"]))
return merchant
+
+
+######################################## ZONES ########################################
+class PartialZone(BaseModel):
+ name: Optional[str]
+ currency: str
+ cost: float
+ countries: List[str] = []
+
+
+class Zone(PartialZone):
+ id: str
+
+ @classmethod
+ def from_row(cls, row: Row) -> "Zone":
+ zone = cls(**dict(row))
+ zone.countries = json.loads(row["regions"])
+ return zone
diff --git a/static/components/shipping-zones/shipping-zones.html b/static/components/shipping-zones/shipping-zones.html
index 08ced67..23b55b5 100644
--- a/static/components/shipping-zones/shipping-zones.html
+++ b/static/components/shipping-zones/shipping-zones.html
@@ -5,10 +5,10 @@
color="primary"
icon="public"
label="Shipping Zones"
- @click="createShippingZone"
+ @click="openZoneDialog()"
>
-
+
New Shipping Zone
Create a new shipping zone
@@ -19,13 +19,80 @@
:key="zone.id"
clickable
v-close-popup
- @click="editShippingZone"
+ @click="openZoneDialog(zone)"
>
- XXX
- xxxxxxxxxxxxx
+ {{zone.name}}
+ {{zone.countries.join(", ")}}
+
+
+
+
+
+
+
+
+
+
+ Update
+ Delete
+
+
+ Create Shipping Zone
+
+
+
Cancel
+
+
+
+
diff --git a/static/components/shipping-zones/shipping-zones.js b/static/components/shipping-zones/shipping-zones.js
index 4aef5f8..71e3ae6 100644
--- a/static/components/shipping-zones/shipping-zones.js
+++ b/static/components/shipping-zones/shipping-zones.js
@@ -2,18 +2,188 @@ async function shippingZones(path) {
const template = await loadTemplateAsync(path)
Vue.component('shipping-zones', {
name: 'shipping-zones',
+ props: ['adminkey', 'inkey'],
template,
data: function () {
return {
- zones: []
+ zones: [],
+ zoneDialog: {
+ showDialog: false,
+ data: {
+ id: null,
+ name: '',
+ countries: [],
+ cost: 0,
+ currency: 'sat'
+ }
+ },
+ currencies: [],
+ shippingZoneOptions: [
+ 'Free (digital)',
+ 'Flat rate',
+ 'Worldwide',
+ 'Europe',
+ 'Australia',
+ 'Austria',
+ 'Belgium',
+ 'Brazil',
+ 'Canada',
+ 'Denmark',
+ 'Finland',
+ 'France',
+ 'Germany',
+ 'Greece',
+ 'Hong Kong',
+ 'Hungary',
+ 'Ireland',
+ 'Indonesia',
+ 'Israel',
+ 'Italy',
+ 'Japan',
+ 'Kazakhstan',
+ 'Korea',
+ 'Luxembourg',
+ 'Malaysia',
+ 'Mexico',
+ 'Netherlands',
+ 'New Zealand',
+ 'Norway',
+ 'Poland',
+ 'Portugal',
+ 'Romania',
+ 'Russia',
+ 'Saudi Arabia',
+ 'Singapore',
+ 'Spain',
+ 'Sweden',
+ 'Switzerland',
+ 'Thailand',
+ 'Turkey',
+ 'Ukraine',
+ 'United Kingdom**',
+ 'United States***',
+ 'Vietnam',
+ 'China'
+ ]
}
},
methods: {
- createShippingZone: async function () {
- console.log('### createShippingZone', createShippingZone)
+ openZoneDialog: function (data) {
+ data = data || {
+ id: null,
+ name: '',
+ countries: [],
+ cost: 0,
+ currency: 'sat'
+ }
+ this.zoneDialog.data = data
+
+ this.zoneDialog.showDialog = true
},
- editShippingZone: async function () {}
+ createZone: async function () {
+ try {
+ const {data} = await LNbits.api.request(
+ 'POST',
+ '/nostrmarket/api/v1/zone',
+ this.adminkey,
+ {}
+ )
+ this.zones = data
+ } catch (error) {
+ LNbits.utils.notifyApiError(error)
+ }
+ },
+ getZones: async function () {
+ try {
+ const {data} = await LNbits.api.request(
+ 'GET',
+ '/nostrmarket/api/v1/zone',
+ this.inkey
+ )
+ this.zones = data
+ console.log('### data', data)
+ } catch (error) {
+ LNbits.utils.notifyApiError(error)
+ }
+ },
+ sendZoneFormData: async function () {
+ console.log('### data', this.zoneDialog.data)
+ this.zoneDialog.showDialog = false
+ if (this.zoneDialog.data.id) {
+ await this.updateShippingZone(this.zoneDialog.data)
+ } else {
+ await this.createShippingZone(this.zoneDialog.data)
+ }
+ await this.getZones()
+ },
+ createShippingZone: async function (newZone) {
+ console.log('### newZone', newZone)
+ try {
+ await LNbits.api.request(
+ 'POST',
+ '/nostrmarket/api/v1/zone',
+ this.adminkey,
+ newZone
+ )
+ this.$q.notify({
+ type: 'positive',
+ message: 'Zone created!'
+ })
+ } catch (error) {
+ LNbits.utils.notifyApiError(error)
+ }
+ },
+ updateShippingZone: async function (updatedZone) {
+ try {
+ await LNbits.api.request(
+ 'PATCH',
+ `/nostrmarket/api/v1/zone/${updatedZone.id}`,
+ this.adminkey,
+ updatedZone
+ )
+ this.$q.notify({
+ type: 'positive',
+ message: 'Zone updated!'
+ })
+ } catch (error) {
+ LNbits.utils.notifyApiError(error)
+ }
+ },
+ deleteShippingZone: async function () {
+ try {
+ await LNbits.api.request(
+ 'DELETE',
+ `/nostrmarket/api/v1/zone/${this.zoneDialog.data.id}`,
+ this.adminkey
+ )
+ this.$q.notify({
+ type: 'positive',
+ message: 'Zone deleted!'
+ })
+ await this.getZones()
+ this.zoneDialog.showDialog = false
+ } catch (error) {
+ LNbits.utils.notifyApiError(error)
+ }
+ },
+ async getCurrencies() {
+ try {
+ const {data} = await LNbits.api.request(
+ 'GET',
+ '/nostrmarket/api/v1/currencies',
+ this.inkey
+ )
+
+ this.currencies = ['sat', ...data]
+ } catch (error) {
+ LNbits.utils.notifyApiError(error)
+ }
+ }
+ },
+ created: async function () {
+ await this.getZones()
+ await this.getCurrencies()
}
})
}
diff --git a/static/js/index.js b/static/js/index.js
index 6d55b30..68ece51 100644
--- a/static/js/index.js
+++ b/static/js/index.js
@@ -42,9 +42,9 @@ const merchant = async () => {
getMerchant: async function () {
try {
const {data} = await LNbits.api.request(
- 'get',
+ 'GET',
'/nostrmarket/api/v1/merchant',
- this.g.user.wallets[0].adminkey
+ this.g.user.wallets[0].inkey
)
this.merchant = data
} catch (error) {
diff --git a/templates/nostrmarket/index.html b/templates/nostrmarket/index.html
index f7aaa6c..b4378d8 100644
--- a/templates/nostrmarket/index.html
+++ b/templates/nostrmarket/index.html
@@ -63,7 +63,10 @@
-
+
List[Zone]:
+ try:
+ return await get_zones(wallet.wallet.user)
+ except Exception as ex:
+ logger.warning(ex)
+ raise HTTPException(
+ status_code=HTTPStatus.INTERNAL_SERVER_ERROR,
+ detail="Cannot create merchant",
+ )
+
+
+@nostrmarket_ext.post("/api/v1/zone")
+async def api_create_zone(
+ data: PartialZone, wallet: WalletTypeInfo = Depends(get_key_type)
+):
+ try:
+ zone = await create_zone(wallet.wallet.user, data)
+ return zone
+ except Exception as ex:
+ logger.warning(ex)
+ raise HTTPException(
+ status_code=HTTPStatus.INTERNAL_SERVER_ERROR,
+ detail="Cannot create merchant",
+ )
+
+
+@nostrmarket_ext.patch("/api/v1/zone/{zone_id}")
+async def api_update_zone(
+ data: Zone,
+ zone_id: str,
+ wallet: WalletTypeInfo = Depends(require_admin_key),
+) -> Zone:
+ try:
+ zone = await get_zone(wallet.wallet.user, zone_id)
+ if not zone:
+ raise HTTPException(
+ status_code=HTTPStatus.NOT_FOUND,
+ detail="Zone does not exist.",
+ )
+ zone = await update_zone(wallet.wallet.user, data)
+ assert zone, "Cannot find updated zone"
+ return zone
+ except HTTPException as ex:
+ raise ex
+ except Exception as ex:
+ logger.warning(ex)
+ raise HTTPException(
+ status_code=HTTPStatus.INTERNAL_SERVER_ERROR,
+ detail="Cannot create merchant",
+ )
+
+
+@nostrmarket_ext.delete("/api/v1/zone/{zone_id}")
+async def api_delete_zone(zone_id, wallet: WalletTypeInfo = Depends(require_admin_key)):
+ try:
+ zone = await get_zone(wallet.wallet.user, zone_id)
+
+ if not zone:
+ raise HTTPException(
+ status_code=HTTPStatus.NOT_FOUND,
+ detail="Zone does not exist.",
+ )
+
+ await delete_zone(zone_id)
+
+ except Exception as ex:
+ logger.warning(ex)
+ raise HTTPException(
+ status_code=HTTPStatus.INTERNAL_SERVER_ERROR,
+ detail="Cannot create merchant",
+ )
+
+
+@nostrmarket_ext.get("/api/v1/currencies")
+async def api_list_currencies_available():
+ return list(currencies.keys())