agentstr 0.1.7__py3-none-any.whl → 0.1.9__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- agentstr/__init__.py +30 -1
- agentstr/marketplace.py +1060 -92
- agentstr/nostr.py +260 -74
- {agentstr-0.1.7.dist-info → agentstr-0.1.9.dist-info}/LICENSE +1 -1
- agentstr-0.1.9.dist-info/METADATA +110 -0
- agentstr-0.1.9.dist-info/RECORD +8 -0
- agentstr-0.1.7.dist-info/METADATA +0 -111
- agentstr-0.1.7.dist-info/RECORD +0 -8
- {agentstr-0.1.7.dist-info → agentstr-0.1.9.dist-info}/WHEEL +0 -0
- {agentstr-0.1.7.dist-info → agentstr-0.1.9.dist-info}/top_level.txt +0 -0
agentstr/marketplace.py
CHANGED
@@ -1,163 +1,1131 @@
|
|
1
|
+
import ast
|
2
|
+
import json
|
1
3
|
import logging
|
2
|
-
|
3
|
-
from
|
4
|
+
import re
|
5
|
+
from typing import Any, Dict, List, Optional, Tuple, Union, cast
|
6
|
+
|
7
|
+
from agentstr.nostr import (
|
8
|
+
EventId,
|
9
|
+
Keys,
|
10
|
+
NostrClient,
|
11
|
+
ProductData,
|
12
|
+
ShippingCost,
|
13
|
+
ShippingMethod,
|
14
|
+
StallData,
|
15
|
+
)
|
4
16
|
|
5
17
|
try:
|
6
18
|
from phi.tools import Toolkit
|
7
19
|
except ImportError:
|
8
|
-
raise ImportError(
|
20
|
+
raise ImportError(
|
21
|
+
"`phidata` not installed. Please install using `pip install phidata`"
|
22
|
+
)
|
9
23
|
|
10
|
-
|
11
|
-
import asyncio
|
12
|
-
except ImportError:
|
13
|
-
raise ImportError("`asyncio` not installed. Please install using `pip install asyncio`")
|
24
|
+
from pydantic import BaseModel, ConfigDict, Field, validate_call
|
14
25
|
|
15
|
-
class MerchantProfile():
|
16
26
|
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
nsec: Optional[str] = None
|
25
|
-
):
|
26
|
-
"""Initialize the Merchant profile.
|
27
|
+
class Profile:
|
28
|
+
|
29
|
+
logger = logging.getLogger("Profile")
|
30
|
+
WEB_URL: str = "https://primal.net/p/"
|
31
|
+
|
32
|
+
def __init__(self, name: str, about: str, picture: str, nsec: Optional[str] = None):
|
33
|
+
"""Initialize the profile.
|
27
34
|
|
28
35
|
Args:
|
29
36
|
name: Name for the merchant
|
30
37
|
about: brief description about the merchant
|
31
38
|
picture: url to a png file with a picture for the merchant
|
32
|
-
nsec: private key to be used by this Merchant
|
39
|
+
nsec: optional private key to be used by this Merchant
|
33
40
|
"""
|
34
41
|
|
35
42
|
# Set log handling for MerchantProfile
|
36
|
-
if not
|
43
|
+
if not Profile.logger.hasHandlers():
|
37
44
|
console_handler = logging.StreamHandler()
|
38
45
|
console_handler.setLevel(logging.INFO)
|
39
|
-
formatter = logging.Formatter(
|
46
|
+
formatter = logging.Formatter(
|
47
|
+
"%(asctime)s - %(name)s - %(levelname)s - %(message)s"
|
48
|
+
)
|
40
49
|
console_handler.setFormatter(formatter)
|
41
|
-
|
42
|
-
|
50
|
+
Profile.logger.addHandler(console_handler)
|
51
|
+
|
43
52
|
self.name = name
|
44
53
|
self.about = about
|
45
54
|
self.picture = picture
|
46
55
|
|
47
56
|
if nsec:
|
48
57
|
self.private_key = nsec
|
49
|
-
keys =
|
58
|
+
keys = Keys.parse(self.private_key)
|
50
59
|
self.public_key = keys.public_key().to_bech32()
|
51
|
-
|
52
|
-
|
60
|
+
Profile.logger.info(
|
61
|
+
f"Pre-defined private key reused for {self.name}: {self.private_key}"
|
62
|
+
)
|
63
|
+
Profile.logger.info(
|
64
|
+
f"Pre-defined public key reused for {self.name}: {self.public_key}"
|
65
|
+
)
|
53
66
|
else:
|
54
|
-
keys =
|
67
|
+
keys = Keys.generate()
|
55
68
|
self.private_key = keys.secret_key().to_bech32()
|
56
69
|
self.public_key = keys.public_key().to_bech32()
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
70
|
+
Profile.logger.info(
|
71
|
+
f"New private key created for {self.name}: {self.private_key}"
|
72
|
+
)
|
73
|
+
Profile.logger.info(
|
74
|
+
f"New public key created for {self.name}: {self.public_key}"
|
75
|
+
)
|
76
|
+
|
77
|
+
self.url = str(self.WEB_URL) + str(self.public_key)
|
78
|
+
|
79
|
+
def __str__(self) -> str:
|
61
80
|
return (
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
81
|
+
"Merchant Profile:\n"
|
82
|
+
"Name = {}\n"
|
83
|
+
"Description = {}\n"
|
84
|
+
"Picture = {}\n"
|
85
|
+
"URL = {}\n"
|
86
|
+
"Private key = {}\n"
|
87
|
+
"Public key = {}".format(
|
88
|
+
self.name,
|
89
|
+
self.about,
|
90
|
+
self.picture,
|
91
|
+
self.url,
|
92
|
+
self.private_key,
|
93
|
+
self.public_key,
|
94
|
+
)
|
95
|
+
)
|
96
|
+
|
97
|
+
def to_dict(self) -> dict:
|
98
|
+
return {
|
99
|
+
"name": self.name,
|
100
|
+
"description": self.about,
|
101
|
+
"picture": self.picture,
|
102
|
+
"public key": self.public_key,
|
103
|
+
"private key": self.private_key,
|
104
|
+
}
|
68
105
|
|
69
|
-
def get_public_key(self) -> str:
|
70
|
-
return self.public_key
|
71
|
-
|
72
|
-
def get_private_key(self) -> str:
|
73
|
-
return self.private_key
|
74
|
-
|
75
|
-
def get_name(self) -> str:
|
76
|
-
return self.name
|
77
|
-
|
78
106
|
def get_about(self) -> str:
|
107
|
+
"""
|
108
|
+
Returns a description of the Merchant
|
109
|
+
|
110
|
+
Returns:
|
111
|
+
str: description of the Merchant
|
112
|
+
"""
|
79
113
|
return self.about
|
80
|
-
|
114
|
+
|
115
|
+
def get_name(self) -> str:
|
116
|
+
"""
|
117
|
+
Returns the Merchant's name
|
118
|
+
|
119
|
+
Returns:
|
120
|
+
str: Merchant's name
|
121
|
+
"""
|
122
|
+
return self.name
|
123
|
+
|
81
124
|
def get_picture(self) -> str:
|
125
|
+
"""
|
126
|
+
Returns the picture associated with the Merchant.
|
127
|
+
|
128
|
+
Returns:
|
129
|
+
str: URL to the picture associated with the Merchant
|
130
|
+
"""
|
82
131
|
return self.picture
|
83
|
-
|
132
|
+
|
133
|
+
def get_private_key(self) -> str:
|
134
|
+
"""
|
135
|
+
Returns the private key.
|
136
|
+
|
137
|
+
Returns:
|
138
|
+
str: private key in bech32 format
|
139
|
+
"""
|
140
|
+
return str(self.private_key)
|
141
|
+
|
142
|
+
def get_public_key(self) -> str:
|
143
|
+
"""
|
144
|
+
Returns the public key.
|
145
|
+
|
146
|
+
Returns:
|
147
|
+
str: public key in bech32 format
|
148
|
+
"""
|
149
|
+
return str(self.public_key)
|
150
|
+
|
151
|
+
def get_url(self) -> str:
|
152
|
+
return str(self.url)
|
153
|
+
|
154
|
+
|
155
|
+
class MerchantProduct(BaseModel):
|
156
|
+
model_config = ConfigDict(arbitrary_types_allowed=True)
|
157
|
+
|
158
|
+
id: str
|
159
|
+
stall_id: str
|
160
|
+
name: str
|
161
|
+
description: str
|
162
|
+
images: List[str]
|
163
|
+
currency: str
|
164
|
+
price: float
|
165
|
+
quantity: int
|
166
|
+
shipping: List[ShippingCost]
|
167
|
+
categories: Optional[List[str]] = []
|
168
|
+
specs: Optional[List[List[str]]] = []
|
169
|
+
|
170
|
+
@classmethod
|
171
|
+
def from_product_data(cls, product: ProductData) -> "MerchantProduct":
|
172
|
+
return cls(
|
173
|
+
id=product.id,
|
174
|
+
stall_id=product.stall_id,
|
175
|
+
name=product.name,
|
176
|
+
description=product.description,
|
177
|
+
images=product.images,
|
178
|
+
currency=product.currency,
|
179
|
+
price=product.price,
|
180
|
+
quantity=product.quantity,
|
181
|
+
shipping=product.shipping,
|
182
|
+
categories=product.categories if product.categories is not None else [],
|
183
|
+
specs=product.specs if product.specs is not None else [],
|
184
|
+
)
|
185
|
+
|
186
|
+
def to_product_data(self) -> ProductData:
|
187
|
+
return ProductData(
|
188
|
+
id=self.id,
|
189
|
+
stall_id=self.stall_id,
|
190
|
+
name=self.name,
|
191
|
+
description=self.description,
|
192
|
+
images=self.images,
|
193
|
+
currency=self.currency,
|
194
|
+
price=self.price,
|
195
|
+
quantity=self.quantity,
|
196
|
+
shipping=self.shipping,
|
197
|
+
categories=self.categories,
|
198
|
+
specs=self.specs,
|
199
|
+
)
|
200
|
+
|
201
|
+
def to_dict(self) -> dict:
|
202
|
+
"""
|
203
|
+
Returns a dictionary representation of the MerchantProduct.
|
204
|
+
ShippingCost class is not serializable, so we need to convert it to a dictionary.
|
205
|
+
|
206
|
+
Returns:
|
207
|
+
dict: dictionary representation of the MerchantProduct
|
208
|
+
"""
|
209
|
+
shipping_dicts = []
|
210
|
+
for shipping in self.shipping:
|
211
|
+
shipping_dicts.append({"id": shipping.id, "cost": shipping.cost})
|
212
|
+
|
213
|
+
return {
|
214
|
+
"id": self.id,
|
215
|
+
"stall_id": self.stall_id,
|
216
|
+
"name": self.name,
|
217
|
+
"description": self.description,
|
218
|
+
"images": self.images,
|
219
|
+
"currency": self.currency,
|
220
|
+
"price": self.price,
|
221
|
+
"quantity": self.quantity,
|
222
|
+
"shipping": shipping_dicts,
|
223
|
+
"categories": self.categories,
|
224
|
+
"specs": self.specs,
|
225
|
+
}
|
226
|
+
|
227
|
+
|
228
|
+
class MerchantStall(BaseModel):
|
229
|
+
model_config = ConfigDict(arbitrary_types_allowed=True)
|
230
|
+
|
231
|
+
id: str
|
232
|
+
name: str
|
233
|
+
description: str
|
234
|
+
currency: str
|
235
|
+
shipping: List[ShippingMethod]
|
236
|
+
|
237
|
+
@classmethod
|
238
|
+
def from_stall_data(cls, stall: StallData) -> "MerchantStall":
|
239
|
+
return cls(
|
240
|
+
id=stall.id(),
|
241
|
+
name=stall.name(),
|
242
|
+
description=stall.description(),
|
243
|
+
currency=stall.currency(),
|
244
|
+
shipping=stall.shipping(),
|
245
|
+
)
|
246
|
+
|
247
|
+
def to_stall_data(self) -> StallData:
|
248
|
+
return StallData(
|
249
|
+
self.id,
|
250
|
+
self.name,
|
251
|
+
self.description,
|
252
|
+
self.currency,
|
253
|
+
self.shipping, # No conversion needed
|
254
|
+
)
|
255
|
+
|
256
|
+
def to_dict(self) -> dict:
|
257
|
+
"""
|
258
|
+
Returns a dictionary representation of the MerchantStall.
|
259
|
+
ShippingMethod class is not serializable, so we need to convert it to a dictionary.
|
260
|
+
We can only access cost and id from the ShippingMethod class. We can't access name or regions.
|
261
|
+
|
262
|
+
Returns:
|
263
|
+
dict: dictionary representation of the MerchantStall
|
264
|
+
"""
|
265
|
+
shipping_dicts = []
|
266
|
+
for shipping in self.shipping:
|
267
|
+
shipping_dicts.append(
|
268
|
+
{
|
269
|
+
"cost": shipping.get_shipping_cost().cost,
|
270
|
+
"id": shipping.get_shipping_cost().id,
|
271
|
+
}
|
272
|
+
)
|
273
|
+
|
274
|
+
return {
|
275
|
+
"id": self.id,
|
276
|
+
"name": self.name,
|
277
|
+
"description": self.description,
|
278
|
+
"currency": self.currency,
|
279
|
+
"shipping zones": [shipping_dicts],
|
280
|
+
}
|
84
281
|
|
85
282
|
|
86
283
|
class Merchant(Toolkit):
|
284
|
+
"""
|
285
|
+
Merchant is a toolkit that allows a merchant to publish products and stalls to Nostr.
|
286
|
+
|
287
|
+
TBD:
|
288
|
+
- Better differentiation between products and stalls in the database and products and stalls published.
|
289
|
+
|
290
|
+
"""
|
291
|
+
|
292
|
+
from pydantic import ConfigDict
|
293
|
+
|
294
|
+
model_config = ConfigDict(
|
295
|
+
arbitrary_types_allowed=True, extra="allow", validate_assignment=True
|
296
|
+
)
|
297
|
+
|
298
|
+
_nostr_client: Optional[NostrClient] = None
|
299
|
+
product_db: List[Tuple[MerchantProduct, Optional[EventId]]] = []
|
300
|
+
stall_db: List[Tuple[MerchantStall, Optional[EventId]]] = []
|
87
301
|
|
88
|
-
WEB_URL: str = "http://njump.me/"
|
89
|
-
|
90
302
|
def __init__(
|
91
303
|
self,
|
92
|
-
merchant_profile:
|
304
|
+
merchant_profile: Profile,
|
93
305
|
relay: str,
|
306
|
+
stalls: List[MerchantStall],
|
307
|
+
products: List[MerchantProduct],
|
94
308
|
):
|
95
309
|
"""Initialize the Merchant toolkit.
|
96
310
|
|
97
311
|
Args:
|
98
312
|
merchant_profile: profile of the merchant using this agent
|
99
313
|
relay: Nostr relay to use for communications
|
314
|
+
stalls: list of stalls managed by this merchant
|
315
|
+
products: list of products sold by this merchant
|
100
316
|
"""
|
101
317
|
super().__init__(name="merchant")
|
102
318
|
self.relay = relay
|
103
319
|
self.merchant_profile = merchant_profile
|
320
|
+
self._nostr_client = NostrClient(
|
321
|
+
self.relay, self.merchant_profile.get_private_key()
|
322
|
+
)
|
323
|
+
|
324
|
+
# initialize the Product DB with no event id
|
325
|
+
self.product_db = [(product, None) for product in products]
|
104
326
|
|
105
|
-
#
|
106
|
-
self.
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
self
|
327
|
+
# initialize the Stall DB with no event id
|
328
|
+
self.stall_db = [(stall, None) for stall in stalls]
|
329
|
+
|
330
|
+
# Register wrapped versions of the methods
|
331
|
+
self.register(self.get_profile)
|
332
|
+
self.register(self.get_relay)
|
333
|
+
self.register(self.get_products)
|
334
|
+
self.register(self.get_stalls)
|
335
|
+
self.register(self.publish_all_products)
|
336
|
+
self.register(self.publish_all_stalls)
|
337
|
+
self.register(self.publish_new_product)
|
338
|
+
self.register(self.publish_product_by_name)
|
339
|
+
self.register(self.publish_products_by_stall_name)
|
340
|
+
self.register(self.publish_profile)
|
341
|
+
self.register(self.publish_new_stall)
|
342
|
+
self.register(self.publish_stall_by_name)
|
343
|
+
self.register(self.remove_all_products)
|
344
|
+
self.register(self.remove_all_stalls)
|
345
|
+
self.register(self.remove_product_by_name)
|
346
|
+
self.register(self.remove_stall_by_name)
|
347
|
+
|
348
|
+
def get_profile(self) -> str:
|
349
|
+
"""
|
350
|
+
Retrieves merchant profile in JSON format
|
351
|
+
|
352
|
+
Returns:
|
353
|
+
str: merchant profile in JSON format
|
354
|
+
"""
|
355
|
+
return json.dumps(self.merchant_profile.to_dict())
|
356
|
+
|
357
|
+
def get_relay(self) -> str:
|
358
|
+
return self.relay
|
359
|
+
|
360
|
+
def get_products(self) -> str:
|
361
|
+
"""
|
362
|
+
Retrieves all the merchant products
|
363
|
+
|
364
|
+
Returns:
|
365
|
+
str: JSON string containing all products
|
366
|
+
"""
|
367
|
+
return json.dumps([p.to_dict() for p, _ in self.product_db])
|
368
|
+
|
369
|
+
def get_stalls(self) -> str:
|
370
|
+
"""
|
371
|
+
Retrieves all the merchant stalls in JSON format
|
372
|
+
|
373
|
+
Returns:
|
374
|
+
str: JSON string containing all stalls
|
375
|
+
"""
|
376
|
+
return json.dumps([s.to_dict() for s, _ in self.stall_db])
|
377
|
+
|
378
|
+
def publish_all_products(
|
379
|
+
self,
|
111
380
|
) -> str:
|
112
381
|
"""
|
113
|
-
Publishes the
|
382
|
+
Publishes or updates all products in the Merchant's Product DB
|
114
383
|
|
115
384
|
Returns:
|
116
|
-
str:
|
385
|
+
str: JSON array with status of all product publishing operations
|
117
386
|
"""
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
387
|
+
|
388
|
+
if self._nostr_client is None:
|
389
|
+
raise ValueError("NostrClient not initialized")
|
390
|
+
|
391
|
+
results = []
|
392
|
+
|
393
|
+
for i, (product, _) in enumerate(self.product_db):
|
394
|
+
try:
|
395
|
+
# Convert MerchantProduct to ProductData for nostr_client
|
396
|
+
product_data = product.to_product_data()
|
397
|
+
# Publish using the SDK's synchronous method
|
398
|
+
event_id = self._nostr_client.publish_product(product_data)
|
399
|
+
self.product_db[i] = (product, event_id)
|
400
|
+
results.append(
|
401
|
+
{
|
402
|
+
"status": "success",
|
403
|
+
"event_id": str(event_id),
|
404
|
+
"product_name": product.name,
|
405
|
+
}
|
406
|
+
)
|
407
|
+
except Exception as e:
|
408
|
+
Profile.logger.error(f"Unable to publish product {product}. Error {e}")
|
409
|
+
results.append(
|
410
|
+
{"status": "error", "message": str(e), "product_name": product.name}
|
411
|
+
)
|
412
|
+
|
413
|
+
return json.dumps(results)
|
414
|
+
|
415
|
+
def publish_all_stalls(
|
416
|
+
self,
|
123
417
|
) -> str:
|
124
418
|
"""
|
125
|
-
|
419
|
+
Publishes or updates all stalls managed by the merchant and adds the corresponding EventId to the Stall DB
|
126
420
|
|
127
421
|
Returns:
|
128
|
-
str:
|
422
|
+
str: JSON array with status of all stall publishing operations
|
129
423
|
"""
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
# Connect to the relay
|
134
|
-
outcome = await nostr_client.connect()
|
424
|
+
if self._nostr_client is None:
|
425
|
+
raise ValueError("NostrClient not initialized")
|
426
|
+
results = []
|
135
427
|
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
428
|
+
for i, (stall, _) in enumerate(self.stall_db):
|
429
|
+
try:
|
430
|
+
# Convert MerchantStall to StallData for nostr_client
|
431
|
+
stall_data = stall.to_stall_data()
|
432
|
+
event_id = self._nostr_client.publish_stall(stall_data)
|
433
|
+
self.stall_db[i] = (stall, event_id)
|
434
|
+
results.append(
|
435
|
+
{
|
436
|
+
"status": "success",
|
437
|
+
"event_id": str(event_id),
|
438
|
+
"stall_name": stall.name,
|
439
|
+
}
|
440
|
+
)
|
441
|
+
except Exception as e:
|
442
|
+
Profile.logger.error(f"Unable to publish stall {stall}. Error {e}")
|
443
|
+
results.append(
|
444
|
+
{"status": "error", "message": str(e), "stall_name": stall.name}
|
445
|
+
)
|
446
|
+
|
447
|
+
return json.dumps(results)
|
448
|
+
|
449
|
+
def publish_new_product(self, product: MerchantProduct) -> str:
|
450
|
+
"""
|
451
|
+
Publishes a new product that is not currently in the Merchant's Product DB and adds it to the Product DB
|
452
|
+
|
453
|
+
Args:
|
454
|
+
product: MerchantProduct to be published
|
455
|
+
|
456
|
+
Returns:
|
457
|
+
str: JSON string with status of the operation
|
458
|
+
"""
|
459
|
+
if self._nostr_client is None:
|
460
|
+
raise ValueError("NostrClient not initialized")
|
461
|
+
|
462
|
+
try:
|
463
|
+
# Convert MerchantProduct to ProductData for nostr_client
|
464
|
+
product_data = product.to_product_data()
|
465
|
+
# Publish using the SDK's synchronous method
|
466
|
+
event_id = self._nostr_client.publish_product(product_data)
|
467
|
+
# we need to add the product event id to the product db
|
468
|
+
self.product_db.append((product, event_id))
|
469
|
+
return json.dumps(
|
470
|
+
{
|
471
|
+
"status": "success",
|
472
|
+
"event_id": str(event_id),
|
473
|
+
"product_name": product.name,
|
474
|
+
}
|
475
|
+
)
|
476
|
+
except Exception as e:
|
477
|
+
return json.dumps(
|
478
|
+
{"status": "error", "message": str(e), "product_name": product.name}
|
479
|
+
)
|
480
|
+
|
481
|
+
def publish_product_by_name(self, arguments: str) -> str:
|
482
|
+
"""
|
483
|
+
Publishes or updates a given product from the Merchant's Product DB
|
484
|
+
Args:
|
485
|
+
arguments: JSON string that may contain {"name": "product_name"} or just "product_name"
|
486
|
+
|
487
|
+
Returns:
|
488
|
+
str: JSON string with status of the operation
|
489
|
+
"""
|
490
|
+
if self._nostr_client is None:
|
491
|
+
raise ValueError("NostrClient not initialized")
|
492
|
+
|
493
|
+
try:
|
494
|
+
# Try to parse as JSON first
|
495
|
+
if isinstance(arguments, dict):
|
496
|
+
parsed = arguments
|
497
|
+
else:
|
498
|
+
parsed = json.loads(arguments)
|
499
|
+
name = parsed.get(
|
500
|
+
"name", parsed
|
501
|
+
) # Get name if exists, otherwise use whole value
|
502
|
+
except json.JSONDecodeError:
|
503
|
+
# If not JSON, use the raw string
|
504
|
+
name = arguments
|
505
|
+
|
506
|
+
# iterate through all products searching for the right name
|
507
|
+
for i, (product, _) in enumerate(self.product_db):
|
508
|
+
if product.name == name:
|
509
|
+
try:
|
510
|
+
# Convert MerchantProduct to ProductData for nostr_client
|
511
|
+
product_data = product.to_product_data()
|
512
|
+
# Publish using the SDK's synchronous method
|
513
|
+
event_id = self._nostr_client.publish_product(product_data)
|
514
|
+
# Update the product_db with the new event_id
|
515
|
+
self.product_db[i] = (product, event_id)
|
516
|
+
return json.dumps(
|
517
|
+
{
|
518
|
+
"status": "success",
|
519
|
+
"event_id": str(event_id),
|
520
|
+
"product_name": product.name,
|
521
|
+
}
|
522
|
+
)
|
523
|
+
except Exception as e:
|
524
|
+
return json.dumps(
|
525
|
+
{
|
526
|
+
"status": "error",
|
527
|
+
"message": str(e),
|
528
|
+
"product_name": product.name,
|
529
|
+
}
|
530
|
+
)
|
531
|
+
|
532
|
+
# If we are here, then we didn't find a match
|
533
|
+
return json.dumps(
|
534
|
+
{
|
535
|
+
"status": "error",
|
536
|
+
"message": f"Product '{name}' not found in database",
|
537
|
+
"product_name": name,
|
538
|
+
}
|
539
|
+
)
|
540
|
+
|
541
|
+
def publish_products_by_stall_name(self, arguments: Union[str, dict]) -> str:
|
542
|
+
"""
|
543
|
+
Publishes or updates all products sold by the merchant in a given stall
|
544
|
+
|
545
|
+
Args:
|
546
|
+
arguments: str or dict with the stall name. Can be in formats:
|
547
|
+
- {"name": "stall_name"}
|
548
|
+
- {"arguments": "{\"name\": \"stall_name\"}"}
|
549
|
+
- "stall_name"
|
550
|
+
|
551
|
+
Returns:
|
552
|
+
str: JSON array with status of all product publishing operations
|
553
|
+
"""
|
554
|
+
if self._nostr_client is None:
|
555
|
+
raise ValueError("NostrClient not initialized")
|
556
|
+
|
557
|
+
try:
|
558
|
+
# Parse arguments to get stall_name
|
559
|
+
stall_name: str
|
560
|
+
if isinstance(arguments, str):
|
561
|
+
try:
|
562
|
+
parsed = json.loads(arguments)
|
563
|
+
if isinstance(parsed, dict):
|
564
|
+
raw_name: Optional[Any] = parsed.get("name")
|
565
|
+
stall_name = str(raw_name) if raw_name is not None else ""
|
566
|
+
else:
|
567
|
+
stall_name = arguments
|
568
|
+
except json.JSONDecodeError:
|
569
|
+
stall_name = arguments
|
570
|
+
else:
|
571
|
+
if "arguments" in arguments:
|
572
|
+
nested = json.loads(arguments["arguments"])
|
573
|
+
if isinstance(nested, dict):
|
574
|
+
raw_name = nested.get("name")
|
575
|
+
stall_name = str(raw_name) if raw_name is not None else ""
|
576
|
+
else:
|
577
|
+
raw_name = nested
|
578
|
+
stall_name = str(raw_name) if raw_name is not None else ""
|
579
|
+
else:
|
580
|
+
raw_name = arguments.get("name", arguments)
|
581
|
+
stall_name = str(raw_name) if raw_name is not None else ""
|
582
|
+
|
583
|
+
results = []
|
584
|
+
stall_id = None
|
585
|
+
|
586
|
+
# Find stall ID
|
587
|
+
for stall, _ in self.stall_db:
|
588
|
+
if stall.name == stall_name:
|
589
|
+
stall_id = stall.id
|
590
|
+
break
|
591
|
+
|
592
|
+
if stall_id is None:
|
593
|
+
return json.dumps(
|
594
|
+
[
|
595
|
+
{
|
596
|
+
"status": "error",
|
597
|
+
"message": f"Stall '{stall_name}' not found in database",
|
598
|
+
"stall_name": stall_name,
|
599
|
+
}
|
600
|
+
]
|
601
|
+
)
|
602
|
+
|
603
|
+
# Publish products
|
604
|
+
for i, (product, _) in enumerate(self.product_db):
|
605
|
+
if product.stall_id == stall_id:
|
606
|
+
try:
|
607
|
+
product_data = product.to_product_data()
|
608
|
+
event_id = self._nostr_client.publish_product(product_data)
|
609
|
+
self.product_db[i] = (product, event_id)
|
610
|
+
results.append(
|
611
|
+
{
|
612
|
+
"status": "success",
|
613
|
+
"event_id": str(event_id),
|
614
|
+
"product_name": product.name,
|
615
|
+
"stall_name": stall_name,
|
616
|
+
}
|
617
|
+
)
|
618
|
+
except Exception as e:
|
619
|
+
results.append(
|
620
|
+
{
|
621
|
+
"status": "error",
|
622
|
+
"message": str(e),
|
623
|
+
"product_name": product.name,
|
624
|
+
"stall_name": stall_name,
|
625
|
+
}
|
626
|
+
)
|
627
|
+
|
628
|
+
if not results:
|
629
|
+
return json.dumps(
|
630
|
+
[
|
631
|
+
{
|
632
|
+
"status": "error",
|
633
|
+
"message": f"No products found in stall '{stall_name}'",
|
634
|
+
"stall_name": stall_name,
|
635
|
+
}
|
636
|
+
]
|
637
|
+
)
|
638
|
+
|
639
|
+
return json.dumps(results)
|
640
|
+
|
641
|
+
except Exception as e:
|
642
|
+
return json.dumps(
|
643
|
+
[{"status": "error", "message": str(e), "arguments": str(arguments)}]
|
644
|
+
)
|
645
|
+
|
646
|
+
def publish_profile(self) -> str:
|
647
|
+
"""
|
648
|
+
Publishes the profile on Nostr
|
649
|
+
|
650
|
+
Returns:
|
651
|
+
str: JSON of the event that published the profile
|
652
|
+
|
653
|
+
Raises:
|
654
|
+
RuntimeError: if it can't publish the event
|
655
|
+
"""
|
656
|
+
if self._nostr_client is None:
|
657
|
+
raise ValueError("NostrClient not initialized")
|
658
|
+
|
659
|
+
try:
|
660
|
+
event_id = self._nostr_client.publish_profile(
|
141
661
|
self.merchant_profile.get_name(),
|
142
662
|
self.merchant_profile.get_about(),
|
143
|
-
self.merchant_profile.get_picture()
|
144
|
-
)
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
# Return the event ID and merchant profile details
|
151
|
-
return eventid + self.merchant_profile.merchant_profile_to_str()
|
152
|
-
|
153
|
-
def get_merchant_url(
|
154
|
-
self
|
155
|
-
) -> str:
|
663
|
+
self.merchant_profile.get_picture(),
|
664
|
+
)
|
665
|
+
return json.dumps(event_id.__dict__)
|
666
|
+
except Exception as e:
|
667
|
+
raise RuntimeError(f"Unable to publish the profile: {e}")
|
668
|
+
|
669
|
+
def publish_new_stall(self, stall: MerchantStall) -> str:
|
156
670
|
"""
|
157
|
-
|
671
|
+
Publishes a new stall that is not currently in the Merchant's Stall DB and adds it to the Stall DB
|
672
|
+
|
673
|
+
Args:
|
674
|
+
stall: MerchantStall to be published
|
158
675
|
|
159
676
|
Returns:
|
160
|
-
str:
|
677
|
+
str: JSON string with status of the operation
|
161
678
|
"""
|
679
|
+
if self._nostr_client is None:
|
680
|
+
raise ValueError("NostrClient not initialized")
|
681
|
+
|
682
|
+
try:
|
683
|
+
# Convert to StallData for SDK
|
684
|
+
stall_data = stall.to_stall_data()
|
685
|
+
# Publish using the SDK's synchronous method
|
686
|
+
event_id = self._nostr_client.publish_stall(stall_data)
|
687
|
+
# we need to add the stall event id to the stall db
|
688
|
+
self.stall_db.append((stall, event_id))
|
689
|
+
return json.dumps(
|
690
|
+
{
|
691
|
+
"status": "success",
|
692
|
+
"event_id": str(event_id),
|
693
|
+
"stall_name": stall.name,
|
694
|
+
}
|
695
|
+
)
|
696
|
+
except Exception as e:
|
697
|
+
return json.dumps(
|
698
|
+
{"status": "error", "message": str(e), "stall_name": stall.name}
|
699
|
+
)
|
700
|
+
|
701
|
+
def publish_stall_by_name(self, arguments: Union[str, dict]) -> str:
|
702
|
+
if self._nostr_client is None:
|
703
|
+
raise ValueError("NostrClient not initialized")
|
704
|
+
|
705
|
+
try:
|
706
|
+
# Parse arguments to get stall_name
|
707
|
+
stall_name: str
|
708
|
+
if isinstance(arguments, str):
|
709
|
+
try:
|
710
|
+
# Try to parse as JSON first
|
711
|
+
parsed = json.loads(arguments)
|
712
|
+
if isinstance(parsed, dict):
|
713
|
+
raw_name: Optional[Any] = parsed.get("name")
|
714
|
+
stall_name = str(raw_name) if raw_name is not None else ""
|
715
|
+
else:
|
716
|
+
stall_name = arguments
|
717
|
+
except json.JSONDecodeError:
|
718
|
+
# If not JSON, use the raw string
|
719
|
+
stall_name = arguments
|
720
|
+
else:
|
721
|
+
# Handle dict input
|
722
|
+
if "arguments" in arguments:
|
723
|
+
nested = json.loads(arguments["arguments"])
|
724
|
+
if isinstance(nested, dict):
|
725
|
+
raw_name = nested.get("name")
|
726
|
+
stall_name = str(raw_name) if raw_name is not None else ""
|
727
|
+
else:
|
728
|
+
raw_name = nested
|
729
|
+
stall_name = str(raw_name) if raw_name is not None else ""
|
730
|
+
else:
|
731
|
+
raw_name = arguments.get("name", arguments)
|
732
|
+
stall_name = str(raw_name) if raw_name is not None else ""
|
733
|
+
|
734
|
+
# Find and publish stall
|
735
|
+
for i, (stall, _) in enumerate(self.stall_db):
|
736
|
+
if stall.name == stall_name:
|
737
|
+
try:
|
738
|
+
stall_data = stall.to_stall_data()
|
739
|
+
event_id = self._nostr_client.publish_stall(stall_data)
|
740
|
+
self.stall_db[i] = (stall, event_id)
|
741
|
+
return json.dumps(
|
742
|
+
{
|
743
|
+
"status": "success",
|
744
|
+
"event_id": str(event_id),
|
745
|
+
"stall_name": stall.name,
|
746
|
+
}
|
747
|
+
)
|
748
|
+
except Exception as e:
|
749
|
+
return json.dumps(
|
750
|
+
[
|
751
|
+
{
|
752
|
+
"status": "error",
|
753
|
+
"message": str(e),
|
754
|
+
"stall_name": stall.name,
|
755
|
+
}
|
756
|
+
]
|
757
|
+
)
|
758
|
+
|
759
|
+
# Stall not found
|
760
|
+
return json.dumps(
|
761
|
+
[
|
762
|
+
{
|
763
|
+
"status": "error",
|
764
|
+
"message": f"Stall '{stall_name}' not found in database",
|
765
|
+
"stall_name": stall_name,
|
766
|
+
}
|
767
|
+
]
|
768
|
+
)
|
162
769
|
|
163
|
-
|
770
|
+
except Exception as e:
|
771
|
+
return json.dumps(
|
772
|
+
[{"status": "error", "message": str(e), "stall_name": "unknown"}]
|
773
|
+
)
|
774
|
+
|
775
|
+
def remove_all_products(self) -> str:
|
776
|
+
"""
|
777
|
+
Removes all published products from Nostr
|
778
|
+
|
779
|
+
Returns:
|
780
|
+
str: JSON array with status of all product removal operations
|
781
|
+
"""
|
782
|
+
if self._nostr_client is None:
|
783
|
+
raise ValueError("NostrClient not initialized")
|
784
|
+
|
785
|
+
results = []
|
786
|
+
|
787
|
+
for i, (product, event_id) in enumerate(self.product_db):
|
788
|
+
if event_id is None:
|
789
|
+
results.append(
|
790
|
+
{
|
791
|
+
"status": "skipped",
|
792
|
+
"message": f"Product '{product.name}' has not been published yet",
|
793
|
+
"product_name": product.name,
|
794
|
+
}
|
795
|
+
)
|
796
|
+
continue
|
797
|
+
|
798
|
+
try:
|
799
|
+
# Delete the event using the SDK's method
|
800
|
+
self._nostr_client.delete_event(
|
801
|
+
event_id, reason=f"Product '{product.name}' removed"
|
802
|
+
)
|
803
|
+
# Remove the event_id, keeping the product in the database
|
804
|
+
self.product_db[i] = (product, None)
|
805
|
+
results.append(
|
806
|
+
{
|
807
|
+
"status": "success",
|
808
|
+
"message": f"Product '{product.name}' removed",
|
809
|
+
"product_name": product.name,
|
810
|
+
"event_id": str(event_id),
|
811
|
+
}
|
812
|
+
)
|
813
|
+
except Exception as e:
|
814
|
+
results.append(
|
815
|
+
{"status": "error", "message": str(e), "product_name": product.name}
|
816
|
+
)
|
817
|
+
|
818
|
+
return json.dumps(results)
|
819
|
+
|
820
|
+
def remove_all_stalls(self) -> str:
|
821
|
+
"""
|
822
|
+
Removes all stalls and their products from Nostr
|
823
|
+
|
824
|
+
Returns:
|
825
|
+
str: JSON array with status of all removal operations
|
826
|
+
"""
|
827
|
+
if self._nostr_client is None:
|
828
|
+
raise ValueError("NostrClient not initialized")
|
829
|
+
|
830
|
+
results = []
|
831
|
+
|
832
|
+
# First remove all products from all stalls
|
833
|
+
for i, (stall, _) in enumerate(self.stall_db):
|
834
|
+
stall_name = stall.name
|
835
|
+
stall_id = stall.id
|
836
|
+
|
837
|
+
# Remove all products in this stall
|
838
|
+
for j, (product, event_id) in enumerate(self.product_db):
|
839
|
+
if product.stall_id == stall_id:
|
840
|
+
if event_id is None:
|
841
|
+
results.append(
|
842
|
+
{
|
843
|
+
"status": "skipped",
|
844
|
+
"message": f"Product '{product.name}' has not been published yet",
|
845
|
+
"product_name": product.name,
|
846
|
+
"stall_name": stall_name,
|
847
|
+
}
|
848
|
+
)
|
849
|
+
continue
|
850
|
+
|
851
|
+
try:
|
852
|
+
self._nostr_client.delete_event(
|
853
|
+
event_id,
|
854
|
+
reason=f"Stall for product '{product.name}' removed",
|
855
|
+
)
|
856
|
+
self.product_db[j] = (product, None)
|
857
|
+
results.append(
|
858
|
+
{
|
859
|
+
"status": "success",
|
860
|
+
"message": f"Product '{product.name}' removed",
|
861
|
+
"product_name": product.name,
|
862
|
+
"stall_name": stall_name,
|
863
|
+
"event_id": str(event_id),
|
864
|
+
}
|
865
|
+
)
|
866
|
+
except Exception as e:
|
867
|
+
results.append(
|
868
|
+
{
|
869
|
+
"status": "error",
|
870
|
+
"message": str(e),
|
871
|
+
"product_name": product.name,
|
872
|
+
"stall_name": stall_name,
|
873
|
+
}
|
874
|
+
)
|
875
|
+
|
876
|
+
# Now remove the stall itself
|
877
|
+
_, stall_event_id = self.stall_db[i]
|
878
|
+
if stall_event_id is None:
|
879
|
+
results.append(
|
880
|
+
{
|
881
|
+
"status": "skipped",
|
882
|
+
"message": f"Stall '{stall_name}' has not been published yet",
|
883
|
+
"stall_name": stall_name,
|
884
|
+
}
|
885
|
+
)
|
886
|
+
else:
|
887
|
+
try:
|
888
|
+
self._nostr_client.delete_event(
|
889
|
+
stall_event_id, reason=f"Stall '{stall_name}' removed"
|
890
|
+
)
|
891
|
+
self.stall_db[i] = (stall, None)
|
892
|
+
results.append(
|
893
|
+
{
|
894
|
+
"status": "success",
|
895
|
+
"message": f"Stall '{stall_name}' removed",
|
896
|
+
"stall_name": stall_name,
|
897
|
+
"event_id": str(stall_event_id),
|
898
|
+
}
|
899
|
+
)
|
900
|
+
except Exception as e:
|
901
|
+
results.append(
|
902
|
+
{"status": "error", "message": str(e), "stall_name": stall_name}
|
903
|
+
)
|
904
|
+
|
905
|
+
return json.dumps(results)
|
906
|
+
|
907
|
+
def remove_product_by_name(self, arguments: str) -> str:
|
908
|
+
"""
|
909
|
+
Deletes a product with the given name from Nostr
|
910
|
+
|
911
|
+
Args:
|
912
|
+
arguments: JSON string that may contain {"name": "product_name"} or just "product_name"
|
913
|
+
|
914
|
+
Returns:
|
915
|
+
str: JSON string with status of the operation
|
916
|
+
"""
|
917
|
+
if self._nostr_client is None:
|
918
|
+
raise ValueError("NostrClient not initialized")
|
919
|
+
|
920
|
+
try:
|
921
|
+
# Try to parse as JSON first
|
922
|
+
if isinstance(arguments, dict):
|
923
|
+
parsed = arguments
|
924
|
+
else:
|
925
|
+
parsed = json.loads(arguments)
|
926
|
+
name = parsed.get(
|
927
|
+
"name", parsed
|
928
|
+
) # Get name if exists, otherwise use whole value
|
929
|
+
except json.JSONDecodeError:
|
930
|
+
# If not JSON, use the raw string
|
931
|
+
name = arguments
|
932
|
+
|
933
|
+
# Find the product and its event_id in the product_db
|
934
|
+
for i, (product, event_id) in enumerate(self.product_db):
|
935
|
+
if product.name == name:
|
936
|
+
if event_id is None:
|
937
|
+
return json.dumps(
|
938
|
+
{
|
939
|
+
"status": "error",
|
940
|
+
"message": f"Product '{name}' has not been published yet",
|
941
|
+
"product_name": name,
|
942
|
+
}
|
943
|
+
)
|
944
|
+
|
945
|
+
try:
|
946
|
+
# Delete the event using the SDK's method
|
947
|
+
self._nostr_client.delete_event(
|
948
|
+
event_id, reason=f"Product '{name}' removed"
|
949
|
+
)
|
950
|
+
# Remove the event_id, keeping the product in the database
|
951
|
+
self.product_db[i] = (product, None)
|
952
|
+
return json.dumps(
|
953
|
+
{
|
954
|
+
"status": "success",
|
955
|
+
"message": f"Product '{name}' removed",
|
956
|
+
"product_name": name,
|
957
|
+
"event_id": str(event_id),
|
958
|
+
}
|
959
|
+
)
|
960
|
+
except Exception as e:
|
961
|
+
return json.dumps(
|
962
|
+
{"status": "error", "message": str(e), "product_name": name}
|
963
|
+
)
|
964
|
+
|
965
|
+
# If we get here, we didn't find the product
|
966
|
+
return json.dumps(
|
967
|
+
{
|
968
|
+
"status": "error",
|
969
|
+
"message": f"Product '{name}' not found in database",
|
970
|
+
"product_name": name,
|
971
|
+
}
|
972
|
+
)
|
973
|
+
|
974
|
+
def remove_stall_by_name(self, arguments: Union[str, dict]) -> str:
|
975
|
+
"""Remove a stall and its products by name
|
976
|
+
|
977
|
+
Args:
|
978
|
+
arguments: str or dict with the stall name. Can be in formats:
|
979
|
+
- {"name": "stall_name"}
|
980
|
+
- {"arguments": "{\"name\": \"stall_name\"}"}
|
981
|
+
- "stall_name"
|
982
|
+
|
983
|
+
Returns:
|
984
|
+
str: JSON array with status of the operation
|
985
|
+
"""
|
986
|
+
if self._nostr_client is None:
|
987
|
+
raise ValueError("NostrClient not initialized")
|
988
|
+
|
989
|
+
try:
|
990
|
+
# Parse arguments to get stall_name
|
991
|
+
stall_name: str
|
992
|
+
if isinstance(arguments, str):
|
993
|
+
try:
|
994
|
+
parsed = json.loads(arguments)
|
995
|
+
if isinstance(parsed, dict):
|
996
|
+
raw_name: Optional[Any] = parsed.get("name")
|
997
|
+
stall_name = str(raw_name) if raw_name is not None else ""
|
998
|
+
else:
|
999
|
+
stall_name = arguments
|
1000
|
+
except json.JSONDecodeError:
|
1001
|
+
stall_name = arguments
|
1002
|
+
else:
|
1003
|
+
if "arguments" in arguments:
|
1004
|
+
nested = json.loads(arguments["arguments"])
|
1005
|
+
if isinstance(nested, dict):
|
1006
|
+
raw_name = nested.get("name")
|
1007
|
+
stall_name = str(raw_name) if raw_name is not None else ""
|
1008
|
+
else:
|
1009
|
+
raw_name = nested
|
1010
|
+
stall_name = str(raw_name) if raw_name is not None else ""
|
1011
|
+
else:
|
1012
|
+
raw_name = arguments.get("name", arguments)
|
1013
|
+
stall_name = str(raw_name) if raw_name is not None else ""
|
1014
|
+
|
1015
|
+
results = []
|
1016
|
+
stall_index = None
|
1017
|
+
stall_id = None
|
1018
|
+
|
1019
|
+
# Find the stall and its event_id in the stall_db
|
1020
|
+
for i, (stall, event_id) in enumerate(self.stall_db):
|
1021
|
+
if stall.name == stall_name:
|
1022
|
+
stall_index = i
|
1023
|
+
stall_id = stall.id
|
1024
|
+
break
|
1025
|
+
|
1026
|
+
# If stall_id is empty, then we found no match
|
1027
|
+
if stall_id is None:
|
1028
|
+
return json.dumps(
|
1029
|
+
[
|
1030
|
+
{
|
1031
|
+
"status": "error",
|
1032
|
+
"message": f"Stall '{stall_name}' not found in database",
|
1033
|
+
"stall_name": stall_name,
|
1034
|
+
}
|
1035
|
+
]
|
1036
|
+
)
|
1037
|
+
|
1038
|
+
# First remove all products in this stall
|
1039
|
+
for i, (product, event_id) in enumerate(self.product_db):
|
1040
|
+
if product.stall_id == stall_id:
|
1041
|
+
if event_id is None:
|
1042
|
+
results.append(
|
1043
|
+
{
|
1044
|
+
"status": "skipped",
|
1045
|
+
"message": f"Product '{product.name}' has not been published yet",
|
1046
|
+
"product_name": product.name,
|
1047
|
+
"stall_name": stall_name,
|
1048
|
+
}
|
1049
|
+
)
|
1050
|
+
continue
|
1051
|
+
|
1052
|
+
try:
|
1053
|
+
self._nostr_client.delete_event(
|
1054
|
+
event_id, reason=f"Stall for '{product.name}' removed"
|
1055
|
+
)
|
1056
|
+
self.product_db[i] = (product, None)
|
1057
|
+
results.append(
|
1058
|
+
{
|
1059
|
+
"status": "success",
|
1060
|
+
"message": f"Product '{product.name}' removed",
|
1061
|
+
"product_name": product.name,
|
1062
|
+
"stall_name": stall_name,
|
1063
|
+
"event_id": str(event_id),
|
1064
|
+
}
|
1065
|
+
)
|
1066
|
+
except Exception as e:
|
1067
|
+
results.append(
|
1068
|
+
{
|
1069
|
+
"status": "error",
|
1070
|
+
"message": str(e),
|
1071
|
+
"product_name": product.name,
|
1072
|
+
"stall_name": stall_name,
|
1073
|
+
}
|
1074
|
+
)
|
1075
|
+
|
1076
|
+
# Now remove the stall itself
|
1077
|
+
if stall_index is not None:
|
1078
|
+
_, stall_event_id = self.stall_db[stall_index]
|
1079
|
+
if stall_event_id is None:
|
1080
|
+
results.append(
|
1081
|
+
{
|
1082
|
+
"status": "skipped",
|
1083
|
+
"message": f"Stall '{stall_name}' has not been published yet",
|
1084
|
+
"stall_name": stall_name,
|
1085
|
+
}
|
1086
|
+
)
|
1087
|
+
else:
|
1088
|
+
try:
|
1089
|
+
self._nostr_client.delete_event(
|
1090
|
+
stall_event_id, reason=f"Stall '{stall_name}' removed"
|
1091
|
+
)
|
1092
|
+
self.stall_db[stall_index] = (
|
1093
|
+
self.stall_db[stall_index][0],
|
1094
|
+
None,
|
1095
|
+
)
|
1096
|
+
results.append(
|
1097
|
+
{
|
1098
|
+
"status": "success",
|
1099
|
+
"message": f"Stall '{stall_name}' removed",
|
1100
|
+
"stall_name": stall_name,
|
1101
|
+
"event_id": str(stall_event_id),
|
1102
|
+
}
|
1103
|
+
)
|
1104
|
+
except Exception as e:
|
1105
|
+
results.append(
|
1106
|
+
{
|
1107
|
+
"status": "error",
|
1108
|
+
"message": str(e),
|
1109
|
+
"stall_name": stall_name,
|
1110
|
+
}
|
1111
|
+
)
|
1112
|
+
|
1113
|
+
return json.dumps(results)
|
1114
|
+
|
1115
|
+
except Exception as e:
|
1116
|
+
return json.dumps(
|
1117
|
+
[{"status": "error", "message": str(e), "stall_name": "unknown"}]
|
1118
|
+
)
|
1119
|
+
|
1120
|
+
def get_event_id(self, response: Any) -> str:
|
1121
|
+
"""Convert any response to a string event ID.
|
1122
|
+
|
1123
|
+
Args:
|
1124
|
+
response: Response that might contain an event ID
|
1125
|
+
|
1126
|
+
Returns:
|
1127
|
+
str: String representation of event ID or empty string if None
|
1128
|
+
"""
|
1129
|
+
if response is None:
|
1130
|
+
return ""
|
1131
|
+
return str(response)
|