1
1

model.py 25 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944
  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
  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 value
  220. FROM ownership, ownables
  221. WHERE user_id = ?
  222. AND ownership.amount > 0
  223. AND ownership.ownable_id = ownables.rowid
  224. ''', (currency_id(), user_id,))
  225. return cursor.fetchall()
  226. def activate_key(key, user_id):
  227. connect()
  228. cursor.execute('''
  229. UPDATE keys
  230. SET used_by_user_id = ?
  231. WHERE used_by_user_id IS NULL
  232. AND key = ?
  233. ''', (user_id, key,))
  234. send_ownable(bank_id(), user_id, CURRENCY_NAME, 1000)
  235. def bank_id():
  236. connect()
  237. cursor.execute('''
  238. SELECT users.rowid
  239. FROM users
  240. WHERE username = 'bank'
  241. ''')
  242. return cursor.fetchone()[0]
  243. def valid_session_id(session_id):
  244. connect()
  245. cursor.execute('''
  246. SELECT rowid
  247. FROM sessions
  248. WHERE session_id = ?
  249. ''', (session_id,))
  250. if cursor.fetchone():
  251. return True
  252. else:
  253. return False
  254. def get_user_orders(user_id):
  255. connect()
  256. cursor.execute('''
  257. SELECT
  258. CASE
  259. WHEN orders.buy THEN 'Buy'
  260. ELSE 'Sell'
  261. END,
  262. ownables.name,
  263. orders.ordered_amount - orders.executed_amount,
  264. orders."limit",
  265. CASE
  266. WHEN orders."limit" IS NULL THEN NULL
  267. WHEN orders.stop_loss THEN 'Yes'
  268. ELSE 'No'
  269. END,
  270. orders.ordered_amount,
  271. datetime(orders.expiry_dt),
  272. orders.rowid
  273. FROM orders, ownables, ownership
  274. WHERE ownership.user_id = ?
  275. AND ownership.ownable_id = ownables.rowid
  276. AND orders.ownership_id = ownership.rowid
  277. ORDER BY ownables.name ASC, orders.stop_loss ASC, orders.buy DESC, orders."limit" ASC
  278. ''', (user_id,))
  279. return cursor.fetchall()
  280. def get_ownable_orders(ownable_id):
  281. connect()
  282. cursor.execute('''
  283. SELECT
  284. CASE
  285. WHEN orders.buy THEN 'Buy'
  286. ELSE 'Sell'
  287. END,
  288. ownables.name,
  289. orders.ordered_amount - orders.executed_amount,
  290. orders."limit",
  291. CASE
  292. WHEN orders."limit" IS NULL THEN NULL
  293. WHEN orders.stop_loss THEN 'Yes'
  294. ELSE 'No'
  295. END,
  296. datetime(orders.expiry_dt),
  297. orders.rowid
  298. FROM orders, ownables, ownership
  299. WHERE ownership.ownable_id = ?
  300. AND ownership.ownable_id = ownables.rowid
  301. AND orders.ownership_id = ownership.rowid
  302. ORDER BY ownables.name ASC, orders.stop_loss ASC, orders.buy DESC, orders."limit" ASC
  303. ''', (ownable_id,))
  304. return cursor.fetchall()
  305. def sell_ordered_amount(user_id, ownable_id):
  306. connect()
  307. # if ownable_id == currency_id():
  308. # return 0
  309. cursor.execute('''
  310. SELECT COALESCE(SUM(orders.ordered_amount - orders.executed_amount),0)
  311. FROM orders, ownership
  312. WHERE ownership.rowid = orders.ownership_id
  313. AND ownership.user_id = ?
  314. AND ownership.ownable_id = ?
  315. AND NOT orders.buy
  316. ''', (user_id, ownable_id))
  317. return cursor.fetchone()[0]
  318. def user_owns_at_least(amount, user_id, ownable_id):
  319. connect()
  320. if not isinstance(amount, float) and not isinstance(amount, int):
  321. # comparison of float with strings does not work so well in sql
  322. raise AssertionError()
  323. cursor.execute('''
  324. SELECT rowid
  325. FROM ownership
  326. WHERE user_id = ?
  327. AND ownable_id = ?
  328. AND amount - ? >= ?
  329. ''', (user_id, ownable_id, sell_ordered_amount(user_id, ownable_id), amount))
  330. if cursor.fetchone():
  331. return True
  332. else:
  333. return False
  334. def news():
  335. connect()
  336. cursor.execute('''
  337. SELECT * FROM
  338. (SELECT *
  339. FROM news
  340. ORDER BY dt DESC
  341. LIMIT 20) n
  342. ORDER BY dt ASC
  343. ''')
  344. return cursor.fetchall()
  345. def ownable_name_exists(name):
  346. connect()
  347. cursor.execute('''
  348. SELECT rowid
  349. FROM ownables
  350. WHERE name = ?
  351. ''', (name,))
  352. if cursor.fetchone():
  353. return True
  354. else:
  355. return False
  356. def new_stock(timeout=60, name=None):
  357. connect()
  358. while name is None:
  359. name = random_chars(6)
  360. if ownable_name_exists(name):
  361. name = None
  362. cursor.execute('''
  363. INSERT INTO ownables(name)
  364. VALUES (?)
  365. ''', (name,))
  366. cursor.execute('''
  367. INSERT INTO news(title)
  368. VALUES (?)
  369. ''', ('A new stock can now be bought: ' + name,))
  370. if random.getrandbits(1):
  371. cursor.execute('''
  372. INSERT INTO news(title)
  373. VALUES (?)
  374. ''', ('Experts expect the price of ' + name + ' to fall',))
  375. else:
  376. cursor.execute('''
  377. INSERT INTO news(title)
  378. VALUES (?)
  379. ''', ('Experts expect the price of ' + name + ' to rise',))
  380. amount = random.randrange(100, 10000)
  381. price = random.randrange(10000, 20000) / amount
  382. ownable_id = ownable_id_by_name(name)
  383. own(bank_id(), name)
  384. bank_order(False,
  385. ownable_id,
  386. price,
  387. amount,
  388. timeout)
  389. return name
  390. def new_stocks(timeout=60, count=1):
  391. return [new_stock(timeout=timeout) for _ in range(count)]
  392. def ownable_id_by_name(ownable_name):
  393. connect()
  394. cursor.execute('''
  395. SELECT rowid
  396. FROM ownables
  397. WHERE name = ?
  398. ''', (ownable_name,))
  399. return cursor.fetchone()[0]
  400. def get_ownership_id(ownable_id, user_id):
  401. connect()
  402. cursor.execute('''
  403. SELECT rowid
  404. FROM ownership
  405. WHERE ownable_id = ?
  406. AND user_id = ?
  407. ''', (ownable_id, user_id,))
  408. return cursor.fetchone()[0]
  409. def currency_id():
  410. connect()
  411. cursor.execute('''
  412. SELECT rowid
  413. FROM ownables
  414. WHERE name = ?
  415. ''', (CURRENCY_NAME,))
  416. return cursor.fetchone()[0]
  417. def user_money(user_id):
  418. connect()
  419. cursor.execute('''
  420. SELECT amount
  421. FROM ownership
  422. WHERE user_id = ?
  423. AND ownable_id = ?
  424. ''', (user_id, currency_id()))
  425. return cursor.fetchone()[0]
  426. def delete_order(order_id):
  427. connect()
  428. cursor.execute('''
  429. DELETE FROM orders
  430. WHERE rowid = ?
  431. ''', (order_id,))
  432. def execute_orders(ownable_id):
  433. connect()
  434. while True:
  435. # find order to execute
  436. cursor.execute('''
  437. SELECT buy_order.*, sell_order.*, buyer.user_id, seller.user_id, buy_order.rowid, sell_order.rowid
  438. FROM orders buy_order, orders sell_order, ownership buyer, ownership seller
  439. WHERE buy_order.buy AND NOT sell_order.buy
  440. AND buyer.rowid = buy_order.ownership_id
  441. AND seller.rowid = sell_order.ownership_id
  442. AND buyer.ownable_id = ?
  443. AND seller.ownable_id = ?
  444. AND (buy_order."limit" IS NULL
  445. OR sell_order."limit" IS NULL
  446. OR (sell_order."limit" <= buy_order."limit"
  447. AND NOT sell_order.stop_loss
  448. AND NOT buy_order.stop_loss))
  449. ORDER BY CASE WHEN sell_order."limit" IS NULL THEN 0 ELSE 1 END ASC,
  450. CASE WHEN buy_order."limit" IS NULL THEN 0 ELSE 1 END ASC,
  451. buy_order."limit" DESC,
  452. sell_order."limit" ASC,
  453. buy_order.ordered_amount - buy_order.executed_amount DESC,
  454. sell_order.ordered_amount - sell_order.executed_amount DESC
  455. LIMIT 1
  456. ''', (ownable_id, ownable_id,))
  457. matching_orders = cursor.fetchone()
  458. # return type: (ownership_id,buy,limit,stop_loss,ordered_amount,executed_amount,expiry_dt,
  459. # ownership_id,buy,limit,stop_loss,ordered_amount,executed_amount,expiry_dt,
  460. # user_id,user_id,rowid,rowid)
  461. if not matching_orders:
  462. break
  463. buy_ownership_id, _, buy_limit, _, buy_order_amount, buy_executed_amount, buy_expiry_dt, \
  464. sell_ownership_id, _, sell_limit, _, sell_order_amount, sell_executed_amount, sell_expiry_dt, \
  465. buyer_id, seller_id, buy_order_id, sell_order_id \
  466. = matching_orders
  467. if buy_limit is None and sell_limit is None:
  468. raise AssertionError() # TODO use last transaction price
  469. elif buy_limit is None:
  470. price = sell_limit
  471. elif sell_limit is None:
  472. price = buy_limit
  473. else: # both not NULL
  474. price = (float(sell_limit) + float(buy_limit)) / 2
  475. if price == 0:
  476. raise AssertionError()
  477. buyer_money = user_money(buyer_id)
  478. amount = min(buy_order_amount - buy_executed_amount,
  479. sell_order_amount - sell_executed_amount,
  480. floor(buyer_money / price))
  481. if amount == 0: # probable because buyer has not enough money
  482. delete_order(buy_order_id)
  483. continue
  484. buy_order_finished = (buy_order_amount - buy_executed_amount - amount <= 0) or (
  485. buyer_money - amount * price < price)
  486. sell_order_finished = (sell_order_amount - sell_executed_amount - amount <= 0)
  487. if price < 0 or amount <= 0:
  488. return AssertionError()
  489. # actually execute the order, but the bank does not send or receive anything
  490. if buyer_id != bank_id(): # buyer pays
  491. cursor.execute('''
  492. UPDATE ownership
  493. SET amount = amount - ?
  494. WHERE user_id = ?
  495. AND ownable_id = ?
  496. ''', (price * amount, buyer_id, currency_id()))
  497. if seller_id != bank_id(): # seller pays
  498. cursor.execute('''
  499. UPDATE ownership
  500. SET amount = amount - ?
  501. WHERE rowid = ?
  502. ''', (amount, sell_ownership_id))
  503. if buyer_id != bank_id(): # buyer receives
  504. cursor.execute('''
  505. UPDATE ownership
  506. SET amount = amount + ?
  507. WHERE rowid = ?
  508. ''', (amount, buy_ownership_id))
  509. if seller_id != bank_id(): # seller receives
  510. cursor.execute('''
  511. UPDATE ownership
  512. SET amount = amount + ?
  513. WHERE user_id = ?
  514. AND ownable_id = ?
  515. ''', (price * amount, seller_id, currency_id()))
  516. # update order execution state
  517. cursor.execute('''
  518. UPDATE orders
  519. SET executed_amount = executed_amount + ?
  520. WHERE rowid = ?
  521. OR rowid = ?
  522. ''', (amount, buy_order_id, sell_order_id))
  523. if buy_order_finished:
  524. delete_order(buy_order_id)
  525. if sell_order_finished:
  526. delete_order(sell_order_id)
  527. if seller_id != buyer_id: # prevent showing self-transactions
  528. cursor.execute('''
  529. INSERT INTO transactions
  530. (price, ownable_id, amount)
  531. VALUES(?, ?, ?)
  532. ''', (price, ownable_id, amount,))
  533. # trigger stop loss orders
  534. if buyer_id != seller_id:
  535. cursor.execute('''
  536. UPDATE orders
  537. SET stop_loss = NULL,
  538. "limit" = NULL
  539. WHERE stop_loss IS NOT NULL
  540. AND stop_loss
  541. AND ? IN (SELECT ownable_id FROM ownership WHERE rowid = ownership_id)
  542. AND ((buy AND "limit" < ?) OR (NOT buy AND "limit" > ?))
  543. ''', (ownable_id, price, price,))
  544. def ownable_id_by_ownership_id(ownership_id):
  545. connect()
  546. cursor.execute('''
  547. SELECT ownable_id
  548. FROM ownership
  549. WHERE rowid = ?
  550. ''', (ownership_id,))
  551. return cursor.fetchone()[0]
  552. def ownable_name_by_id(ownable_id):
  553. connect()
  554. cursor.execute('''
  555. SELECT name
  556. FROM ownables
  557. WHERE rowid = ?
  558. ''', (ownable_id,))
  559. return cursor.fetchone()[0]
  560. def bank_order(buy, ownable_id, limit, amount, time_until_expiration):
  561. if not limit:
  562. raise AssertionError('The bank does not give away anything.')
  563. place_order(buy,
  564. get_ownership_id(ownable_id, bank_id()),
  565. limit,
  566. False,
  567. amount,
  568. time_until_expiration)
  569. ownable_name = ownable_name_by_id(ownable_id)
  570. cursor.execute('''
  571. INSERT INTO news(title)
  572. VALUES (?)
  573. ''', ('External investors are selling ' + ownable_name + ' atm',))
  574. def current_time(): # might differ from datetime.datetime.now() for time zone reasons
  575. connect()
  576. cursor.execute('''
  577. SELECT datetime('now')
  578. ''')
  579. return cursor.fetchone()[0]
  580. def place_order(buy, ownership_id, limit, stop_loss, amount, time_until_expiration):
  581. connect()
  582. expiry = datetime.strptime(current_time(), '%Y-%m-%d %H:%M:%S') + timedelta(minutes=time_until_expiration)
  583. cursor.execute('''
  584. INSERT INTO orders
  585. (buy, ownership_id, "limit", stop_loss, ordered_amount, expiry_dt)
  586. VALUES (?, ?, ?, ?, ?, ?)
  587. ''', (buy, ownership_id, limit, stop_loss, amount, expiry))
  588. execute_orders(ownable_id_by_ownership_id(ownership_id))
  589. return True
  590. def transactions(ownable_id):
  591. connect()
  592. cursor.execute('''
  593. SELECT dt, amount, price
  594. FROM transactions
  595. WHERE ownable_id = ?
  596. ORDER BY dt DESC
  597. ''', (ownable_id,))
  598. return cursor.fetchall()
  599. def drop_expired_orders():
  600. connect()
  601. cursor.execute('''
  602. DELETE FROM orders
  603. WHERE expiry_dt < DATETIME('now')
  604. ''')
  605. return cursor.fetchall()
  606. def generate_keys(count=1):
  607. # source https://stackoverflow.com/questions/17049308/python-3-3-serial-key-generator-list-problems
  608. for i in range(count):
  609. key = '-'.join(random_chars(5) for _ in range(5))
  610. save_key(key)
  611. print(key)
  612. def user_has_order_with_id(session_id, order_id):
  613. connect()
  614. cursor.execute('''
  615. SELECT orders.rowid
  616. FROM orders, ownership, sessions
  617. WHERE orders.rowid = ?
  618. AND sessions.session_id = ?
  619. AND sessions.user_id = ownership.user_id
  620. AND ownership.rowid = orders.ownership_id
  621. ''', (order_id, session_id,))
  622. if cursor.fetchone():
  623. return True
  624. else:
  625. return False
  626. def leaderboard():
  627. connect()
  628. cursor.execute('''
  629. SELECT *
  630. FROM ( -- one score for each user
  631. SELECT
  632. username,
  633. SUM(CASE -- sum score for each of the users ownables
  634. WHEN ownership.ownable_id = ? THEN ownership.amount
  635. ELSE ownership.amount * (SELECT price
  636. FROM transactions
  637. WHERE ownable_id = ownership.ownable_id
  638. ORDER BY dt DESC
  639. LIMIT 1)
  640. END
  641. ) score
  642. FROM users, ownership
  643. WHERE ownership.user_id = users.rowid
  644. AND users.username != 'bank'
  645. GROUP BY users.rowid
  646. ) AS scores
  647. ORDER BY score DESC
  648. LIMIT 50
  649. ''', (currency_id(),))
  650. return cursor.fetchall()
  651. def user_wealth(user_id):
  652. connect()
  653. cursor.execute('''
  654. SELECT SUM(
  655. CASE -- sum score for each of the users ownables
  656. WHEN ownership.ownable_id = ? THEN ownership.amount
  657. ELSE ownership.amount * (SELECT price
  658. FROM transactions
  659. WHERE ownable_id = ownership.ownable_id
  660. ORDER BY dt DESC
  661. LIMIT 1)
  662. END
  663. ) score
  664. FROM ownership
  665. WHERE ownership.user_id = ?
  666. ''', (currency_id(), user_id,))
  667. return cursor.fetchone()[0]
  668. def change_password(session_id, password):
  669. connect()
  670. cursor.execute('''
  671. UPDATE users
  672. SET password = ?
  673. WHERE ? IN (SELECT session_id FROM sessions WHERE sessions.user_id = users.rowid)
  674. ''', (password, session_id,))
  675. def sign_out_user(session_id):
  676. connect()
  677. cursor.execute('''
  678. DELETE FROM sessions
  679. WHERE user_id = (SELECT user_id FROM sessions s2 WHERE s2.session_id = ?)
  680. ''', (session_id,))
  681. def delete_user(user_id):
  682. connect()
  683. cursor.execute('''
  684. DELETE FROM sessions
  685. WHERE user_id = ?
  686. ''', (user_id,))
  687. cursor.execute('''
  688. DELETE FROM orders
  689. WHERE ownership_id IN (
  690. SELECT rowid FROM ownership WHERE user_id = ?)
  691. ''', (user_id,))
  692. cursor.execute('''
  693. DELETE FROM ownership
  694. WHERE user_id = ?
  695. ''', (user_id,))
  696. cursor.execute('''
  697. DELETE FROM keys
  698. WHERE used_by_user_id = ?
  699. ''', (user_id,))
  700. cursor.execute('''
  701. INSERT INTO news(title)
  702. VALUES ((SELECT username FROM users WHERE rowid = ?) || ' retired.')
  703. ''', (user_id,))
  704. cursor.execute('''
  705. DELETE FROM users
  706. WHERE rowid = ?
  707. ''', (user_id,))
  708. def delete_ownable(ownable_id):
  709. connect()
  710. cursor.execute('''
  711. DELETE FROM transactions
  712. WHERE ownable_id = ?
  713. ''', (ownable_id,))
  714. cursor.execute('''
  715. DELETE FROM orders
  716. WHERE ownership_id IN (
  717. SELECT rowid FROM ownership WHERE ownable_id = ?)
  718. ''', (ownable_id,))
  719. # only delete empty ownerships
  720. cursor.execute('''
  721. DELETE FROM ownership
  722. WHERE ownable_id = ?
  723. AND amount = 0
  724. ''', (ownable_id,))
  725. cursor.execute('''
  726. INSERT INTO news(title)
  727. VALUES ((SELECT name FROM ownables WHERE rowid = ?) || ' can not be traded any more.')
  728. ''', (ownable_id,))
  729. cursor.execute('''
  730. DELETE FROM ownables
  731. WHERE rowid = ?
  732. ''', (ownable_id,))