Skip to content
This repository was archived by the owner on May 16, 2019. It is now read-only.

Commit c58528c

Browse files
committed
Merge pull request #367 from OpenBazaar/mail-notifications
SMTP Notifications
2 parents 96aba83 + 68d3289 commit c58528c

File tree

9 files changed

+211
-18
lines changed

9 files changed

+211
-18
lines changed

api/restapi.py

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -849,6 +849,14 @@ def set_settings(self, request):
849849
if resolver != get_value("CONSTANTS", "RESOLVER"):
850850
set_value("CONSTANTS", "RESOLVER", resolver)
851851

852+
if "smtp_notifications" not in request.args:
853+
request.args["smtp_notifications"] = ['false']
854+
855+
smtp_attrs = ["smtp_server", "smtp_sender", "smtp_recipient", "smtp_username", "smtp_password"]
856+
for smtp_attr in smtp_attrs:
857+
if smtp_attr not in request.args:
858+
request.args[smtp_attr] = ['']
859+
852860
settings_list = settings.get()
853861
if "moderators" in request.args and settings_list is not None:
854862
mod_json = settings_list[11]
@@ -871,7 +879,13 @@ def set_settings(self, request):
871879
json.dumps(request.args["blocked"] if request.args["blocked"] != "" else []),
872880
request.args["terms_conditions"][0],
873881
request.args["refund_policy"][0],
874-
json.dumps(request.args["moderators"] if request.args["moderators"] != "" else [])
882+
json.dumps(request.args["moderators"] if request.args["moderators"] != "" else []),
883+
1 if str_to_bool(request.args["smtp_notifications"][0]) else 0,
884+
request.args["smtp_server"][0],
885+
request.args["smtp_sender"][0],
886+
request.args["smtp_recipient"][0],
887+
request.args["smtp_username"][0],
888+
request.args["smtp_password"][0]
875889
)
876890

877891
request.write(json.dumps({"success": True}, indent=4))
@@ -913,7 +927,13 @@ def get_settings(self, request):
913927
"refund_policy": "" if settings[10] is None else settings[10],
914928
"resolver": get_value("CONSTANTS", "RESOLVER"),
915929
"network_connection": nat_type,
916-
"transaction_fee": TRANSACTION_FEE
930+
"transaction_fee": TRANSACTION_FEE,
931+
"smtp_notifications": True if settings[14] == 1 else False,
932+
"smtp_server": settings[15],
933+
"smtp_sender": settings[16],
934+
"smtp_recipient": settings[17],
935+
"smtp_username": settings[18],
936+
"smtp_password": settings[19],
917937
}
918938
mods = []
919939
try:

db/datastore.py

Lines changed: 17 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
from protos import objects
1111
from protos.objects import Listings, Followers, Following
1212
from os.path import join
13-
from db.migrations import migration1, migration2, migration3
13+
from db.migrations import migration1, migration2, migration3, migration4
1414

1515

1616
class Database(object):
@@ -116,7 +116,7 @@ def _create_database(database_path):
116116
conn = lite.connect(database_path)
117117
cursor = conn.cursor()
118118

119-
cursor.execute('''PRAGMA user_version = 3''')
119+
cursor.execute('''PRAGMA user_version = 4''')
120120
cursor.execute('''CREATE TABLE hashmap(hash TEXT PRIMARY KEY, filepath TEXT)''')
121121

122122
cursor.execute('''CREATE TABLE profile(id INTEGER PRIMARY KEY, serializedUserInfo BLOB, tempHandle TEXT)''')
@@ -169,7 +169,9 @@ def _create_database(database_path):
169169

170170
cursor.execute('''CREATE TABLE settings(id INTEGER PRIMARY KEY, refundAddress TEXT, currencyCode TEXT,
171171
country TEXT, language TEXT, timeZone TEXT, notifications INTEGER, shippingAddresses BLOB, blocked BLOB,
172-
termsConditions TEXT, refundPolicy TEXT, moderatorList BLOB, username TEXT, password TEXT)''')
172+
termsConditions TEXT, refundPolicy TEXT, moderatorList BLOB, username TEXT, password TEXT,
173+
smtpNotifications INTEGER, smtpServer TEXT, smtpSender TEXT, smtpRecipient TEXT, smtpUsername TEXT,
174+
smtpPassword TEXT)''')
173175

174176
conn.commit()
175177
conn.close()
@@ -180,16 +182,21 @@ def _run_migrations(self):
180182
cursor.execute('''PRAGMA user_version''')
181183
version = cursor.fetchone()[0]
182184
conn.close()
185+
183186
if version == 0:
184187
migration1.migrate(self.PATH)
185188
migration2.migrate(self.PATH)
186189
migration3.migrate(self.PATH)
190+
migration4.migrate(self.PATH)
187191
elif version == 1:
188192
migration2.migrate(self.PATH)
189193
migration3.migrate(self.PATH)
194+
migration4.migrate(self.PATH)
190195
elif version == 2:
191196
migration3.migrate(self.PATH)
192-
197+
migration4.migrate(self.PATH)
198+
elif version == 3:
199+
migration4.migrate(self.PATH)
193200

194201
class HashMap(object):
195202
"""
@@ -1244,16 +1251,19 @@ def __init__(self, database_path):
12441251
self.PATH = database_path
12451252

12461253
def update(self, refundAddress, currencyCode, country, language, timeZone, notifications,
1247-
shipping_addresses, blocked, terms_conditions, refund_policy, moderator_list):
1254+
shipping_addresses, blocked, terms_conditions, refund_policy, moderator_list, smtp_notifications,
1255+
smtp_server, smtp_sender, smtp_recipient, smtp_username, smtp_password):
12481256
conn = Database.connect_database(self.PATH)
12491257
with conn:
12501258
cursor = conn.cursor()
12511259
cursor.execute('''INSERT OR REPLACE INTO settings(id, refundAddress, currencyCode, country,
12521260
language, timeZone, notifications, shippingAddresses, blocked, termsConditions,
1253-
refundPolicy, moderatorList) VALUES (?,?,?,?,?,?,?,?,?,?,?,?)''',
1261+
refundPolicy, moderatorList, smtpNotifications, smtpServer, smtpSender,
1262+
smtpRecipient, smtpUsername, smtpPassword) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)''',
12541263
(1, refundAddress, currencyCode, country, language, timeZone,
12551264
notifications, shipping_addresses, blocked, terms_conditions,
1256-
refund_policy, moderator_list))
1265+
refund_policy, moderator_list, smtp_notifications, smtp_server,
1266+
smtp_sender, smtp_recipient, smtp_username, smtp_password))
12571267
conn.commit()
12581268
conn.close()
12591269

db/migrations/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,2 @@
11
__author__ = 'chris'
2+
__all__ = ['migration1', 'migration2', 'migration3', 'migration4']

db/migrations/migration4.py

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import sqlite3
2+
3+
4+
def migrate(database_path):
5+
print "migrating to db version 4"
6+
conn = sqlite3.connect(database_path)
7+
conn.text_factory = str
8+
cursor = conn.cursor()
9+
10+
# update settings table to include smtp server settings
11+
cursor.execute('''ALTER TABLE settings ADD COLUMN "smtpNotifications" INTEGER''')
12+
cursor.execute('''ALTER TABLE settings ADD COLUMN "smtpServer" TEXT''')
13+
cursor.execute('''ALTER TABLE settings ADD COLUMN "smtpSender" TEXT''')
14+
cursor.execute('''ALTER TABLE settings ADD COLUMN "smtpRecipient" TEXT''')
15+
cursor.execute('''ALTER TABLE settings ADD COLUMN "smtpUsername" TEXT''')
16+
cursor.execute('''ALTER TABLE settings ADD COLUMN "smtpPassword" TEXT''')
17+
18+
# update version
19+
cursor.execute('''PRAGMA user_version = 4''')
20+
conn.commit()
21+
conn.close()

db/tests/test_datastore.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -335,12 +335,12 @@ def test_VendorStore(self):
335335
self.assertEqual(v, {})
336336

337337
def test_Settings(self):
338-
NUM_SETTINGS = 14
338+
NUM_SETTINGS = 20
339339
settings = self.settings.get()
340340
self.assertIsNone(settings)
341341

342342
self.settings.update('NEW_ADDRESS', 'BTC', 'AUSTRALIA', 'EN',
343-
'', '', '', '', '', '', '')
343+
'', '', '', '', '', '', '', '', '', '', '', '', '')
344344
settings = self.settings.get()
345345
self.assertEqual(NUM_SETTINGS, len(settings))
346346

market/contracts.py

Lines changed: 51 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
from market.transactions import BitcoinTransaction
2828
from protos.countries import CountryCode
2929
from protos.objects import Listings
30+
from market.smtpnotification import SMTPNotification
3031

3132

3233
class Contract(object):
@@ -531,6 +532,13 @@ def accept_order_confirmation(self, notification_listener, confirmation_json=Non
531532
vendor_guid = self.contract["vendor_offer"]["listing"]["id"]["guid"]
532533
self.notification_listener.notify(vendor_guid, handle, "order confirmation", contract_hash, title,
533534
image_hash)
535+
536+
# Send SMTP notification
537+
notification = SMTPNotification(self.db)
538+
notification.send("[OpenBazaar] Order Confirmed and Shipped",
539+
"You have received an order confirmation.<br><br>"
540+
"Order: %s<br>Vendor: %s<br>Title: %s<br>" % (contract_hash, vendor_guid, title))
541+
534542
return True
535543
except Exception, e:
536544
return e.message
@@ -747,6 +755,14 @@ def accept_receipt(self, notification_listener, blockchain, receipt_json=None):
747755

748756
self.notification_listener.notify(buyer_guid, handle, "rating received", order_id, title, image_hash)
749757

758+
notification_rater = handle if handle else buyer_guid.encode('hex')
759+
760+
notification = SMTPNotification(self.db)
761+
notification.send("[OpenBazaar] New Rating Received",
762+
"You received a new rating from %s for Order #%s - \"%s\". " % (notification_rater,
763+
order_id,
764+
title))
765+
750766
if "rating" in self.contract["buyer_receipt"]["receipt"]:
751767
self.db.ratings.add_rating(self.contract["buyer_receipt"]["receipt"]
752768
["rating"]["tx_summary"]["listing"],
@@ -803,9 +819,11 @@ def await_funding(self, notification_listener, libbitcoin_client, proofSig, is_p
803819
self.contract["vendor_offer"]["listing"]["metadata"]["category"])
804820
else:
805821
file_path = os.path.join(DATA_FOLDER, "store", "contracts", "unfunded", order_id + ".json")
822+
title = self.contract["vendor_offer"]["listing"]["item"]["title"]
823+
description = self.contract["vendor_offer"]["listing"]["item"]["description"]
806824
self.db.sales.new_sale(order_id,
807-
self.contract["vendor_offer"]["listing"]["item"]["title"],
808-
self.contract["vendor_offer"]["listing"]["item"]["description"],
825+
title,
826+
description,
809827
time.time(),
810828
self.contract["buyer_order"]["order"]["payment"]["amount"],
811829
payment_address,
@@ -814,6 +832,17 @@ def await_funding(self, notification_listener, libbitcoin_client, proofSig, is_p
814832
buyer,
815833
self.contract["vendor_offer"]["listing"]["metadata"]["category"])
816834

835+
try:
836+
notification = SMTPNotification(self.db)
837+
notification.send("[OpenBazaar] Order Received", "Order #%s<br>"
838+
"Buyer: %s<br>"
839+
"BTC Address: %s<br>"
840+
"Title: %s<br>"
841+
"Description: %s<br>"
842+
% (order_id, buyer, payment_address, title, description))
843+
except Exception as e:
844+
self.log.info("Error with SMTP notification: %s" % e.message)
845+
817846
with open(file_path, 'w') as outfile:
818847
outfile.write(json.dumps(self.contract, indent=4))
819848
self.blockchain.subscribe_address(str(payment_address), notification_cb=self.on_tx_received)
@@ -843,8 +872,8 @@ def on_tx_received(self, address_version, address_hash, height, block_hash, tx):
843872
if self.amount_funded >= amount_to_pay: # if fully funded
844873
self.payment_received()
845874

846-
except Exception:
847-
self.log.critical("Error processing bitcoin transaction")
875+
except Exception as e:
876+
self.log.critical("Error processing bitcoin transaction: %s" % e.message)
848877

849878
def payment_received(self):
850879
self.blockchain.unsubscribe_address(
@@ -865,6 +894,14 @@ def payment_received(self):
865894
vendor_guid = self.contract["vendor_offer"]["listing"]["id"]["guid"]
866895
self.notification_listener.notify(unhexlify(vendor_guid), handle, "payment received",
867896
order_id, title, image_hash)
897+
898+
notification = SMTPNotification(self.db)
899+
notification.send("[OpenBazaar] Purchase Payment Received", "Your payment was received.<br><br>"
900+
"Order: %s<br>"
901+
"Vendor: %s<br>"
902+
"Title: %s"
903+
% (order_id, vendor_guid, title))
904+
868905
# update the db
869906
if self.db.purchases.get_status(order_id) == 0:
870907
self.db.purchases.update_status(order_id, 1)
@@ -880,6 +917,11 @@ def payment_received(self):
880917
handle = ""
881918
self.notification_listener.notify(unhexlify(buyer_guid), handle, "new order", order_id,
882919
title, image_hash)
920+
921+
notification = SMTPNotification(self.db)
922+
notification.send("[OpenBazaar] Payment for Order Received", "Payment was received for Order #%s."
923+
% order_id)
924+
883925
self.db.sales.update_status(order_id, 1)
884926
self.db.sales.update_outpoint(order_id, json.dumps(self.outpoints))
885927
self.log.info("Received new order %s" % order_id)
@@ -1070,6 +1112,11 @@ def process_refund(self, refund_json, blockchain, notification_listener):
10701112
handle = ""
10711113
notification_listener.notify(buyer_guid, handle, "refund", order_id, title, image_hash)
10721114

1115+
notification = SMTPNotification(self.db)
1116+
notification.send("[OpenBazaar] Refund Received", "You received a refund.<br><br>"
1117+
"Order: %s<br>Title: %s"
1118+
% (order_id, title))
1119+
10731120
def verify(self, sender_key):
10741121
"""
10751122
Validate that an order sent over by a buyer is filled out correctly.

market/moderation.py

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
from keys.keychain import KeyChain
1212
from market.contracts import Contract
1313
from protos.objects import PlaintextMessage
14+
from market.smtpnotification import SMTPNotification
1415

1516

1617
def process_dispute(contract, db, message_listener, notification_listener, testnet):
@@ -121,10 +122,20 @@ def process_dispute(contract, db, message_listener, notification_listener, testn
121122
raise Exception("Order ID for dispute not found")
122123

123124
message_listener.notify(p, "")
125+
title = contract["vendor_offer"]["listing"]["item"]["title"]
124126
notification_listener.notify(guid, handle, "dispute_open", order_id,
125-
contract["vendor_offer"]["listing"]["item"]["title"],
127+
title,
126128
unhexlify(contract["vendor_offer"]["listing"]["item"]["image_hashes"][0]))
127129

130+
# Send SMTP notification
131+
notification = SMTPNotification(db)
132+
guid = guid.encode("hex")
133+
notification.send("[OpenBazaar] Dispute Opened",
134+
"A dispute has been opened.\n\n"
135+
"Order: %s\n"
136+
"Opened By: %s\n"
137+
"Title: %s" % (order_id, guid, title))
138+
128139

129140
def close_dispute(resolution_json, db, message_listener, notification_listener, testnet):
130141
"""
@@ -183,6 +194,16 @@ def close_dispute(resolution_json, db, message_listener, notification_listener,
183194
p.avatar_hash = moderator_avatar
184195

185196
message_listener.notify(p, "")
197+
title = contract["vendor_offer"]["listing"]["item"]["title"]
186198
notification_listener.notify(moderator_guid, moderator_handle, "dispute_close", order_id,
187-
contract["vendor_offer"]["listing"]["item"]["title"],
199+
title,
188200
contract["vendor_offer"]["listing"]["item"]["image_hashes"][0])
201+
202+
# Send SMTP notification
203+
notification = SMTPNotification(db)
204+
guid = moderator_guid.encode("hex")
205+
notification.send("[OpenBazaar] Dispute Closed",
206+
"A dispute has been closed.\n\n"
207+
"Order: %s\n"
208+
"Closed By: %s\n"
209+
"Title: %s" % (order_id, guid, title))

market/protocol.py

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
from market.contracts import Contract
1414
from market.moderation import process_dispute, close_dispute
1515
from market.profile import Profile
16+
from market.smtpnotification import SMTPNotification
1617
from nacl.public import PublicKey, Box
1718
from net.rpcudp import RPCProtocol
1819
from protos.message import GET_CONTRACT, GET_IMAGE, GET_PROFILE, GET_LISTINGS, GET_USER_METADATA,\
@@ -157,6 +158,14 @@ def rpc_follow(self, sender, proto, signature):
157158
listener.notify(sender.id, f.metadata.handle, "follow", "", "", f.metadata.avatar_hash)
158159
except DoesNotImplement:
159160
pass
161+
162+
# Send SMTP notification
163+
notification = SMTPNotification(self.db)
164+
notification.send("[OpenBazaar] %s is now following you!" % m.name,
165+
"You have a new follower:<br><br>Name: %s<br>GUID: <a href=\"ob://%s\">%s</a><br>"
166+
"Handle: %s" %
167+
(m.name, f.guid.encode('hex'), f.guid.encode('hex'), m.handle))
168+
160169
return ["True", m.SerializeToString(), self.signing_key.sign(m.SerializeToString())[:64]]
161170
except Exception:
162171
self.log.warning("failed to validate follower")
@@ -315,8 +324,9 @@ def rpc_dispute_open(self, sender, pubkey, encrypted):
315324
self.router.addContact(sender)
316325
self.log.info("Contract dispute opened by %s" % sender)
317326
return ["True"]
318-
except Exception:
327+
except Exception as e:
319328
self.log.error("unable to parse disputed contract from %s" % sender)
329+
self.log.error("Exception: %s" % e.message)
320330
return ["False"]
321331

322332
def rpc_dispute_close(self, sender, pubkey, encrypted):

0 commit comments

Comments
 (0)