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())