-
Notifications
You must be signed in to change notification settings - Fork 411
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
025ea8c
commit b34cbd3
Showing
10 changed files
with
366 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
https://docs.cs50.net/2018/x/psets/7/pset7.html |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
https://docs.cs50.net/2018/x/psets/7/finance/finance.html |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,139 @@ | ||
import os | ||
|
||
from cs50 import SQL | ||
from flask import Flask, flash, redirect, render_template, request, session | ||
from flask_session import Session | ||
from tempfile import mkdtemp | ||
from werkzeug.exceptions import default_exceptions | ||
from werkzeug.security import check_password_hash, generate_password_hash | ||
|
||
from helpers import apology, login_required, lookup, usd | ||
|
||
# Ensure environment variable is set | ||
if not os.environ.get("API_KEY"): | ||
raise RuntimeError("API_KEY not set") | ||
|
||
# Configure application | ||
app = Flask(__name__) | ||
|
||
# Ensure templates are auto-reloaded | ||
app.config["TEMPLATES_AUTO_RELOAD"] = True | ||
|
||
# Ensure responses aren't cached | ||
@app.after_request | ||
def after_request(response): | ||
response.headers["Cache-Control"] = "no-cache, no-store, must-revalidate" | ||
response.headers["Expires"] = 0 | ||
response.headers["Pragma"] = "no-cache" | ||
return response | ||
|
||
# Custom filter | ||
app.jinja_env.filters["usd"] = usd | ||
|
||
# Configure session to use filesystem (instead of signed cookies) | ||
app.config["SESSION_FILE_DIR"] = mkdtemp() | ||
app.config["SESSION_PERMANENT"] = False | ||
app.config["SESSION_TYPE"] = "filesystem" | ||
Session(app) | ||
|
||
# Configure CS50 Library to use SQLite database | ||
db = SQL("sqlite:///finance.db") | ||
|
||
|
||
@app.route("/") | ||
@login_required | ||
def index(): | ||
"""Show portfolio of stocks""" | ||
return apology("TODO") | ||
|
||
|
||
@app.route("/buy", methods=["GET", "POST"]) | ||
@login_required | ||
def buy(): | ||
"""Buy shares of stock""" | ||
return apology("TODO") | ||
|
||
|
||
@app.route("/history") | ||
@login_required | ||
def history(): | ||
"""Show history of transactions""" | ||
return apology("TODO") | ||
|
||
|
||
@app.route("/login", methods=["GET", "POST"]) | ||
def login(): | ||
"""Log user in""" | ||
|
||
# Forget any user_id | ||
session.clear() | ||
|
||
# User reached route via POST (as by submitting a form via POST) | ||
if request.method == "POST": | ||
|
||
# Ensure username was submitted | ||
if not request.form.get("username"): | ||
return apology("must provide username", 403) | ||
|
||
# Ensure password was submitted | ||
elif not request.form.get("password"): | ||
return apology("must provide password", 403) | ||
|
||
# Query database for username | ||
rows = db.execute("SELECT * FROM users WHERE username = :username", | ||
username=request.form.get("username")) | ||
|
||
# Ensure username exists and password is correct | ||
if len(rows) != 1 or not check_password_hash(rows[0]["hash"], request.form.get("password")): | ||
return apology("invalid username and/or password", 403) | ||
|
||
# Remember which user has logged in | ||
session["user_id"] = rows[0]["id"] | ||
|
||
# Redirect user to home page | ||
return redirect("/") | ||
|
||
# User reached route via GET (as by clicking a link or via redirect) | ||
else: | ||
return render_template("login.html") | ||
|
||
|
||
@app.route("/logout") | ||
def logout(): | ||
"""Log user out""" | ||
|
||
# Forget any user_id | ||
session.clear() | ||
|
||
# Redirect user to login form | ||
return redirect("/") | ||
|
||
|
||
@app.route("/quote", methods=["GET", "POST"]) | ||
@login_required | ||
def quote(): | ||
"""Get stock quote.""" | ||
return apology("TODO") | ||
|
||
|
||
@app.route("/register", methods=["GET", "POST"]) | ||
def register(): | ||
"""Register user""" | ||
return apology("TODO") | ||
|
||
|
||
@app.route("/sell", methods=["GET", "POST"]) | ||
@login_required | ||
def sell(): | ||
"""Sell shares of stock""" | ||
return apology("TODO") | ||
|
||
|
||
def errorhandler(e): | ||
"""Handle error""" | ||
return apology(e.name, e.code) | ||
|
||
|
||
# listen for errors | ||
for code in default_exceptions: | ||
app.errorhandler(code)(errorhandler) |
Binary file not shown.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,84 @@ | ||
import csv | ||
import os | ||
import urllib.request | ||
|
||
from flask import redirect, render_template, request, session | ||
from functools import wraps | ||
|
||
|
||
def apology(message, code=400): | ||
"""Render message as an apology to user.""" | ||
def escape(s): | ||
""" | ||
Escape special characters. | ||
https://github.com/jacebrowning/memegen#special-characters | ||
""" | ||
for old, new in [("-", "--"), (" ", "-"), ("_", "__"), ("?", "~q"), | ||
("%", "~p"), ("#", "~h"), ("/", "~s"), ("\"", "''")]: | ||
s = s.replace(old, new) | ||
return s | ||
return render_template("apology.html", top=code, bottom=escape(message)), code | ||
|
||
|
||
def login_required(f): | ||
""" | ||
Decorate routes to require login. | ||
http://flask.pocoo.org/docs/0.12/patterns/viewdecorators/ | ||
""" | ||
@wraps(f) | ||
def decorated_function(*args, **kwargs): | ||
if session.get("user_id") is None: | ||
return redirect("/login") | ||
return f(*args, **kwargs) | ||
return decorated_function | ||
|
||
|
||
def lookup(symbol): | ||
"""Look up quote for symbol.""" | ||
|
||
# Reject symbol if it starts with caret | ||
if symbol.startswith("^"): | ||
return None | ||
|
||
# Reject symbol if it contains comma | ||
if "," in symbol: | ||
return None | ||
|
||
# Query Alpha Vantage for quote | ||
# https://www.alphavantage.co/documentation/ | ||
try: | ||
|
||
# GET CSV | ||
url = f"https://www.alphavantage.co/query?apikey={os.getenv('API_KEY')}&datatype=csv&function=TIME_SERIES_INTRADAY&interval=1min&symbol={symbol}" | ||
webpage = urllib.request.urlopen(url) | ||
|
||
# Parse CSV | ||
datareader = csv.reader(webpage.read().decode("utf-8").splitlines()) | ||
|
||
# Ignore first row | ||
next(datareader) | ||
|
||
# Parse second row | ||
row = next(datareader) | ||
|
||
# Ensure stock exists | ||
try: | ||
price = float(row[4]) | ||
except: | ||
return None | ||
|
||
# Return stock's name (as a str), price (as a float), and (uppercased) symbol (as a str) | ||
return { | ||
"price": price, | ||
"symbol": symbol.upper() | ||
} | ||
|
||
except: | ||
return None | ||
|
||
|
||
def usd(value): | ||
"""Format value as USD.""" | ||
return f"${value:,.2f}" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
cs50 | ||
Flask | ||
Flask-Session |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,47 @@ | ||
nav .navbar-brand | ||
{ | ||
/* size for brand */ | ||
font-size: xx-large; | ||
} | ||
|
||
/* colors for brand */ | ||
nav .navbar-brand .blue | ||
{ | ||
color: #537fbe; | ||
} | ||
nav .navbar-brand .red | ||
{ | ||
color: #ea433b; | ||
} | ||
nav .navbar-brand .yellow | ||
{ | ||
color: #f5b82e; | ||
} | ||
nav .navbar-brand .green | ||
{ | ||
color: #2e944b; | ||
} | ||
|
||
main .form-control | ||
{ | ||
/* center form controls */ | ||
display: inline-block; | ||
|
||
/* override Bootstrap's 100% width for form controls */ | ||
width: auto; | ||
} | ||
|
||
main | ||
{ | ||
/* scroll horizontally as needed */ | ||
overflow-x: auto; | ||
|
||
/* center contents */ | ||
text-align: center; | ||
} | ||
|
||
main img | ||
{ | ||
/* constrain images on small screens */ | ||
max-width: 100%; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
{% extends "layout.html" %} | ||
|
||
{% block title %} | ||
Apology | ||
{% endblock %} | ||
|
||
{% block main %} | ||
<img alt="{{ top }}" class="border" src="http://memegen.link/custom/{{ top | urlencode }}/{{ bottom | urlencode }}.jpg?alt=https://i.imgur.com/CsCgN7Ll.png"/> | ||
{% endblock %} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,65 @@ | ||
<!DOCTYPE html> | ||
|
||
<html lang="en"> | ||
|
||
<head> | ||
|
||
<!-- Required meta tags --> | ||
<meta charset="utf-8"/> | ||
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no"/> | ||
|
||
<!-- documentation at http://getbootstrap.com/docs/4.0/, alternative themes at https://bootswatch.com/4-alpha/ --> | ||
<link href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0-beta.2/css/bootstrap.min.css" rel="stylesheet"/> | ||
|
||
<link href="/static/styles.css" rel="stylesheet"/> | ||
|
||
<script src="https://code.jquery.com/jquery-3.1.1.min.js"></script> | ||
<script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.12.3/umd/popper.min.js"></script> | ||
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0-beta.2/js/bootstrap.min.js"></script> | ||
|
||
<title>C$50 Finance: {% block title %}{% endblock %}</title> | ||
|
||
</head> | ||
|
||
<body> | ||
|
||
<nav class="navbar navbar-expand-md navbar-light bg-light border"> | ||
<a class="navbar-brand" href="/"><span class="blue">C</span><span class="red">$</span><span class="yellow">5</span><span class="green">0</span> <span class="red">Finance</span></a> | ||
<button aria-controls="navbar" aria-expanded="false" aria-label="Toggle navigation" class="navbar-toggler" data-target="#navbar" data-toggle="collapse" type="button"> | ||
<span class="navbar-toggler-icon"></span> | ||
</button> | ||
<div class="collapse navbar-collapse" id="navbar"> | ||
{% if session.user_id %} | ||
<ul class="navbar-nav mr-auto mt-2"> | ||
<li class="nav-item"><a class="nav-link" href="/quote">Quote</a></li> | ||
<li class="nav-item"><a class="nav-link" href="/buy">Buy</a></li> | ||
<li class="nav-item"><a class="nav-link" href="/sell">Sell</a></li> | ||
<li class="nav-item"><a class="nav-link" href="/history">History</a></li> | ||
</ul> | ||
<ul class="navbar-nav ml-auto mt-2"> | ||
<li class="nav-item"><a class="nav-link" href="/logout">Log Out</a></li> | ||
</ul> | ||
{% else %} | ||
<ul class="navbar-nav ml-auto mt-2"> | ||
<li class="nav-item"><a class="nav-link" href="/register">Register</a></li> | ||
<li class="nav-item"><a class="nav-link" href="/login">Log In</a></li> | ||
</ul> | ||
{% endif %} | ||
</div> | ||
</nav> | ||
|
||
{% if get_flashed_messages() %} | ||
<header> | ||
<div class="alert alert-primary border text-center" role="alert"> | ||
{{ get_flashed_messages() | join(" ") }} | ||
</div> | ||
</header> | ||
{% endif %} | ||
|
||
<main class="container p-5"> | ||
{% block main %}{% endblock %} | ||
</main> | ||
|
||
</body> | ||
|
||
</html> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
{% extends "layout.html" %} | ||
|
||
{% block title %} | ||
Log In | ||
{% endblock %} | ||
|
||
{% block main %} | ||
<form action="/login" method="post"> | ||
<div class="form-group"> | ||
<input autocomplete="off" autofocus class="form-control" name="username" placeholder="Username" type="text"/> | ||
</div> | ||
<div class="form-group"> | ||
<input class="form-control" name="password" placeholder="Password" type="password"/> | ||
</div> | ||
<button class="btn btn-primary" type="submit">Log In</button> | ||
</form> | ||
{% endblock %} |