model.py 26 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975
  1. import random
  2. import re
  3. import sqlite3 as db
  4. import sys
  5. import uuid
  6. from datetime import timedelta, datetime
  7. from math import floor
  8. import db_setup
  9. from game import CURRENCY_NAME
  10. from util import random_chars
  11. from debug import debug
  12. # connection: db.Connection = None
  13. # cursor: db.Cursor = None
  14. connection = None # no type annotations in python 3.5
  15. cursor = None # no type annotations in python 3.5
  16. db_name = None
  17. def query_save_name():
  18. global db_name
  19. if debug:
  20. db_name = 'test.db'
  21. return
  22. while True:
  23. save_name = input('Name of the savegame: ')
  24. if re.match(r"[A-Za-z0-9.-]{0,50}", save_name):
  25. db_name = save_name + '.db'
  26. return
  27. else:
  28. print('Must match "[A-Za-z0-9.-]{0,50}"')
  29. def connect(reconnect=False):
  30. global connection
  31. global cursor
  32. global db_name
  33. if reconnect:
  34. connection.commit()
  35. connection.close()
  36. cursor = None
  37. connection = None
  38. db_name = None
  39. if connection is None or cursor is None:
  40. query_save_name()
  41. try:
  42. connection = db.connect(db_name)
  43. # connection.text_factory = lambda x: unicode(x, 'utf-8', 'ignore')
  44. cursor = connection.cursor()
  45. except db.Error as e:
  46. print("Database error %s:" % e.args[0])
  47. sys.exit(1)
  48. # finally:
  49. # if con is not None:
  50. # con.close()
  51. def setup():
  52. connect()
  53. db_setup.setup(cursor)
  54. connection.commit()
  55. def used_key_count():
  56. connect()
  57. cursor.execute('''
  58. SELECT COUNT(*)
  59. FROM keys
  60. WHERE used_by_user_id IS NOT NULL
  61. ''')
  62. return cursor.fetchone()[0]
  63. def login(username, password):
  64. connect()
  65. # do not allow login as bank
  66. if password == '':
  67. return None
  68. cursor.execute('''
  69. SELECT rowid
  70. FROM users
  71. WHERE username = ?
  72. AND password = ?
  73. ''', (username, password))
  74. user_id = cursor.fetchone()
  75. if user_id:
  76. return new_session(user_id)
  77. else:
  78. return None
  79. def register(username, password, game_key):
  80. connect()
  81. if username == '':
  82. return False
  83. if password == '':
  84. return False
  85. cursor.execute('''
  86. INSERT INTO users
  87. (username, password)
  88. VALUES (? , ?)
  89. ''', (username, password))
  90. own(get_user_id_by_name(username), CURRENCY_NAME)
  91. if game_key != '':
  92. if valid_key(game_key):
  93. activate_key(game_key, get_user_id_by_name(username))
  94. return True
  95. def own(user_id, ownable_name):
  96. if not isinstance(ownable_name, str):
  97. return AssertionError('A name must be a string.')
  98. cursor.execute('''
  99. WITH one_ownable_id AS (SELECT rowid FROM ownables WHERE name = ?),
  100. one_user_id AS (SELECT ?)
  101. INSERT INTO ownership (user_id, ownable_id)
  102. SELECT *
  103. FROM one_user_id, one_ownable_id
  104. WHERE NOT EXISTS (
  105. SELECT * FROM ownership
  106. WHERE ownership.user_id IN one_user_id
  107. AND ownership.ownable_id IN one_ownable_id
  108. )
  109. ''', (ownable_name, user_id,))
  110. def send_ownable(from_user_id, to_user_id, ownable_name, amount):
  111. connect()
  112. if amount < 0:
  113. return False
  114. if from_user_id != bank_id():
  115. cursor.execute('''
  116. UPDATE ownership
  117. SET amount = amount - ?
  118. WHERE user_id = ?
  119. AND ownable_id = (SELECT rowid FROM ownables WHERE name = ?)
  120. ''', (amount, from_user_id, ownable_name,))
  121. cursor.execute('''
  122. UPDATE ownership
  123. SET amount = amount + ?
  124. WHERE user_id = ?
  125. AND ownable_id = (SELECT rowid FROM ownables WHERE name = ?)
  126. ''', (amount, to_user_id, ownable_name))
  127. return True
  128. def valid_key(key):
  129. connect()
  130. cursor.execute('''
  131. SELECT key
  132. FROM keys
  133. WHERE used_by_user_id IS NULL
  134. AND key = ?
  135. ''', (key,))
  136. if cursor.fetchone():
  137. return True
  138. else:
  139. return False
  140. def new_session(user_id):
  141. connect()
  142. session_id = str(uuid.uuid4())
  143. cursor.execute('''
  144. INSERT INTO SESSIONS
  145. (user_id, session_id)
  146. VALUES (? , ?)
  147. ''', (user_id[0], session_id))
  148. return session_id
  149. def save_key(key):
  150. connect()
  151. cursor.execute('''
  152. INSERT INTO keys
  153. (key)
  154. VALUES (?)
  155. ''', (key,))
  156. def drop_old_sessions():
  157. connect()
  158. cursor.execute('''
  159. DELETE FROM sessions
  160. WHERE
  161. (SELECT COUNT(*) as newer
  162. FROM sessions s2
  163. WHERE user_id = s2.user_id
  164. AND rowid < s2.rowid) >= 10
  165. ''')
  166. def user_exists(username):
  167. connect()
  168. cursor.execute('''
  169. SELECT rowid
  170. FROM users
  171. WHERE username = ?
  172. ''', (username,))
  173. if cursor.fetchone():
  174. return True
  175. else:
  176. return False
  177. def unused_keys():
  178. connect()
  179. cursor.execute('''
  180. SELECT key
  181. FROM keys
  182. WHERE used_by_user_id IS NULL
  183. ''')
  184. return [str(key[0]).strip().upper() for key in cursor.fetchall()]
  185. def get_user_id_by_session_id(session_id):
  186. connect()
  187. cursor.execute('''
  188. SELECT users.rowid
  189. FROM sessions, users
  190. WHERE sessions.session_id = ?
  191. AND users.rowid = sessions.user_id
  192. ''', (session_id,))
  193. ids = cursor.fetchone()
  194. if not ids:
  195. return False
  196. return ids[0]
  197. def get_user_id_by_name(username):
  198. connect()
  199. cursor.execute('''
  200. SELECT users.rowid
  201. FROM users
  202. WHERE username = ?
  203. ''', (username,))
  204. return cursor.fetchone()[0]
  205. def get_user_ownership(user_id):
  206. connect()
  207. cursor.execute('''
  208. SELECT
  209. ownables.name,
  210. ownership.amount,
  211. COALESCE (
  212. CASE -- sum score for each of the users ownables
  213. WHEN ownership.ownable_id = ? THEN 1
  214. ELSE (SELECT price
  215. FROM transactions
  216. WHERE ownable_id = ownership.ownable_id
  217. ORDER BY dt DESC
  218. LIMIT 1)
  219. END, 0) AS price,
  220. (SELECT MAX("limit")
  221. FROM orders, ownership o2
  222. WHERE o2.rowid = orders.ownership_id
  223. AND o2.ownable_id = ownership.ownable_id
  224. AND buy
  225. AND NOT stop_loss) AS bid,
  226. (SELECT MIN("limit")
  227. FROM orders, ownership o2
  228. WHERE o2.rowid = orders.ownership_id
  229. AND o2.ownable_id = ownership.ownable_id
  230. AND NOT buy
  231. AND NOT stop_loss) AS ask
  232. FROM ownership, ownables
  233. WHERE user_id = ?
  234. AND ownership.amount > 0
  235. AND ownership.ownable_id = ownables.rowid
  236. ''', (currency_id(), user_id,))
  237. return cursor.fetchall()
  238. def activate_key(key, user_id):
  239. connect()
  240. cursor.execute('''
  241. UPDATE keys
  242. SET used_by_user_id = ?
  243. WHERE used_by_user_id IS NULL
  244. AND key = ?
  245. ''', (user_id, key,))
  246. send_ownable(bank_id(), user_id, CURRENCY_NAME, 1000)
  247. def bank_id():
  248. connect()
  249. cursor.execute('''
  250. SELECT users.rowid
  251. FROM users
  252. WHERE username = 'bank'
  253. ''')
  254. return cursor.fetchone()[0]
  255. def valid_session_id(session_id):
  256. connect()
  257. cursor.execute('''
  258. SELECT rowid
  259. FROM sessions
  260. WHERE session_id = ?
  261. ''', (session_id,))
  262. if cursor.fetchone():
  263. return True
  264. else:
  265. return False
  266. def get_user_orders(user_id):
  267. connect()
  268. cursor.execute('''
  269. SELECT
  270. CASE
  271. WHEN orders.buy THEN 'Buy'
  272. ELSE 'Sell'
  273. END,
  274. ownables.name,
  275. (orders.ordered_amount - orders.executed_amount) || '/' || orders.ordered_amount,
  276. orders."limit",
  277. CASE
  278. WHEN orders."limit" IS NULL THEN NULL
  279. WHEN orders.stop_loss THEN 'Yes'
  280. ELSE 'No'
  281. END,
  282. datetime(orders.expiry_dt),
  283. orders.rowid
  284. FROM orders, ownables, ownership
  285. WHERE ownership.user_id = ?
  286. AND ownership.ownable_id = ownables.rowid
  287. AND orders.ownership_id = ownership.rowid
  288. ORDER BY ownables.name ASC, orders.stop_loss ASC, orders.buy DESC, orders."limit" ASC
  289. ''', (user_id,))
  290. return cursor.fetchall()
  291. def get_ownable_orders(ownable_id):
  292. connect()
  293. cursor.execute('''
  294. SELECT
  295. CASE
  296. WHEN orders.buy THEN 'Buy'
  297. ELSE 'Sell'
  298. END,
  299. ownables.name,
  300. orders.ordered_amount - orders.executed_amount,
  301. orders."limit",
  302. CASE
  303. WHEN orders."limit" IS NULL THEN NULL
  304. WHEN orders.stop_loss THEN 'Yes'
  305. ELSE 'No'
  306. END,
  307. datetime(orders.expiry_dt),
  308. orders.rowid
  309. FROM orders, ownables, ownership
  310. WHERE ownership.ownable_id = ?
  311. AND ownership.ownable_id = ownables.rowid
  312. AND orders.ownership_id = ownership.rowid
  313. ORDER BY ownables.name ASC, orders.stop_loss ASC, orders.buy DESC, orders."limit" ASC
  314. ''', (ownable_id,))
  315. return cursor.fetchall()
  316. def sell_ordered_amount(user_id, ownable_id):
  317. connect()
  318. cursor.execute('''
  319. SELECT COALESCE(SUM(orders.ordered_amount - orders.executed_amount),0)
  320. FROM orders, ownership
  321. WHERE ownership.rowid = orders.ownership_id
  322. AND ownership.user_id = ?
  323. AND ownership.ownable_id = ?
  324. AND NOT orders.buy
  325. ''', (user_id, ownable_id))
  326. return cursor.fetchone()[0]
  327. def available_amount(user_id, ownable_id):
  328. connect()
  329. cursor.execute('''
  330. SELECT amount
  331. FROM ownership
  332. WHERE user_id = ?
  333. AND ownable_id = ?
  334. ''', (user_id, ownable_id))
  335. return cursor.fetchone()[0] - sell_ordered_amount(user_id, ownable_id)
  336. def user_owns_at_least(amount, user_id, ownable_id):
  337. connect()
  338. if not isinstance(amount, float) and not isinstance(amount, int):
  339. # comparison of float with strings does not work so well in sql
  340. raise AssertionError()
  341. cursor.execute('''
  342. SELECT rowid
  343. FROM ownership
  344. WHERE user_id = ?
  345. AND ownable_id = ?
  346. AND amount - ? >= ?
  347. ''', (user_id, ownable_id, sell_ordered_amount(user_id, ownable_id), amount))
  348. if cursor.fetchone():
  349. return True
  350. else:
  351. return False
  352. def news():
  353. connect()
  354. cursor.execute('''
  355. SELECT * FROM
  356. (SELECT *
  357. FROM news
  358. ORDER BY dt DESC
  359. LIMIT 20) n
  360. ORDER BY dt ASC
  361. ''')
  362. return cursor.fetchall()
  363. def ownable_name_exists(name):
  364. connect()
  365. cursor.execute('''
  366. SELECT rowid
  367. FROM ownables
  368. WHERE name = ?
  369. ''', (name,))
  370. if cursor.fetchone():
  371. return True
  372. else:
  373. return False
  374. def new_stock(timeout=60, name=None):
  375. connect()
  376. while name is None:
  377. name = random_chars(6)
  378. if ownable_name_exists(name):
  379. name = None
  380. cursor.execute('''
  381. INSERT INTO ownables(name)
  382. VALUES (?)
  383. ''', (name,))
  384. cursor.execute('''
  385. INSERT INTO news(title)
  386. VALUES (?)
  387. ''', ('A new stock can now be bought: ' + name,))
  388. if random.getrandbits(1):
  389. cursor.execute('''
  390. INSERT INTO news(title)
  391. VALUES (?)
  392. ''', ('Experts expect the price of ' + name + ' to fall',))
  393. else:
  394. cursor.execute('''
  395. INSERT INTO news(title)
  396. VALUES (?)
  397. ''', ('Experts expect the price of ' + name + ' to rise',))
  398. amount = random.randrange(100, 10000)
  399. price = random.randrange(10000, 20000) / amount
  400. ownable_id = ownable_id_by_name(name)
  401. own(bank_id(), name)
  402. bank_order(False,
  403. ownable_id,
  404. price,
  405. amount,
  406. timeout)
  407. return name
  408. def new_stocks(timeout=60, count=1):
  409. return [new_stock(timeout=timeout) for _ in range(count)]
  410. def ownable_id_by_name(ownable_name):
  411. connect()
  412. cursor.execute('''
  413. SELECT rowid
  414. FROM ownables
  415. WHERE name = ?
  416. ''', (ownable_name,))
  417. return cursor.fetchone()[0]
  418. def get_ownership_id(ownable_id, user_id):
  419. connect()
  420. cursor.execute('''
  421. SELECT rowid
  422. FROM ownership
  423. WHERE ownable_id = ?
  424. AND user_id = ?
  425. ''', (ownable_id, user_id,))
  426. return cursor.fetchone()[0]
  427. def currency_id():
  428. connect()
  429. cursor.execute('''
  430. SELECT rowid
  431. FROM ownables
  432. WHERE name = ?
  433. ''', (CURRENCY_NAME,))
  434. return cursor.fetchone()[0]
  435. def user_money(user_id):
  436. connect()
  437. cursor.execute('''
  438. SELECT amount
  439. FROM ownership
  440. WHERE user_id = ?
  441. AND ownable_id = ?
  442. ''', (user_id, currency_id()))
  443. return cursor.fetchone()[0]
  444. def delete_order(order_id):
  445. connect()
  446. cursor.execute('''
  447. DELETE FROM orders
  448. WHERE rowid = ?
  449. ''', (order_id,))
  450. def current_value(ownable_id):
  451. connect()
  452. cursor.execute('''SELECT price
  453. FROM transactions
  454. WHERE ownable_id = ?
  455. ORDER BY dt DESC
  456. LIMIT 1
  457. ''', (ownable_id,))
  458. return cursor.fetchone()[0]
  459. def execute_orders(ownable_id):
  460. connect()
  461. while True:
  462. # find order to execute
  463. cursor.execute('''
  464. SELECT buy_order.*, sell_order.*, buyer.user_id, seller.user_id, buy_order.rowid, sell_order.rowid
  465. FROM orders buy_order, orders sell_order, ownership buyer, ownership seller
  466. WHERE buy_order.buy AND NOT sell_order.buy
  467. AND buyer.rowid = buy_order.ownership_id
  468. AND seller.rowid = sell_order.ownership_id
  469. AND buyer.ownable_id = ?
  470. AND seller.ownable_id = ?
  471. AND (buy_order."limit" IS NULL
  472. OR sell_order."limit" IS NULL
  473. OR (sell_order."limit" <= buy_order."limit"
  474. AND NOT sell_order.stop_loss
  475. AND NOT buy_order.stop_loss))
  476. ORDER BY CASE WHEN sell_order."limit" IS NULL THEN 0 ELSE 1 END ASC,
  477. CASE WHEN buy_order."limit" IS NULL THEN 0 ELSE 1 END ASC,
  478. buy_order."limit" DESC,
  479. sell_order."limit" ASC,
  480. buy_order.ordered_amount - buy_order.executed_amount DESC,
  481. sell_order.ordered_amount - sell_order.executed_amount DESC
  482. LIMIT 1
  483. ''', (ownable_id, ownable_id,))
  484. matching_orders = cursor.fetchone()
  485. # return type: (ownership_id,buy,limit,stop_loss,ordered_amount,executed_amount,expiry_dt,
  486. # ownership_id,buy,limit,stop_loss,ordered_amount,executed_amount,expiry_dt,
  487. # user_id,user_id,rowid,rowid)
  488. if not matching_orders:
  489. break
  490. buy_ownership_id, _, buy_limit, _, buy_order_amount, buy_executed_amount, buy_expiry_dt, \
  491. sell_ownership_id, _, sell_limit, _, sell_order_amount, sell_executed_amount, sell_expiry_dt, \
  492. buyer_id, seller_id, buy_order_id, sell_order_id \
  493. = matching_orders
  494. if buy_limit is None and sell_limit is None:
  495. price = current_value(ownable_id)
  496. elif buy_limit is None:
  497. price = sell_limit
  498. elif sell_limit is None:
  499. price = buy_limit
  500. else: # both not NULL
  501. price = (float(sell_limit) + float(buy_limit)) / 2
  502. if price == 0:
  503. raise AssertionError()
  504. buyer_money = user_money(buyer_id)
  505. amount = min(buy_order_amount - buy_executed_amount,
  506. sell_order_amount - sell_executed_amount,
  507. floor(buyer_money / price))
  508. if amount == 0: # probable because buyer has not enough money
  509. delete_order(buy_order_id)
  510. continue
  511. buy_order_finished = (buy_order_amount - buy_executed_amount - amount <= 0) or (
  512. buyer_money - amount * price < price)
  513. sell_order_finished = (sell_order_amount - sell_executed_amount - amount <= 0)
  514. if price < 0 or amount <= 0:
  515. return AssertionError()
  516. # actually execute the order, but the bank does not send or receive anything
  517. if buyer_id != bank_id(): # buyer pays
  518. cursor.execute('''
  519. UPDATE ownership
  520. SET amount = amount - ?
  521. WHERE user_id = ?
  522. AND ownable_id = ?
  523. ''', (price * amount, buyer_id, currency_id()))
  524. if seller_id != bank_id(): # seller pays
  525. cursor.execute('''
  526. UPDATE ownership
  527. SET amount = amount - ?
  528. WHERE rowid = ?
  529. ''', (amount, sell_ownership_id))
  530. if buyer_id != bank_id(): # buyer receives
  531. cursor.execute('''
  532. UPDATE ownership
  533. SET amount = amount + ?
  534. WHERE rowid = ?
  535. ''', (amount, buy_ownership_id))
  536. if seller_id != bank_id(): # seller receives
  537. cursor.execute('''
  538. UPDATE ownership
  539. SET amount = amount + ?
  540. WHERE user_id = ?
  541. AND ownable_id = ?
  542. ''', (price * amount, seller_id, currency_id()))
  543. # update order execution state
  544. cursor.execute('''
  545. UPDATE orders
  546. SET executed_amount = executed_amount + ?
  547. WHERE rowid = ?
  548. OR rowid = ?
  549. ''', (amount, buy_order_id, sell_order_id))
  550. if buy_order_finished:
  551. delete_order(buy_order_id)
  552. if sell_order_finished:
  553. delete_order(sell_order_id)
  554. if seller_id != buyer_id: # prevent showing self-transactions
  555. cursor.execute('''
  556. INSERT INTO transactions
  557. (price, ownable_id, amount)
  558. VALUES(?, ?, ?)
  559. ''', (price, ownable_id, amount,))
  560. # trigger stop-loss orders
  561. if buyer_id != seller_id:
  562. cursor.execute('''
  563. UPDATE orders
  564. SET stop_loss = NULL,
  565. "limit" = NULL
  566. WHERE stop_loss IS NOT NULL
  567. AND stop_loss
  568. AND ? IN (SELECT ownable_id FROM ownership WHERE rowid = ownership_id)
  569. AND ((buy AND "limit" < ?) OR (NOT buy AND "limit" > ?))
  570. ''', (ownable_id, price, price,))
  571. def ownable_id_by_ownership_id(ownership_id):
  572. connect()
  573. cursor.execute('''
  574. SELECT ownable_id
  575. FROM ownership
  576. WHERE rowid = ?
  577. ''', (ownership_id,))
  578. return cursor.fetchone()[0]
  579. def ownable_name_by_id(ownable_id):
  580. connect()
  581. cursor.execute('''
  582. SELECT name
  583. FROM ownables
  584. WHERE rowid = ?
  585. ''', (ownable_id,))
  586. return cursor.fetchone()[0]
  587. def bank_order(buy, ownable_id, limit, amount, time_until_expiration):
  588. if not limit:
  589. raise AssertionError('The bank does not give away anything.')
  590. place_order(buy,
  591. get_ownership_id(ownable_id, bank_id()),
  592. limit,
  593. False,
  594. amount,
  595. time_until_expiration)
  596. ownable_name = ownable_name_by_id(ownable_id)
  597. cursor.execute('''
  598. INSERT INTO news(title)
  599. VALUES (?)
  600. ''', ('External investors are selling ' + ownable_name + ' atm',))
  601. def current_time(): # might differ from datetime.datetime.now() for time zone reasons
  602. connect()
  603. cursor.execute('''
  604. SELECT datetime('now')
  605. ''')
  606. return cursor.fetchone()[0]
  607. def place_order(buy, ownership_id, limit, stop_loss, amount, time_until_expiration):
  608. connect()
  609. expiry = datetime.strptime(current_time(), '%Y-%m-%d %H:%M:%S') + timedelta(minutes=time_until_expiration)
  610. cursor.execute('''
  611. INSERT INTO orders
  612. (buy, ownership_id, "limit", stop_loss, ordered_amount, expiry_dt)
  613. VALUES (?, ?, ?, ?, ?, ?)
  614. ''', (buy, ownership_id, limit, stop_loss, amount, expiry))
  615. execute_orders(ownable_id_by_ownership_id(ownership_id))
  616. return True
  617. def transactions(ownable_id):
  618. connect()
  619. cursor.execute('''
  620. SELECT dt, amount, price
  621. FROM transactions
  622. WHERE ownable_id = ?
  623. ORDER BY dt DESC
  624. ''', (ownable_id,))
  625. return cursor.fetchall()
  626. def drop_expired_orders():
  627. connect()
  628. cursor.execute('''
  629. DELETE FROM orders
  630. WHERE expiry_dt < DATETIME('now')
  631. ''')
  632. return cursor.fetchall()
  633. def generate_keys(count=1):
  634. # source https://stackoverflow.com/questions/17049308/python-3-3-serial-key-generator-list-problems
  635. for i in range(count):
  636. key = '-'.join(random_chars(5) for _ in range(5))
  637. save_key(key)
  638. print(key)
  639. def user_has_order_with_id(session_id, order_id):
  640. connect()
  641. cursor.execute('''
  642. SELECT orders.rowid
  643. FROM orders, ownership, sessions
  644. WHERE orders.rowid = ?
  645. AND sessions.session_id = ?
  646. AND sessions.user_id = ownership.user_id
  647. AND ownership.rowid = orders.ownership_id
  648. ''', (order_id, session_id,))
  649. if cursor.fetchone():
  650. return True
  651. else:
  652. return False
  653. def leaderboard():
  654. connect()
  655. cursor.execute('''
  656. SELECT *
  657. FROM ( -- one score for each user
  658. SELECT
  659. username,
  660. SUM(CASE -- sum score for each of the users ownables
  661. WHEN ownership.ownable_id = ? THEN ownership.amount
  662. ELSE ownership.amount * (SELECT price
  663. FROM transactions
  664. WHERE ownable_id = ownership.ownable_id
  665. ORDER BY dt DESC
  666. LIMIT 1)
  667. END
  668. ) score
  669. FROM users, ownership
  670. WHERE ownership.user_id = users.rowid
  671. AND users.username != 'bank'
  672. GROUP BY users.rowid
  673. ) AS scores
  674. ORDER BY score DESC
  675. LIMIT 50
  676. ''', (currency_id(),))
  677. return cursor.fetchall()
  678. def user_wealth(user_id):
  679. connect()
  680. cursor.execute('''
  681. SELECT SUM(
  682. CASE -- sum score for each of the users ownables
  683. WHEN ownership.ownable_id = ? THEN ownership.amount
  684. ELSE ownership.amount * (SELECT price
  685. FROM transactions
  686. WHERE ownable_id = ownership.ownable_id
  687. ORDER BY dt DESC
  688. LIMIT 1)
  689. END
  690. ) score
  691. FROM ownership
  692. WHERE ownership.user_id = ?
  693. ''', (currency_id(), user_id,))
  694. return cursor.fetchone()[0]
  695. def change_password(session_id, password):
  696. connect()
  697. cursor.execute('''
  698. UPDATE users
  699. SET password = ?
  700. WHERE ? IN (SELECT session_id FROM sessions WHERE sessions.user_id = users.rowid)
  701. ''', (password, session_id,))
  702. def sign_out_user(session_id):
  703. connect()
  704. cursor.execute('''
  705. DELETE FROM sessions
  706. WHERE user_id = (SELECT user_id FROM sessions s2 WHERE s2.session_id = ?)
  707. ''', (session_id,))
  708. def delete_user(user_id):
  709. connect()
  710. cursor.execute('''
  711. DELETE FROM sessions
  712. WHERE user_id = ?
  713. ''', (user_id,))
  714. cursor.execute('''
  715. DELETE FROM orders
  716. WHERE ownership_id IN (
  717. SELECT rowid FROM ownership WHERE user_id = ?)
  718. ''', (user_id,))
  719. cursor.execute('''
  720. DELETE FROM ownership
  721. WHERE user_id = ?
  722. ''', (user_id,))
  723. cursor.execute('''
  724. DELETE FROM keys
  725. WHERE used_by_user_id = ?
  726. ''', (user_id,))
  727. cursor.execute('''
  728. INSERT INTO news(title)
  729. VALUES ((SELECT username FROM users WHERE rowid = ?) || ' retired.')
  730. ''', (user_id,))
  731. cursor.execute('''
  732. DELETE FROM users
  733. WHERE rowid = ?
  734. ''', (user_id,))
  735. def delete_ownable(ownable_id):
  736. connect()
  737. cursor.execute('''
  738. DELETE FROM transactions
  739. WHERE ownable_id = ?
  740. ''', (ownable_id,))
  741. cursor.execute('''
  742. DELETE FROM orders
  743. WHERE ownership_id IN (
  744. SELECT rowid FROM ownership WHERE ownable_id = ?)
  745. ''', (ownable_id,))
  746. # only delete empty ownerships
  747. cursor.execute('''
  748. DELETE FROM ownership
  749. WHERE ownable_id = ?
  750. AND amount = 0
  751. ''', (ownable_id,))
  752. cursor.execute('''
  753. INSERT INTO news(title)
  754. VALUES ((SELECT name FROM ownables WHERE rowid = ?) || ' can not be traded any more.')
  755. ''', (ownable_id,))
  756. cursor.execute('''
  757. DELETE FROM ownables
  758. WHERE rowid = ?
  759. ''', (ownable_id,))