· 5 years ago · Feb 12, 2021, 11:18 PM
1import os
2
3from cs50 import SQL
4from flask import Flask, flash, jsonify, redirect, render_template, request, session
5from flask_session import Session
6from tempfile import mkdtemp
7from werkzeug.exceptions import default_exceptions, HTTPException, InternalServerError
8from werkzeug.security import check_password_hash, generate_password_hash
9
10from helpers import apology, login_required, lookup, usd
11
12# Configure application
13app = Flask(__name__)
14
15# Ensure templates are auto-reloaded
16app.config["TEMPLATES_AUTO_RELOAD"] = True
17
18# Ensure responses aren't cached
19@app.after_request
20def after_request(response):
21 response.headers["Cache-Control"] = "no-cache, no-store, must-revalidate"
22 response.headers["Expires"] = 0
23 response.headers["Pragma"] = "no-cache"
24 return response
25
26# Custom filter
27app.jinja_env.filters["usd"] = usd
28
29# Configure session to use filesystem (instead of signed cookies)
30app.config["SESSION_FILE_DIR"] = mkdtemp()
31app.config["SESSION_PERMANENT"] = False
32app.config["SESSION_TYPE"] = "filesystem"
33Session(app)
34
35# Configure CS50 Library to use SQLite database
36db = SQL("sqlite:///finance.db")
37
38# Make sure API key is set
39if not os.environ.get("API_KEY"):
40 raise RuntimeError("API_KEY not set")
41
42
43@app.route("/")
44@login_required
45def index():
46 user_id = session["user_id"]
47 rows = db.execute("SELECT stock_ticker, stock_name, number_of_shares FROM users_stock_count, stock_names WHERE users_stock_count.stock_id = stock_names.stock_id AND users_stock_count.user_id = :id", id=user_id)
48 user_money = db.execute("SELECT cash FROM users WHERE id = :id", id=user_id)
49 current_stock_prices = []
50
51 total_money = 0
52 for row in rows:
53 stock_info = lookup(row["stock_ticker"])
54 current_stock_prices.append(stock_info["price"])
55 total_money += stock_info["price"] * row["number_of_shares"]
56
57 return render_template("index.html", rows=rows, current_stock_prices=current_stock_prices, user_money=user_money, total_money=total_money)
58
59@app.route("/buy", methods=["GET", "POST"])
60@login_required
61def buy():
62 """Buy shares of stock"""
63 if request.method == ("POST"):
64 stockSymbol = request.form.get("symbol")
65 shareNumber = request.form.get("shares")
66 user_id = session["user_id"]
67
68 if not lookup(stockSymbol):
69 return apology("Stock does not exist")
70 elif not shareNumber.isnumeric() or not int(shareNumber) >= 1:
71 return apology("Please enter a number greater than 0")
72 else:
73 stockInfo = lookup(stockSymbol)
74 stockPrice = stockInfo["price"]
75 stockName = stockInfo["name"]
76 userMoney = db.execute("SELECT cash FROM users WHERE id = :id", id=user_id)
77
78 if not float(userMoney[0]['cash']) >= float(stockPrice) * int(shareNumber):
79 return apology("not enough cash")
80 else:
81 stock_ticker_rows = db.execute("SELECT stock_ticker FROM stock_names WHERE stock_ticker=:stock_ticker", stock_ticker=stockSymbol)
82
83 if len(stock_ticker_rows) == 0:
84 db.execute("INSERT INTO stock_names (stock_name, stock_ticker) VALUES (:stockName, :stockSymbol)", stockSymbol=stockSymbol, stockName=stockName)
85
86 stock_id_rows = db.execute("SELECT stock_id FROM stock_names WHERE stock_ticker=:stockSymbol", stockSymbol=stockSymbol)
87 stock_id = stock_id_rows[0]['stock_id']
88 number_of_shares = int(shareNumber)
89 price_float = float(stockPrice)
90 total_price = price_float * number_of_shares
91 db.execute("INSERT INTO transactions (user_id, stock_id, purchased_shares, purchase_price, total_price) VALUES (:user_id, :stock_id, :purchased_shares, :purchased_price, :total_price)", user_id=user_id, stock_id=stock_id, purchased_shares=number_of_shares, purchased_price=price_float, total_price=total_price)
92 db.execute("UPDATE users SET cash = cash - :total_price WHERE id = :user_id", total_price=total_price, user_id=user_id)
93
94 stock_count_user_id = db.execute("SELECT user_id FROM users_stock_count WHERE user_id=:user_id", user_id=user_id)
95 stock_count_stock_id = db.execute("SELECT user_id FROM users_stock_count WHERE stock_id=:stock_id", stock_id=stock_id)
96
97 if len(stock_count_user_id) == 0:
98 db.execute("INSERT INTO users_stock_count (user_id, stock_id, number_of_shares) VALUES (:user_id, :stock_id, :number_of_shares)", user_id=user_id, stock_id=stock_id, number_of_shares=number_of_shares)
99 else:
100 for row in stock_count_stock_id:
101 if row["user_id"] == user_id:
102 db.execute("UPDATE users_stock_count SET number_of_shares = number_of_shares + :number_of_shares WHERE stock_id = :stock_id", number_of_shares=number_of_shares, stock_id=stock_id)
103
104 return redirect("/")
105
106 db.execute("INSERT INTO users_stock_count (user_id, stock_id, number_of_shares) VALUES (:user_id, :stock_id, :number_of_shares)", user_id=user_id, stock_id=stock_id, number_of_shares=number_of_shares)
107
108 return redirect("/")
109 else:
110 return render_template("/buy.html")
111
112
113@app.route("/check", methods=["GET"])
114def check():
115 """Return true if username available, else false, in JSON format"""
116 username = request.args.get("username")
117
118 if not len(username) > 0:
119 return apology("Username is too short")
120
121 rows = db.execute("SELECT username FROM users WHERE username = :username", username=username)
122
123 if len(rows) != 0:
124 return jsonify(result="false")
125 else:
126 return jsonify(result="true")
127
128@app.route("/history")
129@login_required
130def history():
131 """Show history of transactions"""
132 user_id=session["user_id"]
133
134 rows = db.execute("SELECT transactions.stock_id, stock_names.stock_name, stock_names.stock_ticker, transactions.purchased_shares, transactions.purchase_price, transactions.date, transactions.time, transactions.shares_sold, transactions.sale_price FROM transactions, stock_names WHERE transactions.stock_id = stock_names.stock_id AND transactions.user_id = :id", id=user_id)
135
136 return render_template("/history.html", rows=rows)
137
138
139@app.route("/login", methods=["GET", "POST"])
140def login():
141 """Log user in"""
142
143 # Forget any user_id
144 session.clear()
145
146 # User reached route via POST (as by submitting a form via POST)
147 if request.method == "POST":
148
149 # Ensure username was submitted
150 if not request.form.get("username"):
151 return apology("must provide username", 403)
152
153 # Ensure password was submitted
154 elif not request.form.get("password"):
155 return apology("must provide password", 403)
156
157 # Query database for username
158 rows = db.execute("SELECT * FROM users WHERE username = :username",
159 username=request.form.get("username"))
160
161 # Ensure username exists and password is correct
162 if len(rows) != 1 or not check_password_hash(rows[0]["hash"], request.form.get("password")):
163 return apology("invalid username and/or password", 403)
164
165 # Remember which user has logged in
166 session["user_id"] = rows[0]["id"]
167
168 # Redirect user to home page
169 return redirect("/")
170
171 # User reached route via GET (as by clicking a link or via redirect)
172 else:
173 return render_template("login.html")
174
175
176@app.route("/logout")
177def logout():
178 """Log user out"""
179
180 # Forget any user_id
181 session.clear()
182
183 # Redirect user to login form
184 return redirect("/")
185
186
187@app.route("/quote", methods=["GET", "POST"])
188@login_required
189def quote():
190 """Get stock quote."""
191 if request.method == "POST":
192 stocks = lookup(request.form.get("symbol"))
193
194 return render_template("quoted.html", stocks=stocks)
195 else:
196 return render_template("quote.html")
197
198
199@app.route("/register", methods=["GET", "POST"])
200def register():
201 """Register user"""
202 if request.method == "POST":
203 username = request.form.get("username")
204 password = request.form.get("password")
205 confirmation = request.form.get("confirmation")
206
207 if not len(username) >= 1 or not len(password) >= 1:
208 return apology("Please enter a username and password")
209
210 rows = db.execute("SELECT username FROM users WHERE username = :username", username=username)
211
212 if len(rows) != 0:
213 return apology("Username already exists")
214
215 if password != confirmation:
216 return apology("The provided passwords do not match")
217
218 hash = generate_password_hash(password, method='pbkdf2:sha256', salt_length=8)
219
220 db.execute("INSERT INTO users (username, hash) VALUES (:username, :hash)", username=username, hash=hash)
221
222 return redirect("/")
223 else:
224 return render_template("register.html")
225
226
227@app.route("/sell", methods=["GET", "POST"])
228@login_required
229def sell():
230 """Sell shares of stock"""
231 if request.method == "POST":
232 stock_symbol = request.form.get("symbol")
233 share_number = request.form.get("shares")
234
235 if not stock_symbol:
236 return apology("Please select a stock")
237 elif not share_number.isnumeric():
238 return apology("Please enter a valid number to sell")
239 elif not int(share_number) > 0:
240 return apology("Please enter a valid number to sell")
241 else:
242 user_id = session["user_id"]
243 rows = db.execute("SELECT users_stock_count.user_id, stock_names.stock_id, stock_names.stock_ticker, users_stock_count.number_of_shares FROM stock_names, users_stock_count WHERE users_stock_count.stock_id = stock_names.stock_id AND users_stock_count.user_id = :id", id=user_id)
244
245 for row in rows:
246 if row["stock_ticker"] == stock_symbol:
247 if not int(row["number_of_shares"]) >= int(share_number):
248 return apology("you dont own that many shares")
249 else:
250 share_number_int = int(share_number)
251
252 stock_id = row["stock_id"]
253
254 stock_info = lookup(stock_symbol)
255 stock_price = stock_info["price"]
256 total_price = stock_price * share_number_int
257
258 db.execute("UPDATE users_stock_count SET number_of_shares = number_of_shares - :share_number WHERE user_id = :user_id AND stock_id = :stock_id", share_number=share_number_int, user_id=user_id, stock_id=stock_id)
259 db.execute("UPDATE users SET cash = cash + :total_price WHERE id = :user_id", total_price=total_price, user_id=user_id)
260 db.execute("INSERT INTO transactions (user_id, stock_id, purchased_shares, purchase_price, total_price, shares_sold, sale_price, total_sale_price) VALUES (:user_id, :stock_id, 0, 0, 0, :shares_sold, :sale_price, :total_sale_price)", user_id=user_id, stock_id=stock_id, shares_sold=share_number_int, sale_price=stock_price, total_sale_price=total_price)
261
262 return redirect("/")
263 else:
264 user_id = session["user_id"]
265 stock_tickers = db.execute("SELECT stock_names.stock_ticker, number_of_shares FROM stock_names, users_stock_count WHERE users_stock_count.stock_id = stock_names.stock_id AND users_stock_count.user_id = :id", id=user_id)
266
267 return render_template("sell.html", stock_tickers=stock_tickers)
268
269
270def errorhandler(e):
271 """Handle error"""
272 if not isinstance(e, HTTPException):
273 e = InternalServerError()
274 return apology(e.name, e.code)
275
276
277# Listen for errors
278for code in default_exceptions:
279 app.errorhandler(code)(errorhandler)