Skip to content

Commit

Permalink
upload v4
Browse files Browse the repository at this point in the history
  • Loading branch information
PaulNGilson committed Nov 20, 2024
1 parent 4ad2f6b commit 91d2ee9
Show file tree
Hide file tree
Showing 22 changed files with 437 additions and 149 deletions.
133 changes: 119 additions & 14 deletions app.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,13 +32,16 @@ def password_valid(self, connection, username, password_attempt):
if not self.signed_up(connection, username):
return False
rows = connection.execute('SELECT * FROM users WHERE username = %s', [username])
return check_password_hash(rows[0]["hashed_password"], password_attempt) != username
return check_password_hash(rows[0]["hashed_password"], password_attempt)
def get_user_database_id(self, connection, username):
rows = connection.execute('SELECT * FROM users WHERE username = %s', [username])
return rows[0]["id"]
def get_username(self, connection, id):
rows = connection.execute('SELECT * FROM users WHERE id = %s', [id])
return rows[0]["username"]
def sign_up(self, connection, username, password):
hashed_password = generate_password_hash(password)
connection.execute('INSERT INTO users (username, hashed_password) VALUES (%s, %s)', [username, hashed_password])

@login_manager.user_loader
def user_loader(username: str):
Expand Down Expand Up @@ -68,6 +71,10 @@ def page_not_found(e):



@app.route('/', methods=['GET'])
def get_root():
return redirect(url_for('get_home'))

@app.route('/home', methods=['GET'])
def get_home():
logged_in_as = ", " + str(current_user.id) if current_user.__dict__.get("id") else None
Expand All @@ -82,10 +89,11 @@ def get_about():
def get_gigs():
connection = get_flask_database_connection(app)
repo = GigRepository(connection)
locations = ["All"]
venue_locations = [("", "All")]
for gig in repo.all():
if gig.location not in locations:
locations.append(gig.location)
if (gig.venue, gig.location) not in venue_locations:
venue_locations.append((gig.venue, gig.location))
locations = [vl[1] for vl in venue_locations]
selected_location = "All"
if "location" in request.form.keys():
selected_location = request.form["location"]
Expand Down Expand Up @@ -124,12 +132,26 @@ def get_band_by_name(band_name):
@app.route('/book_gig/<gig_id>', methods=["POST"])
@login_required
def post_book_gig(gig_id):
try:
int(request.form["ticket_count"])
except:
return render_template('400.html', reason="Non-integer ticket number requested"), 400
if int(request.form["ticket_count"]) < 1:
return render_template('400.html', reason="Ticket number must be at least 1"), 400
connection = get_flask_database_connection(app)
repo = BookingRepository(connection)
user_database_id = User().get_user_database_id(connection, current_user.id)
repo.make_booking(gig_id, user_database_id, request.form["ticket_count"])
return redirect(url_for('get_account'))

@app.route('/cancel_booking/<booking_id>', methods=["GET"])
@login_required
def get_cancel_booking(booking_id):
connection = get_flask_database_connection(app)
repo = BookingRepository(connection)
repo.cancel_booking(booking_id)
return redirect(url_for('get_account'))

@app.route('/login', methods=["POST"])
def post_login():
username = request.form["username"]
Expand All @@ -143,19 +165,50 @@ def post_login():
login_user(user_model)
return redirect(url_for('get_home'))
else:
return "Wrong credentials"
return "Unknown user"
return render_template('401.html', reason="Wrong credentials"), 401
return render_template('401.html', reason="Unknown user"), 401

@app.route('/login', methods=['GET'])
def get_login():
signup_message = None
if "signup_message" in request.args.keys():
signup_message = request.args["signup_message"]
logged_in_as = ", " + str(current_user.id) if current_user.__dict__.get("id") else None
return render_template('login.html', logged_in_as=logged_in_as)
return render_template('login.html', logged_in_as=logged_in_as, signup_message=signup_message)

def password_complexity(password):
return len(password) > 7 and any(char in password for char in "!@$%&")

@app.route('/signup', methods=['GET', 'POST'])
def get_signup():
logged_in_as = ", " + str(current_user.id) if current_user.__dict__.get("id") else None
if logged_in_as:
return redirect(url_for('get_home'))
username_error = None
password_error = None
username = ""
if request.method == "POST":
username = request.form["username"]
password = request.form["password"]
confirm_password = request.form["confirm_password"]
if password != confirm_password:
password_error = "Passwords did not match"
elif not password_complexity(password):
password_error = "Password complexity requirements not met"
elif username == "":
username_error = "Username cannot be blank"
elif username.lower() == "admin":
username_error = "Username cannot be 'admin' (or similar)"
if not username_error and not password_error:
connection = get_flask_database_connection(app)
User().sign_up(connection, username, password)
return redirect(url_for('get_login', signup_message=[True]))
return render_template('signup.html', username_error=username_error, password_error=password_error, username=username)

@app.route('/logout', methods=['GET'])
def get_logout():
logout_user()
logged_in_as = ", " + str(current_user.id) if current_user.__dict__.get("id") else None
return render_template('logout.html', logged_in_as=logged_in_as)
return render_template('logout.html')

@app.route('/account', methods=['GET'])
@login_required
Expand All @@ -171,7 +224,8 @@ def get_account():
ticket_text = f"{booking.ticket_count} tickets" if booking.ticket_count > 1 else f"{booking.ticket_count} ticket"
booking_details.append({
"ticket_count": ticket_text,
"gig": repo.get_by_id(booking.gig_id)
"gig": repo.get_by_id(booking.gig_id),
"id": booking.id
})
logged_in_as = ", " + str(current_user.id) if current_user.__dict__.get("id") else None
return render_template('account.html', booking_details=booking_details, logged_in_as=logged_in_as)
Expand Down Expand Up @@ -205,7 +259,7 @@ def admin_add_gig():

@app.route('/api')
def api_root():
return "You need to specify a resource such as \"gigs\" via a request like GET /api/&lt;resource&gt;"
return "Specify a resource such as \"gigs\" via a request like GET /api/&lt;resource&gt;"

@app.route('/api/<resource>')
def api_resource(resource):
Expand Down Expand Up @@ -233,16 +287,19 @@ def api_resource(resource):
bands = set([gig.band for gig in gigs])
return json.dumps(list(bands))
case "accounts" | "bookings":
return "You need to specify an Id for this resource via a request like GET /api/&lt;resource&gt;/&lt;Id&gt;"
return "Specify an Id for this resource via a request like GET /api/&lt;resource&gt;/&lt;Id&gt;"
case _:
return "Unknown API resource: " + resource
return "Unknown API resource: " + resource, 404

@app.route('/api/gigs/<id>')
def api_gig(id):
connection = get_flask_database_connection(app)
repo = GigRepository(connection)
gig = repo.get_by_id(id)
return json.dumps(gig.jsonify())
try:
return json.dumps(gig.jsonify())
except:
return json.dumps({}), 404

@app.route('/api/bands/<name>')
def api_band(name):
Expand All @@ -251,5 +308,53 @@ def api_band(name):
gigs = repo.get_by_band_name(name)
return json.dumps([gig.jsonify() for gig in gigs])

@app.route('/api/accounts/<id>')
@login_required
def api_account(id):
connection = get_flask_database_connection(app)
repo = BookingRepository(connection)
bookings = repo.get_bookings(id)
try:
username = User().get_username(connection, id)
return json.dumps({
"bookings": [booking.jsonify() for booking in bookings],
"username": username
})
except:
return json.dumps({}), 404

@app.route('/api/bookings/<id>', methods=["GET"])
@admin_user_required
def api_booking(id):
connection = get_flask_database_connection(app)
repo = BookingRepository(connection)
booking = repo.get_by_id(id)
try:
return json.dumps(booking.jsonify())
except:
return json.dumps({}), 404

@app.route('/api/bookings', methods=["POST"])
@admin_user_required
def api_post_booking():
connection = get_flask_database_connection(app)
repo = BookingRepository(connection)
try:
gig_id = request.form["gig_id"]
user_id = request.form["user_id"]
ticket_count = request.form["ticket_count"]
repo.make_booking(gig_id, user_id, ticket_count)
return "", 200
except:
return "POST failed", 400

@app.route('/api/bookings/<id>', methods=["DELETE"])
@admin_user_required
def api_delete_booking(id):
connection = get_flask_database_connection(app)
repo = BookingRepository(connection)
booking = repo.cancel_booking(id)
return "", 200

if __name__ == '__main__':
app.run(debug=True, port=int(os.environ.get('PORT', 5001)))
12 changes: 12 additions & 0 deletions lib/booking.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,3 +18,15 @@ def __init__(self, id, dt, gig_id, user_id, ticket_count):

def __eq__(self, other):
return self.__dict__ == other.__dict__

def datetime_pretty(self):
return self.datetime.strftime("%Y-%m-%d %H:%M")

def jsonify(self):
return {
"id": self.id,
"datetime": self.datetime_pretty(),
"gig_id": self.gig_id,
"user_id": self.user_id,
"ticket_count": self.ticket_count
}
11 changes: 11 additions & 0 deletions lib/booking_repository.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,17 @@ def get_bookings(self, user_id):
bookings.append(Booking(row["id"], row["datetime"], row["gig_id"], row["user_id"], row["ticket_count"]))
return bookings

def get_by_id(self, booking_id):
rows = self._connection.execute('SELECT * FROM bookings WHERE id = %s', [booking_id])
if rows == []:
return None
else:
row = rows[0]
return Booking(row["id"], row["datetime"], row["gig_id"], row["user_id"], row["ticket_count"])

def make_booking(self, gig_id, user_id, ticket_count):
booking_datetime_str = datetime.datetime.now().strftime("%Y-%m-%d %H:%M")
self._connection.execute('INSERT INTO bookings (datetime, gig_id, user_id, ticket_count) VALUES (%s, %s, %s, %s)', [booking_datetime_str, gig_id, user_id, ticket_count])

def cancel_booking(self, booking_id):
self._connection.execute('DELETE FROM bookings WHERE id = %s', [booking_id])
2 changes: 1 addition & 1 deletion lib/gig_repository.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ def get_by_location_and_dates(self, location, date_from="2000-01-01", date_to="2
return matches

def get_by_band_name(self, band_name):
rows = self._connection.execute('SELECT * FROM gigs WHERE band = %s ORDER BY datetime', [band_name])
rows = self._connection.execute('SELECT * FROM gigs WHERE LOWER(band) = LOWER(%s) ORDER BY datetime', [band_name])
gigs = []
for row in rows:
gigs.append(Gig(row["id"], row["datetime"], row["band"], row["venue"], row["location"], row["postcode"]))
Expand Down
26 changes: 17 additions & 9 deletions static/style.css
Original file line number Diff line number Diff line change
Expand Up @@ -5,28 +5,29 @@ body {
margin-left: 20px;
max-width: 850px;
}
h2 {
h1 {
font-size: 2em !important;
margin-bottom: 0;
}
#logo {
margin: 15px;
margin-right: 30px;
}
.menu_bar {
background-color: black;
background-color: rgb(38, 68, 73);
height: 3em;
margin-bottom: 10px; /** spacing below menu, before page content **/
}
.btn-menu{
--bs-btn-color:#fff;
--bs-btn-bg:#c7c4b9;
--bs-btn-border-color:#c7c4b9;
--bs-btn-bg:#777469;
--bs-btn-border-color:#777469;
--bs-btn-hover-color:#fff;
--bs-btn-hover-bg:#a7a499;
--bs-btn-hover-border-color:#a7a499;
--bs-btn-active-color:#bfb;
--bs-btn-active-bg:#979489;
--bs-btn-active-border-color:#979489;
--bs-btn-hover-bg:#575449;
--bs-btn-hover-border-color:#575449;
--bs-btn-active-color:#8ac2d4;
--bs-btn-active-bg:#474439;
--bs-btn-active-border-color:#474439;
--bs-btn-padding-y:0.25rem;
--bs-btn-font-size:0.875rem;
--bs-btn-border-radius:var(--bs-border-radius-sm)
Expand All @@ -45,4 +46,11 @@ h2 {
}
.login-field {
margin-bottom: 10px;
}
a {
color: #1d7d9c;
text-decoration: underline;
}
* {
font-family: 'Gill Sans';
}
18 changes: 18 additions & 0 deletions templates/400.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<!DOCTYPE html>
<html lang="en">
<head>
<title>400 Client Error</title>
<link href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-QWTKZyjpPEjISv5WaRU9OFeRpok6YctnYmDr5pNlyT2bRjXh0JMhjY6hW+ALEwIH" crossorigin="anonymous">
<link rel="stylesheet" href="/static/style.css" >
</head>
<body>
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/js/bootstrap.bundle.min.js" integrity="sha384-YvpcrYf0tY3lHB60NNkmXc5s9fDVZLESaAA55NDzOxhy9GkcIdslK1eN7N6jIeHz" crossorigin="anonymous"></script>
<h1>{% include 'logo_link.html' %}</h1>
<div id="page-content">
<h2>400 Client Error</h2>
{% if reason %}<p>Reason: {{ reason }}</p>{% endif %}
<br /><br /><br />
<p>Go <a href="javascript:window.history.back();">Back</a> and try again!</p>
</div>
</body>
</html>
9 changes: 5 additions & 4 deletions templates/401.html
Original file line number Diff line number Diff line change
@@ -1,18 +1,19 @@
<!DOCTYPE html>
<html>
<html lang="en">
<head>
<title>401 Unauthorised</title>
<link href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-QWTKZyjpPEjISv5WaRU9OFeRpok6YctnYmDr5pNlyT2bRjXh0JMhjY6hW+ALEwIH" crossorigin="anonymous">
<link rel="stylesheet" href="/static/style.css" >
</head>
<body>
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/js/bootstrap.bundle.min.js" integrity="sha384-YvpcrYf0tY3lHB60NNkmXc5s9fDVZLESaAA55NDzOxhy9GkcIdslK1eN7N6jIeHz" crossorigin="anonymous"></script>
<h2 id="element-fa1b5c0"><img id="logo" src="/static/images/logo.bmp" width="100px" /></h2>
<h1>{% include 'logo_link.html' %}</h1>
<div id="page-content">
<h1>401 Unauthorised</h1>
<h2>401 Unauthorised</h2>
<p>You are not allowed to access this page.</p>
{% if reason %}<p>Reason: {{ reason }}</p>{% endif %}
<br /><br /><br />
<p>Why don't you try to Log In first...</p>
<p>Why don't you try to <a href="/login">Log In</a> first...</p>
</div>
</body>
</html>
4 changes: 2 additions & 2 deletions templates/404.html
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
<!DOCTYPE html>
<html>
<html lang="en">
<head>
<title>404 Not Found</title>
<link href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-QWTKZyjpPEjISv5WaRU9OFeRpok6YctnYmDr5pNlyT2bRjXh0JMhjY6hW+ALEwIH" crossorigin="anonymous">
<link rel="stylesheet" href="/static/style.css" >
</head>
<body>
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/js/bootstrap.bundle.min.js" integrity="sha384-YvpcrYf0tY3lHB60NNkmXc5s9fDVZLESaAA55NDzOxhy9GkcIdslK1eN7N6jIeHz" crossorigin="anonymous"></script>
<h2 id="element-234ce119"><img id="logo" src="/static/images/logo.bmp" width="100px" /></h2>
<h1>{% include 'logo_link.html' %}</h1>
<div id="page-content">
<h1>404 Not Found</h1>
<p>I'm sorry to inform you that you have sailed off the edge of the world.</p>
Expand Down
Loading

0 comments on commit 91d2ee9

Please sign in to comment.