Skip to content

Commit 04c2201

Browse files
committed
release v2
1 parent c7db498 commit 04c2201

28 files changed

Lines changed: 551 additions & 41 deletions

.github/workflows/ci.yml

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
name: Run Giga Unit Tests
2+
3+
on: [push, pull_request]
4+
5+
jobs:
6+
test:
7+
runs-on: ubuntu-latest
8+
9+
steps:
10+
- name: Checkout code
11+
uses: actions/checkout@v4
12+
13+
- name: Build custom Postgres image
14+
run: |
15+
docker build -t custom-postgres -f ci_config/Dockerfile.postgres .
16+
if docker ps -q --filter "ancestor=postgres:latest" | grep -q .; then
17+
docker stop $(docker ps -q --filter "ancestor=postgres:latest")
18+
fi
19+
docker run --name pg-container -d -p 5432:5432 \
20+
-e POSTGRES_DB=giga_test \
21+
-e POSTGRES_USER=postgres \
22+
-e POSTGRES_HOST_AUTH_METHOD=trust \
23+
custom-postgres
24+
25+
- name: Wait for PostgreSQL to be ready (1/2)
26+
run: |
27+
timeout 30s bash -c '
28+
until docker exec pg-container pg_isready -U postgres; do
29+
echo "Waiting for PostgreSQL to be ready..."
30+
sleep 2
31+
done
32+
'
33+
34+
- name: Wait for PostgreSQL to be ready (2/2)
35+
run: |
36+
until docker exec pg-container psql -U postgres -c "\l"; do
37+
echo "Waiting for PostgreSQL to accept connections..."
38+
sleep 2
39+
done
40+
41+
42+
- name: Set up Python
43+
uses: actions/setup-python@v5
44+
with:
45+
python-version: '3.x'
46+
47+
- name: Install dependencies
48+
run: |
49+
python -m pip install --upgrade pip
50+
pip install -r requirements.txt
51+
52+
- name: Install Playwright browsers
53+
run: playwright install
54+
55+
- name: Create database if it doesn't exist
56+
run: psql -h localhost -U postgres -tc "SELECT 1 FROM pg_database WHERE datname = 'giga_test'" | grep -q 1 || psql -h localhost -U postgres -c "CREATE DATABASE giga_test;"
57+
58+
- name: Run tests
59+
run: pytest

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,5 +9,5 @@ Giga - the gig booking site that'll be _huge_.
99
* `source giga_venv/bin/activate`
1010
* `pip install -r requirements.txt`
1111
* Create the databases specified in `lib/database_connection.py` - `giga` and `giga_test`
12-
* Seed the production database e.g. `psql -h 127.0.0.1 giga -f seeds/test_gigs.sql -f seeds/test_bookings.sql`
12+
* Seed the production database e.g. `psql -h 127.0.0.1 giga -f seeds/test_gigs.sql -f seeds/test_users.sql -f seeds/test_bookings.sql`
1313
* Run the server with `python app.py`

app.py

Lines changed: 101 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,49 @@
33
from lib.database_connection import get_flask_database_connection
44
from lib.gig_repository import GigRepository
55
from lib.booking_repository import BookingRepository
6+
from flask_login import (
7+
LoginManager,
8+
UserMixin,
9+
current_user,
10+
login_required,
11+
login_user,
12+
logout_user
13+
)
14+
from werkzeug.security import generate_password_hash, check_password_hash
15+
from functools import wraps
16+
import datetime
617

718
app = Flask(__name__)
19+
app.config.update(
20+
SECRET_KEY="adobgaiodbgaidgbiodgb",
21+
)
22+
23+
login_manager = LoginManager()
24+
login_manager.init_app(app)
25+
26+
class User(UserMixin):
27+
def signed_up(self, connection, username):
28+
rows = connection.execute('SELECT * FROM users WHERE username = %s', [username])
29+
return len(rows) > 0
30+
def password_valid(self, connection, username, password_attempt):
31+
if not self.signed_up(connection, username):
32+
return False
33+
rows = connection.execute('SELECT * FROM users WHERE username = %s', [username])
34+
return check_password_hash(rows[0]["hashed_password"], password_attempt) != username
35+
def get_user_database_id(self, connection, username):
36+
rows = connection.execute('SELECT * FROM users WHERE username = %s', [username])
37+
return rows[0]["id"]
38+
39+
@login_manager.user_loader
40+
def user_loader(username: str):
41+
connection = get_flask_database_connection(app)
42+
if User().signed_up(connection, username):
43+
user_model = User()
44+
user_model.id = username
45+
return user_model
46+
return None
47+
48+
849

950
@app.route('/home', methods=['GET'])
1051
def get_home():
@@ -14,30 +55,82 @@ def get_home():
1455
def get_about():
1556
return render_template('about.html')
1657

17-
@app.route('/gigs', methods=['GET'])
58+
@app.route('/gigs', methods=['GET', 'POST'])
1859
def get_gigs():
1960
connection = get_flask_database_connection(app)
20-
repo = BookingRepository(connection)
21-
gig_booked_ids = [booking.gig_id for booking in repo.get_bookings()]
2261
repo = GigRepository(connection)
23-
gigs = repo.all()
24-
gig_ids = [gig.id for gig in gigs]
25-
booked_percentage = int(len(set(gig_booked_ids)) / len(set(gig_ids)) * 100)
26-
return render_template('gigs.html', gigs=gigs, number_bookings=booked_percentage)
62+
locations = ["All"]
63+
for gig in repo.all():
64+
if gig.location not in locations:
65+
locations.append(gig.location)
66+
selected_location = "All"
67+
if "location" in request.form.keys():
68+
selected_location = request.form["location"]
69+
date_from = "1900-01-01"
70+
if "date_from" in request.form.keys():
71+
date_from = request.form["date_from"]
72+
date_to = "3000-01-01"
73+
if "date_to" in request.form.keys():
74+
date_to = request.form["date_to"]
75+
gigs = repo.get_by_location_and_dates(selected_location, date_from, date_to)
76+
return render_template('gigs.html', gigs=gigs, locations=locations, selected_location=selected_location, date_from=date_from, date_to=date_to)
77+
78+
@app.route('/gigs/<id>', methods=['GET'])
79+
def get_gig_by_id(id):
80+
connection = get_flask_database_connection(app)
81+
repo = GigRepository(connection)
82+
gig = repo.get_by_id(id)
83+
logged_in_as = str(current_user.id) if current_user.__dict__.get("id") else None
84+
repo = BookingRepository(connection)
85+
if current_user.__dict__ != {}:
86+
already_booked_gig = gig.id in [booking.gig_id for booking in repo.get_bookings(1)]
87+
else:
88+
already_booked_gig = False
89+
gig_in_past = gig.datetime < datetime.datetime.now()
90+
return render_template('gig.html', gig=gig, logged_in_as=logged_in_as, already_booked_gig=already_booked_gig, gig_in_past=gig_in_past)
91+
92+
@app.route("/book_gig/<gig_id>", methods=["POST"])
93+
def post_book_gig(gig_id):
94+
if int(request.form["ticket_count"]) > 8:
95+
return "A user can't book more than 8 tickets for one gig"
96+
connection = get_flask_database_connection(app)
97+
repo = BookingRepository(connection)
98+
user_database_id = User().get_user_database_id(connection, current_user.id)
99+
repo.make_booking(gig_id, user_database_id, request.form["ticket_count"])
100+
return redirect(url_for('get_account'))
101+
102+
@app.route("/login", methods=["POST"])
103+
def post_login():
104+
username = request.form["usernmae"]
105+
password = request.form["password"]
106+
connection = get_flask_database_connection(app)
107+
108+
if User().signed_up(connection, username):
109+
if User().password_valid(connection, username, password):
110+
user_model = User()
111+
user_model.id = username
112+
login_user(user_model)
113+
return redirect(url_for('get_home'))
114+
else:
115+
return "Wrong credentials"
116+
return "Unknown user"
27117

28118
@app.route('/login', methods=['GET'])
29119
def get_login():
30120
return render_template('login.html')
31121

32122
@app.route('/logout', methods=['GET'])
33123
def get_logout():
124+
logout_user()
34125
return render_template('logout.html')
35126

36127
@app.route('/account', methods=['GET'])
128+
@login_required
37129
def get_account():
38130
connection = get_flask_database_connection(app)
39131
repo = BookingRepository(connection)
40-
bookings = repo.get_bookings()
132+
user_database_id = User().get_user_database_id(connection, current_user.id)
133+
bookings = repo.get_bookings(user_database_id)
41134
repo = GigRepository(connection)
42135
gigs = repo.all()
43136
booking_details = []
@@ -49,9 +142,5 @@ def get_account():
49142
})
50143
return render_template('account.html', booking_details=booking_details)
51144

52-
@app.route('/tcs', methods=['GET'])
53-
def get_tcs():
54-
return render_template('tcs.html')
55-
56145
if __name__ == '__main__':
57146
app.run(debug=True, port=int(os.environ.get('PORT', 5001)))

ci_config/Dockerfile.postgres

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
FROM postgres:latest
2+
COPY ci_config/init-auth.sh /docker-entrypoint-initdb.d/
3+
RUN chmod +x /docker-entrypoint-initdb.d/init-auth.sh

ci_config/init-auth.sh

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
#!/bin/bash
2+
set -e
3+
4+
# Modify pg_hba.conf
5+
cat > "$PGDATA/pg_hba.conf" << EOF
6+
# TYPE DATABASE USER ADDRESS METHOD
7+
local all all trust
8+
host all all 127.0.0.1/32 trust
9+
host all all ::1/128 trust
10+
host all all 0.0.0.0/0 trust
11+
EOF
12+
13+
# Set proper permissions
14+
chown postgres:postgres "$PGDATA/pg_hba.conf"
15+
chmod 600 "$PGDATA/pg_hba.conf"
16+
17+
# Create the runner role and grant necessary permissions
18+
psql -U postgres -d postgres -c "CREATE ROLE runner WITH LOGIN SUPERUSER PASSWORD 'runner';"

lib/booking.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,3 +15,6 @@ def __init__(self, id, dt, gig_id, user_id, ticket_count):
1515
self.gig_id = gig_id
1616
self.user_id = user_id
1717
self.ticket_count = ticket_count
18+
19+
def __eq__(self, other):
20+
return self.__dict__ == other.__dict__

lib/booking_repository.py

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,17 @@
11
from lib.booking import Booking
2+
import datetime
23

34
class BookingRepository:
45
def __init__(self, connection):
56
self._connection = connection
67

7-
def get_bookings(self):
8-
rows = self._connection.execute('SELECT * FROM bookings')
8+
def get_bookings(self, user_id):
9+
rows = self._connection.execute('SELECT * FROM bookings WHERE user_id = %s', [user_id])
910
bookings = []
1011
for row in rows:
1112
bookings.append(Booking(row["id"], row["datetime"], row["gig_id"], row["user_id"], row["ticket_count"]))
1213
return bookings
14+
15+
def make_booking(self, gig_id, user_id, ticket_count):
16+
booking_datetime_str = datetime.datetime.now().strftime("%Y-%m-%d %H:%M")
17+
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])

lib/gig.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,3 +20,9 @@ def __init__(self, id, dt, band, venue, location, postcode):
2020

2121
# for maps lookup
2222
self.postcode = postcode
23+
24+
def __eq__(self, other):
25+
return self.__dict__ == other.__dict__
26+
27+
def datetime_pretty(self):
28+
return self.datetime.strftime("%Y-%m-%d %H:%M")

lib/gig_repository.py

Lines changed: 31 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,5 +13,34 @@ def all(self):
1313

1414
def get_by_id(self, gig_id):
1515
rows = self._connection.execute('SELECT * FROM gigs WHERE id = %s', [gig_id])
16-
row = rows[0]
17-
return Gig(row["id"], row["datetime"], row["band"], row["venue"], row["location"], row["postcode"])
16+
if rows == []:
17+
return None
18+
else:
19+
row = rows[0]
20+
return Gig(row["id"], row["datetime"], row["band"], row["venue"], row["location"], row["postcode"])
21+
22+
def get_by_location(self, location):
23+
rows = self._connection.execute('SELECT * FROM gigs WHERE LOWER(location) = LOWER(%s) ORDER BY datetime', [location])
24+
gigs = []
25+
for row in rows:
26+
gigs.append(Gig(row["id"], row["datetime"], row["band"], row["venue"], row["location"], row["postcode"]))
27+
return gigs
28+
29+
def get_by_dates(self, date_from="1900-01-01", date_to="3000-01-01"):
30+
rows = self._connection.execute('SELECT * FROM gigs WHERE datetime BETWEEN %s AND %s ORDER BY datetime', [date_from, date_to])
31+
gigs = []
32+
for row in rows:
33+
gigs.append(Gig(row["id"], row["datetime"], row["band"], row["venue"], row["location"], row["postcode"]))
34+
return gigs
35+
36+
def get_by_location_and_dates(self, location, date_from="1900-01-01", date_to="3000-01-01"):
37+
if location == "All":
38+
gigs_by_location = self.all()
39+
else:
40+
gigs_by_location = self.get_by_location(location)
41+
gigs_by_dates = self.get_by_dates(date_from, date_to)
42+
matches = []
43+
for gig in gigs_by_location:
44+
if gig in gigs_by_dates:
45+
matches.append(gig)
46+
return matches

seeds/test_bookings.sql

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,9 @@ CREATE TABLE bookings (
1010
user_id INTEGER,
1111
ticket_count INTEGER,
1212
constraint fk_gig foreign key(gig_id)
13-
references gigs(id)
13+
references gigs(id),
14+
constraint fk_user foreign key(user_id)
15+
references users(id)
1416
);
1517

1618
INSERT INTO bookings (datetime, gig_id, user_id, ticket_count) VALUES ('2025-11-22 15:43', 1, 1, 1);

0 commit comments

Comments
 (0)