Skip to content

Commit 2125466

Browse files
authored
Add docker compose config (#208)
1 parent 570ee79 commit 2125466

File tree

14 files changed

+276
-214
lines changed

14 files changed

+276
-214
lines changed

README.md

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -56,8 +56,7 @@ The following starts the frontend and backend for local development:
5656
### Frontend
5757
```
5858
cd frontend
59-
yarn
60-
npm run dev
59+
yarn run dev
6160
```
6261

6362
### Backend

backend/Dockerfile

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,3 +15,9 @@ USER bracket
1515
RUN set -ex \
1616
&& pip3 install --upgrade pip pipenv wheel virtualenv \
1717
&& pipenv install --deploy
18+
19+
CMD pipenv run pipenv run gunicorn \
20+
-k uvicorn.workers.UvicornWorker \
21+
bracket.app:app \
22+
--bind 0.0.0.0:8400 \
23+
--workers 2

backend/bracket/app.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
from starlette.staticfiles import StaticFiles
77

88
from bracket.config import Environment, config, environment, init_sentry
9-
from bracket.database import database, init_db_when_empty
9+
from bracket.database import database
1010
from bracket.routes import (
1111
auth,
1212
clubs,
@@ -19,6 +19,7 @@
1919
tournaments,
2020
users,
2121
)
22+
from bracket.utils.db_init import init_db_when_empty
2223

2324
init_sentry()
2425

backend/bracket/config.py

Lines changed: 14 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
from enum import auto
44

55
import sentry_sdk
6-
from pydantic import BaseSettings, Field, PostgresDsn
6+
from pydantic import BaseSettings, PostgresDsn
77

88
from bracket.utils.types import EnumAutoStr
99

@@ -32,16 +32,16 @@ def get_log_level(self) -> int:
3232

3333

3434
class Config(BaseSettings):
35-
pg_dsn: PostgresDsn = 'postgresql://user:pass@localhost:5432/db' # type: ignore[assignment]
35+
admin_email: str | None = None
36+
admin_password: str | None = None
37+
allow_insecure_http_sso: bool = False
38+
allow_user_registration: bool = True
39+
base_url: str = 'http://localhost:8400'
40+
cors_origin_regex: str = ''
41+
cors_origins: str = ''
3642
jwt_secret: str
37-
cors_origins: str = Field(default='')
38-
cors_origin_regex: str = Field(default='')
39-
admin_email: str | None = Field(default=None)
40-
admin_password: str | None = Field(default=None)
41-
sentry_dsn: str | None = Field(default=None)
42-
allow_insecure_http_sso: bool = Field(default=False)
43-
base_url: str = Field(default='http://localhost:8400')
44-
allow_user_registration: bool = Field(default=True)
43+
pg_dsn: PostgresDsn = 'postgresql://user:pass@localhost:5432/db' # type: ignore[assignment]
44+
sentry_dsn: str | None = None
4545

4646

4747
class CIConfig(Config):
@@ -50,7 +50,10 @@ class Config:
5050

5151

5252
class DevelopmentConfig(Config):
53-
allow_insecure_http_sso: bool = Field(default=True)
53+
admin_email = 'test@example.org'
54+
admin_password = 'aeGhoe1ahng2Aezai0Dei6Aih6dieHoo'
55+
allow_insecure_http_sso = True
56+
jwt_secret = '7495204c062787f257b12d03b88d80da1d338796a6449666eb634c9efbbf5fa7'
5457

5558
class Config:
5659
env_file = 'dev.env'

backend/bracket/database.py

Lines changed: 1 addition & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -1,39 +1,8 @@
11
import sqlalchemy
22
from databases import Database
3-
from heliclockter import datetime_utc
43

5-
from bracket.config import Environment, config, environment
6-
from bracket.models.db.user import User
7-
from bracket.schema import metadata, users
8-
from bracket.utils.logging import logger
9-
from bracket.utils.security import pwd_context
4+
from bracket.config import config
105

116
database = Database(config.pg_dsn)
127

138
engine = sqlalchemy.create_engine(config.pg_dsn)
14-
15-
16-
async def init_db_when_empty() -> int | None:
17-
table_count = await database.fetch_val(
18-
'SELECT count(*) FROM information_schema.tables WHERE table_schema = \'public\''
19-
)
20-
if (
21-
table_count <= 1
22-
and environment != Environment.CI
23-
and config.admin_email
24-
and config.admin_password
25-
):
26-
logger.warning('Empty db detected, creating tables...')
27-
metadata.create_all(engine)
28-
29-
logger.warning('Empty db detected, creating admin user...')
30-
admin = User(
31-
name='Admin',
32-
email=config.admin_email,
33-
password_hash=pwd_context.hash(config.admin_password),
34-
created=datetime_utc.now(),
35-
)
36-
user_id: int = await database.execute(query=users.insert(), values=admin.dict())
37-
return user_id
38-
39-
return None

backend/bracket/routes/auth.py

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,9 @@
1212
from bracket.database import database
1313
from bracket.models.db.tournament import Tournament
1414
from bracket.models.db.user import UserInDB, UserPublic
15-
from bracket.schema import tournaments, users
16-
from bracket.sql.users import get_user_access_to_club, get_user_access_to_tournament
17-
from bracket.utils.db import fetch_all_parsed, fetch_one_parsed
15+
from bracket.schema import tournaments
16+
from bracket.sql.users import get_user, get_user_access_to_club, get_user_access_to_tournament
17+
from bracket.utils.db import fetch_all_parsed
1818
from bracket.utils.security import pwd_context
1919
from bracket.utils.types import assert_some
2020

@@ -55,10 +55,6 @@ def verify_password(plain_password: str, hashed_password: str) -> bool:
5555
return pwd_context.verify(plain_password, hashed_password)
5656

5757

58-
async def get_user(email: str) -> UserInDB | None:
59-
return await fetch_one_parsed(database, UserInDB, users.select().where(users.c.email == email))
60-
61-
6258
async def authenticate_user(email: str, password: str) -> UserInDB | None:
6359
user = await get_user(email)
6460

backend/bracket/sql/users.py

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
from datetime import datetime
22

33
from bracket.database import database
4-
from bracket.models.db.user import User, UserPublic, UserToUpdate
4+
from bracket.models.db.user import User, UserInDB, UserPublic, UserToUpdate
5+
from bracket.schema import users
6+
from bracket.utils.db import fetch_one_parsed
57
from bracket.utils.types import assert_some
68

79

@@ -94,3 +96,7 @@ async def check_whether_email_is_in_use(email: str) -> bool:
9496
'''
9597
result = await database.fetch_one(query=query, values={'email': email})
9698
return result is not None
99+
100+
101+
async def get_user(email: str) -> UserInDB | None:
102+
return await fetch_one_parsed(database, UserInDB, users.select().where(users.c.email == email))

backend/bracket/utils/db_init.py

Lines changed: 190 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,190 @@
1+
from heliclockter import datetime_utc
2+
from sqlalchemy import Table
3+
4+
from bracket.config import Environment, config, environment
5+
from bracket.database import database, engine
6+
from bracket.logic.elo import recalculate_elo_for_tournament_id
7+
from bracket.models.db.club import Club
8+
from bracket.models.db.court import Court
9+
from bracket.models.db.match import Match
10+
from bracket.models.db.player import Player
11+
from bracket.models.db.round import Round
12+
from bracket.models.db.stage import Stage
13+
from bracket.models.db.team import Team
14+
from bracket.models.db.tournament import Tournament
15+
from bracket.models.db.user import User
16+
from bracket.models.db.user_x_club import UserXClub
17+
from bracket.schema import (
18+
clubs,
19+
courts,
20+
matches,
21+
metadata,
22+
players,
23+
rounds,
24+
stages,
25+
teams,
26+
tournaments,
27+
users,
28+
users_x_clubs,
29+
)
30+
from bracket.sql.users import get_user
31+
from bracket.utils.db import insert_generic
32+
from bracket.utils.dummy_records import (
33+
DUMMY_CLUB,
34+
DUMMY_COURT1,
35+
DUMMY_COURT2,
36+
DUMMY_MATCH1,
37+
DUMMY_MATCH2,
38+
DUMMY_MATCH3,
39+
DUMMY_MATCH4,
40+
DUMMY_PLAYER1,
41+
DUMMY_PLAYER2,
42+
DUMMY_PLAYER3,
43+
DUMMY_PLAYER4,
44+
DUMMY_PLAYER5,
45+
DUMMY_PLAYER6,
46+
DUMMY_PLAYER7,
47+
DUMMY_PLAYER8,
48+
DUMMY_PLAYER9,
49+
DUMMY_ROUND1,
50+
DUMMY_ROUND2,
51+
DUMMY_ROUND3,
52+
DUMMY_STAGE1,
53+
DUMMY_STAGE2,
54+
DUMMY_TEAM1,
55+
DUMMY_TEAM2,
56+
DUMMY_TEAM3,
57+
DUMMY_TEAM4,
58+
DUMMY_TOURNAMENT,
59+
DUMMY_USER,
60+
)
61+
from bracket.utils.logging import logger
62+
from bracket.utils.security import pwd_context
63+
from bracket.utils.types import BaseModelT
64+
65+
66+
async def create_admin_user() -> int:
67+
assert config.admin_email
68+
assert config.admin_password
69+
70+
admin = User(
71+
name='Admin',
72+
email=config.admin_email,
73+
password_hash=pwd_context.hash(config.admin_password),
74+
created=datetime_utc.now(),
75+
)
76+
77+
user: int = await database.execute(query=users.insert(), values=admin.dict())
78+
return user
79+
80+
81+
async def init_db_when_empty() -> int | None:
82+
table_count = await database.fetch_val(
83+
'SELECT count(*) FROM information_schema.tables WHERE table_schema = \'public\''
84+
)
85+
if config.admin_email and config.admin_password:
86+
if (table_count <= 1 and environment != Environment.CI) or (
87+
environment is Environment.DEVELOPMENT and await get_user(config.admin_email) is None
88+
):
89+
logger.warning('Empty db detected, creating tables...')
90+
metadata.create_all(engine)
91+
92+
logger.warning('Empty db detected, creating admin user...')
93+
return await create_admin_user()
94+
95+
return None
96+
97+
98+
async def sql_create_dev_db() -> None:
99+
assert environment is Environment.DEVELOPMENT
100+
101+
logger.warning('Initializing database with dummy records')
102+
await database.connect()
103+
metadata.drop_all(engine)
104+
metadata.create_all(engine)
105+
real_user_id = await init_db_when_empty()
106+
107+
table_lookup: dict[type, Table] = {
108+
User: users,
109+
Club: clubs,
110+
Stage: stages,
111+
Team: teams,
112+
UserXClub: users_x_clubs,
113+
Player: players,
114+
Round: rounds,
115+
Match: matches,
116+
Tournament: tournaments,
117+
Court: courts,
118+
}
119+
120+
async def insert_dummy(obj_to_insert: BaseModelT) -> int:
121+
record_id, _ = await insert_generic(
122+
database, obj_to_insert, table_lookup[type(obj_to_insert)], type(obj_to_insert)
123+
)
124+
return record_id
125+
126+
user_id_1 = await insert_dummy(DUMMY_USER)
127+
club_id_1 = await insert_dummy(DUMMY_CLUB)
128+
await insert_dummy(UserXClub(user_id=user_id_1, club_id=club_id_1))
129+
130+
if real_user_id is not None:
131+
await insert_dummy(UserXClub(user_id=real_user_id, club_id=club_id_1))
132+
133+
tournament_id_1 = await insert_dummy(DUMMY_TOURNAMENT.copy(update={'club_id': club_id_1}))
134+
stage_id_1 = await insert_dummy(DUMMY_STAGE1.copy(update={'tournament_id': tournament_id_1}))
135+
stage_id_2 = await insert_dummy(DUMMY_STAGE2.copy(update={'tournament_id': tournament_id_1}))
136+
team_id_1 = await insert_dummy(DUMMY_TEAM1.copy(update={'tournament_id': tournament_id_1}))
137+
team_id_2 = await insert_dummy(DUMMY_TEAM2.copy(update={'tournament_id': tournament_id_1}))
138+
team_id_3 = await insert_dummy(DUMMY_TEAM3.copy(update={'tournament_id': tournament_id_1}))
139+
team_id_4 = await insert_dummy(DUMMY_TEAM4.copy(update={'tournament_id': tournament_id_1}))
140+
141+
await insert_dummy(DUMMY_PLAYER1.copy(update={'tournament_id': tournament_id_1}))
142+
await insert_dummy(DUMMY_PLAYER2.copy(update={'tournament_id': tournament_id_1}))
143+
await insert_dummy(DUMMY_PLAYER3.copy(update={'tournament_id': tournament_id_1}))
144+
await insert_dummy(DUMMY_PLAYER4.copy(update={'tournament_id': tournament_id_1}))
145+
await insert_dummy(DUMMY_PLAYER5.copy(update={'tournament_id': tournament_id_1}))
146+
await insert_dummy(DUMMY_PLAYER6.copy(update={'tournament_id': tournament_id_1}))
147+
await insert_dummy(DUMMY_PLAYER7.copy(update={'tournament_id': tournament_id_1}))
148+
await insert_dummy(DUMMY_PLAYER8.copy(update={'tournament_id': tournament_id_1}))
149+
await insert_dummy(DUMMY_PLAYER9.copy(update={'tournament_id': tournament_id_1}))
150+
151+
round_id_1 = await insert_dummy(DUMMY_ROUND1.copy(update={'stage_id': stage_id_1}))
152+
round_id_2 = await insert_dummy(DUMMY_ROUND2.copy(update={'stage_id': stage_id_1}))
153+
round_id_3 = await insert_dummy(DUMMY_ROUND3.copy(update={'stage_id': stage_id_2}))
154+
155+
court_id_1 = await insert_dummy(DUMMY_COURT1.copy(update={'tournament_id': tournament_id_1}))
156+
court_id_2 = await insert_dummy(DUMMY_COURT2.copy(update={'tournament_id': tournament_id_1}))
157+
158+
await insert_dummy(
159+
DUMMY_MATCH1.copy(
160+
update={
161+
'round_id': round_id_1,
162+
'team1_id': team_id_1,
163+
'team2_id': team_id_2,
164+
'court_id': court_id_1,
165+
}
166+
),
167+
)
168+
await insert_dummy(
169+
DUMMY_MATCH2.copy(
170+
update={
171+
'round_id': round_id_1,
172+
'team1_id': team_id_3,
173+
'team2_id': team_id_4,
174+
'court_id': court_id_2,
175+
}
176+
),
177+
)
178+
await insert_dummy(
179+
DUMMY_MATCH3.copy(
180+
update={'round_id': round_id_2, 'team1_id': team_id_2, 'team2_id': team_id_4}
181+
),
182+
)
183+
await insert_dummy(
184+
DUMMY_MATCH4.copy(
185+
update={'round_id': round_id_3, 'team1_id': team_id_3, 'team2_id': team_id_1}
186+
),
187+
)
188+
189+
for tournament in await database.fetch_all(tournaments.select()):
190+
await recalculate_elo_for_tournament_id(tournament.id) # type: ignore[attr-defined]

0 commit comments

Comments
 (0)