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

Commit 5ba23b6

Browse files
authored
Merge pull request #420 from OpenBazaar/stats-collection
Auditing to Database
2 parents ddf06d3 + 0a006b2 commit 5ba23b6

File tree

8 files changed

+199
-58
lines changed

8 files changed

+199
-58
lines changed

api/restapi.py

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
import os
55
import obelisk
66
import nacl.encoding
7+
import time
78
from binascii import unhexlify
89
from collections import OrderedDict
910
from functools import wraps
@@ -172,7 +173,8 @@ def parse_profile(profile, temp_handle=None):
172173
"pgp_key": profile.pgp_key.public_key,
173174
"avatar_hash": profile.avatar_hash.encode("hex"),
174175
"header_hash": profile.header_hash.encode("hex"),
175-
"social_accounts": {}
176+
"social_accounts": {},
177+
"last_modified": profile.last_modified
176178
}
177179
}
178180
if temp_handle:
@@ -230,7 +232,8 @@ def parse_listings(listings):
230232
"currency_code": l.currency_code,
231233
"nsfw": l.nsfw,
232234
"origin": str(CountryCode.Name(l.origin)),
233-
"ships_to": []
235+
"ships_to": [],
236+
"last_modified": l.last_modified
234237
}
235238
if l.contract_type != 0:
236239
listing_json["contract_type"] = str(objects.Listings.ContractType.Name(l.contract_type))
@@ -445,6 +448,7 @@ def update_profile(self, request):
445448
key.public_key = self.keychain.verify_key.encode()
446449
key.signature = self.keychain.signing_key.sign(key.public_key)[:64]
447450
u.guid_key.MergeFrom(key)
451+
u.last_modified = int(time.time())
448452
p.update(u)
449453
request.write(json.dumps({"success": True}))
450454
request.finish()

db/datastore.py

Lines changed: 58 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
import os
44
import sqlite3 as lite
5+
import time
56
from api.utils import sanitize_html
67
from collections import Counter
78
from config import DATA_FOLDER
@@ -10,14 +11,14 @@
1011
from protos import objects
1112
from protos.objects import Listings, Followers, Following
1213
from os.path import join
13-
from db.migrations import migration1, migration2, migration3, migration4, migration5, migration6
14+
from db.migrations import migration1, migration2, migration3, migration4, migration5, migration6, migration7
1415

1516

1617
class Database(object):
1718

1819
__slots__ = ['PATH', 'filemap', 'profile', 'listings', 'keys', 'follow', 'messages',
1920
'notifications', 'broadcasts', 'vendors', 'moderators', 'purchases', 'sales',
20-
'cases', 'ratings', 'transactions', 'settings']
21+
'cases', 'ratings', 'transactions', 'settings', 'audit_shopping']
2122

2223
def __init__(self, testnet=False, filepath=None):
2324
object.__setattr__(self, 'PATH', self._database_path(testnet, filepath))
@@ -37,6 +38,7 @@ def __init__(self, testnet=False, filepath=None):
3738
object.__setattr__(self, 'ratings', Ratings(self.PATH))
3839
object.__setattr__(self, 'transactions', Transactions(self.PATH))
3940
object.__setattr__(self, 'settings', Settings(self.PATH))
41+
object.__setattr__(self, 'audit_shopping', ShoppingEvents(self.PATH))
4042

4143
self._initialize_datafolder_tree()
4244
self._initialize_database(self.PATH)
@@ -174,6 +176,17 @@ def _create_database(database_path):
174176
smtpNotifications INTEGER, smtpServer TEXT, smtpSender TEXT, smtpRecipient TEXT, smtpUsername TEXT,
175177
smtpPassword TEXT)''')
176178

179+
cursor.execute('''CREATE TABLE IF NOT EXISTS audit_shopping (
180+
audit_shopping_id integer PRIMARY KEY NOT NULL,
181+
shopper_guid text NOT NULL,
182+
contract_hash text NOT NULL,
183+
"timestamp" integer NOT NULL,
184+
action_id integer NOT NULL
185+
);''')
186+
cursor.execute('''CREATE INDEX IF NOT EXISTS shopper_guid_index ON audit_shopping
187+
(audit_shopping_id ASC);''')
188+
cursor.execute('''CREATE INDEX IF NOT EXISTS action_id_index ON audit_shopping (audit_shopping_id ASC);''')
189+
177190
conn.commit()
178191
conn.close()
179192

@@ -190,23 +203,30 @@ def _run_migrations(self):
190203
migration3.migrate(self.PATH)
191204
migration4.migrate(self.PATH)
192205
migration5.migrate(self.PATH)
206+
migration6.migrate(self.PATH)
193207
elif version == 1:
194208
migration2.migrate(self.PATH)
195209
migration3.migrate(self.PATH)
196210
migration4.migrate(self.PATH)
197211
migration5.migrate(self.PATH)
212+
migration6.migrate(self.PATH)
198213
elif version == 2:
199214
migration3.migrate(self.PATH)
200215
migration4.migrate(self.PATH)
201216
migration5.migrate(self.PATH)
217+
migration6.migrate(self.PATH)
202218
elif version == 3:
203219
migration4.migrate(self.PATH)
204220
migration5.migrate(self.PATH)
221+
migration6.migrate(self.PATH)
205222
elif version == 4:
206223
migration5.migrate(self.PATH)
207224
migration6.migrate(self.PATH)
208225
elif version == 5:
209226
migration6.migrate(self.PATH)
227+
migration7.migrate(self.PATH)
228+
elif version == 6:
229+
migration7.migrate(self.PATH)
210230

211231

212232
class HashMap(object):
@@ -1360,3 +1380,39 @@ def get_credentials(self):
13601380
ret = cursor.fetchone()
13611381
conn.close()
13621382
return ret
1383+
1384+
class ShoppingEvents(object):
1385+
"""
1386+
Stores audit events for shoppers on your storefront
1387+
"""
1388+
1389+
def __init__(self, database_path):
1390+
self.PATH = database_path
1391+
1392+
def set(self, shopper_guid, action_id, contract_hash=None):
1393+
conn = Database.connect_database(self.PATH)
1394+
with conn:
1395+
cursor = conn.cursor()
1396+
timestamp = int(time.time())
1397+
if not contract_hash:
1398+
contract_hash = ''
1399+
cursor.execute('''INSERT INTO audit_shopping(shopper_guid, timestamp, contract_hash, action_id) VALUES
1400+
(?,?,?,?)''', (shopper_guid, timestamp, contract_hash, action_id))
1401+
conn.commit()
1402+
conn.close()
1403+
1404+
def get(self):
1405+
conn = Database.connect_database(self.PATH)
1406+
cursor = conn.cursor()
1407+
cursor.execute('''SELECT * FROM audit_shopping''')
1408+
ret = cursor.fetchall()
1409+
conn.close()
1410+
return ret
1411+
1412+
def get_events_by_id(self, event_id):
1413+
conn = Database.connect_database(self.PATH)
1414+
cursor = conn.cursor()
1415+
cursor.execute('''SELECT * FROM audit_shopping WHERE event_id=?''', event_id)
1416+
ret = cursor.fetchall()
1417+
conn.close()
1418+
return ret

db/migrations/migration7.py

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
import sqlite3
2+
3+
4+
def migrate(database_path):
5+
print "migrating to db version 7"
6+
conn = sqlite3.connect(database_path)
7+
conn.text_factory = str
8+
cursor = conn.cursor()
9+
10+
# create new table
11+
cursor.execute('''CREATE TABLE IF NOT EXISTS audit_shopping (
12+
audit_shopping_id integer PRIMARY KEY NOT NULL,
13+
shopper_guid text NOT NULL,
14+
contract_hash text,
15+
"timestamp" integer NOT NULL,
16+
action_id integer NOT NULL
17+
);''')
18+
cursor.execute('''CREATE INDEX IF NOT EXISTS shopper_guid_index ON audit_shopping (audit_shopping_id ASC);''')
19+
cursor.execute('''CREATE INDEX IF NOT EXISTS action_id_index ON audit_shopping (audit_shopping_id ASC);''')
20+
21+
# update version
22+
cursor.execute('''PRAGMA user_version = 7''')
23+
conn.commit()
24+
conn.close()

market/audit.py

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
__author__ = 'hoffmabc'
2+
3+
from log import Logger
4+
5+
6+
class Audit(object):
7+
"""
8+
A class for handling audit information
9+
"""
10+
11+
def __init__(self, db):
12+
self.db = db
13+
14+
self.log = Logger(system=self)
15+
16+
self.action_ids = {
17+
"GET_PROFILE": 0,
18+
"GET_CONTRACT": 1,
19+
"GET_LISTINGS": 2, # Click Store tab
20+
"GET_FOLLOWING": 3,
21+
"GET_FOLLOWERS": 4,
22+
"GET_RATINGS": 5
23+
}
24+
25+
def record(self, guid, action_id, contract_hash=None):
26+
self.log.info("Recording Audit Event [%s]" % action_id)
27+
28+
if action_id in self.action_ids:
29+
self.db.audit_shopping.set(guid, self.action_ids[action_id], contract_hash)
30+
else:
31+
self.log.error("Could not identify this action id")

market/contracts.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -149,7 +149,8 @@ def create(self,
149149
"metadata": {
150150
"version": "1",
151151
"category": metadata_category.lower(),
152-
"category_sub": "fixed price"
152+
"category_sub": "fixed price",
153+
"last_modified": int(time.time())
153154
},
154155
"id": {
155156
"guid": self.keychain.guid.encode("hex"),
@@ -1059,6 +1060,7 @@ def save(self):
10591060
data.contract_type = listings.DIGITAL_GOOD
10601061
elif self.contract["vendor_offer"]["listing"]["metadata"]["category"].lower() == "service":
10611062
data.contract_type = listings.SERVICE
1063+
data.last_modified = int(time.time())
10621064

10631065
# save the mapping of the contract file path and contract hash in the database
10641066
self.db.filemap.insert(data.contract_hash.encode("hex"), file_path[len(DATA_FOLDER):])

market/protocol.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
from interfaces import MessageProcessor, BroadcastListener, MessageListener, NotificationListener
1111
from keys.bip32utils import derive_childkey
1212
from log import Logger
13+
from market.audit import Audit
1314
from market.contracts import Contract
1415
from market.moderation import process_dispute, close_dispute
1516
from market.profile import Profile
@@ -33,6 +34,7 @@ def __init__(self, node, router, signing_key, database):
3334
self.node = node
3435
RPCProtocol.__init__(self, node, router)
3536
self.log = Logger(system=self)
37+
self.audit = Audit(db=database)
3638
self.multiplexer = None
3739
self.db = database
3840
self.signing_key = signing_key
@@ -50,6 +52,7 @@ def add_listener(self, listener):
5052

5153
def rpc_get_contract(self, sender, contract_hash):
5254
self.log.info("serving contract %s to %s" % (contract_hash.encode('hex'), sender))
55+
self.audit.record(sender.id.encode("hex"), "GET_CONTRACT", contract_hash.encode('hex'))
5356
self.router.addContact(sender)
5457
try:
5558
with open(self.db.filemap.get_file(contract_hash.encode("hex")), "r") as filename:
@@ -75,6 +78,7 @@ def rpc_get_image(self, sender, image_hash):
7578

7679
def rpc_get_profile(self, sender):
7780
self.log.info("serving profile to %s" % sender)
81+
self.audit.record(sender.id.encode("hex"), "GET_PROFILE")
7882
self.router.addContact(sender)
7983
try:
8084
proto = Profile(self.db).get(True)
@@ -101,6 +105,7 @@ def rpc_get_user_metadata(self, sender):
101105

102106
def rpc_get_listings(self, sender):
103107
self.log.info("serving store listings to %s" % sender)
108+
self.audit.record(sender.id.encode("hex"), "GET_LISTINGS")
104109
self.router.addContact(sender)
105110
try:
106111
p = Profile(self.db).get()
@@ -186,6 +191,7 @@ def rpc_unfollow(self, sender, signature):
186191

187192
def rpc_get_followers(self, sender, start=None):
188193
self.log.info("serving followers list to %s" % sender)
194+
self.audit.record(sender.id.encode("hex"), "GET_FOLLOWERS")
189195
self.router.addContact(sender)
190196
if start is not None:
191197
ser = self.db.follow.get_followers(int(start))
@@ -195,6 +201,7 @@ def rpc_get_followers(self, sender, start=None):
195201

196202
def rpc_get_following(self, sender):
197203
self.log.info("serving following list to %s" % sender)
204+
self.audit.record(sender.id.encode("hex"), "GET_FOLLOWING")
198205
self.router.addContact(sender)
199206
ser = self.db.follow.get_following()
200207
if ser is None:
@@ -346,6 +353,7 @@ def rpc_dispute_close(self, sender, pubkey, encrypted):
346353
def rpc_get_ratings(self, sender, listing_hash=None):
347354
a = "ALL" if listing_hash is None else listing_hash.encode("hex")
348355
self.log.info("serving ratings for contract %s to %s" % (a, sender))
356+
self.audit.record(sender.id.encode("hex"), "GET_RATINGS", listing_hash.encode("hex"))
349357
self.router.addContact(sender)
350358
try:
351359
ratings = []

protos/objects.proto

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,7 @@ message Profile {
7474
PublicKey pgp_key = 21; // pgp signature covers guid
7575
bytes avatar_hash = 22;
7676
bytes header_hash = 23;
77+
uint64 last_modified = 24;
7778

7879
// Social media account for the profile
7980
message SocialAccount {
@@ -121,6 +122,7 @@ message Listings {
121122
bytes avatar_hash = 10;
122123
string handle = 11;
123124
ContractType contract_type = 12;
125+
uint64 last_modified = 13;
124126
}
125127

126128
enum ContractType {

0 commit comments

Comments
 (0)