Skip to content

Commit

Permalink
Add docker compose config (#208)
Browse files Browse the repository at this point in the history
  • Loading branch information
evroon committed Sep 12, 2023
1 parent 570ee79 commit 2125466
Show file tree
Hide file tree
Showing 14 changed files with 276 additions and 214 deletions.
3 changes: 1 addition & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -56,8 +56,7 @@ The following starts the frontend and backend for local development:
### Frontend
```
cd frontend
yarn
npm run dev
yarn run dev
```

### Backend
Expand Down
6 changes: 6 additions & 0 deletions backend/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -15,3 +15,9 @@ USER bracket
RUN set -ex \
&& pip3 install --upgrade pip pipenv wheel virtualenv \
&& pipenv install --deploy

CMD pipenv run pipenv run gunicorn \
-k uvicorn.workers.UvicornWorker \
bracket.app:app \
--bind 0.0.0.0:8400 \
--workers 2
3 changes: 2 additions & 1 deletion backend/bracket/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
from starlette.staticfiles import StaticFiles

from bracket.config import Environment, config, environment, init_sentry
from bracket.database import database, init_db_when_empty
from bracket.database import database
from bracket.routes import (
auth,
clubs,
Expand All @@ -19,6 +19,7 @@
tournaments,
users,
)
from bracket.utils.db_init import init_db_when_empty

init_sentry()

Expand Down
25 changes: 14 additions & 11 deletions backend/bracket/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
from enum import auto

import sentry_sdk
from pydantic import BaseSettings, Field, PostgresDsn
from pydantic import BaseSettings, PostgresDsn

from bracket.utils.types import EnumAutoStr

Expand Down Expand Up @@ -32,16 +32,16 @@ def get_log_level(self) -> int:


class Config(BaseSettings):
pg_dsn: PostgresDsn = 'postgresql://user:pass@localhost:5432/db' # type: ignore[assignment]
admin_email: str | None = None
admin_password: str | None = None
allow_insecure_http_sso: bool = False
allow_user_registration: bool = True
base_url: str = 'http://localhost:8400'
cors_origin_regex: str = ''
cors_origins: str = ''
jwt_secret: str
cors_origins: str = Field(default='')
cors_origin_regex: str = Field(default='')
admin_email: str | None = Field(default=None)
admin_password: str | None = Field(default=None)
sentry_dsn: str | None = Field(default=None)
allow_insecure_http_sso: bool = Field(default=False)
base_url: str = Field(default='http://localhost:8400')
allow_user_registration: bool = Field(default=True)
pg_dsn: PostgresDsn = 'postgresql://user:pass@localhost:5432/db' # type: ignore[assignment]
sentry_dsn: str | None = None


class CIConfig(Config):
Expand All @@ -50,7 +50,10 @@ class Config:


class DevelopmentConfig(Config):
allow_insecure_http_sso: bool = Field(default=True)
admin_email = '[email protected]'
admin_password = 'aeGhoe1ahng2Aezai0Dei6Aih6dieHoo'
allow_insecure_http_sso = True
jwt_secret = '7495204c062787f257b12d03b88d80da1d338796a6449666eb634c9efbbf5fa7'

class Config:
env_file = 'dev.env'
Expand Down
33 changes: 1 addition & 32 deletions backend/bracket/database.py
Original file line number Diff line number Diff line change
@@ -1,39 +1,8 @@
import sqlalchemy
from databases import Database
from heliclockter import datetime_utc

from bracket.config import Environment, config, environment
from bracket.models.db.user import User
from bracket.schema import metadata, users
from bracket.utils.logging import logger
from bracket.utils.security import pwd_context
from bracket.config import config

database = Database(config.pg_dsn)

engine = sqlalchemy.create_engine(config.pg_dsn)


async def init_db_when_empty() -> int | None:
table_count = await database.fetch_val(
'SELECT count(*) FROM information_schema.tables WHERE table_schema = \'public\''
)
if (
table_count <= 1
and environment != Environment.CI
and config.admin_email
and config.admin_password
):
logger.warning('Empty db detected, creating tables...')
metadata.create_all(engine)

logger.warning('Empty db detected, creating admin user...')
admin = User(
name='Admin',
email=config.admin_email,
password_hash=pwd_context.hash(config.admin_password),
created=datetime_utc.now(),
)
user_id: int = await database.execute(query=users.insert(), values=admin.dict())
return user_id

return None
10 changes: 3 additions & 7 deletions backend/bracket/routes/auth.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,9 @@
from bracket.database import database
from bracket.models.db.tournament import Tournament
from bracket.models.db.user import UserInDB, UserPublic
from bracket.schema import tournaments, users
from bracket.sql.users import get_user_access_to_club, get_user_access_to_tournament
from bracket.utils.db import fetch_all_parsed, fetch_one_parsed
from bracket.schema import tournaments
from bracket.sql.users import get_user, get_user_access_to_club, get_user_access_to_tournament
from bracket.utils.db import fetch_all_parsed
from bracket.utils.security import pwd_context
from bracket.utils.types import assert_some

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


async def get_user(email: str) -> UserInDB | None:
return await fetch_one_parsed(database, UserInDB, users.select().where(users.c.email == email))


async def authenticate_user(email: str, password: str) -> UserInDB | None:
user = await get_user(email)

Expand Down
8 changes: 7 additions & 1 deletion backend/bracket/sql/users.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
from datetime import datetime

from bracket.database import database
from bracket.models.db.user import User, UserPublic, UserToUpdate
from bracket.models.db.user import User, UserInDB, UserPublic, UserToUpdate
from bracket.schema import users
from bracket.utils.db import fetch_one_parsed
from bracket.utils.types import assert_some


Expand Down Expand Up @@ -94,3 +96,7 @@ async def check_whether_email_is_in_use(email: str) -> bool:
'''
result = await database.fetch_one(query=query, values={'email': email})
return result is not None


async def get_user(email: str) -> UserInDB | None:
return await fetch_one_parsed(database, UserInDB, users.select().where(users.c.email == email))
190 changes: 190 additions & 0 deletions backend/bracket/utils/db_init.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,190 @@
from heliclockter import datetime_utc
from sqlalchemy import Table

from bracket.config import Environment, config, environment
from bracket.database import database, engine
from bracket.logic.elo import recalculate_elo_for_tournament_id
from bracket.models.db.club import Club
from bracket.models.db.court import Court
from bracket.models.db.match import Match
from bracket.models.db.player import Player
from bracket.models.db.round import Round
from bracket.models.db.stage import Stage
from bracket.models.db.team import Team
from bracket.models.db.tournament import Tournament
from bracket.models.db.user import User
from bracket.models.db.user_x_club import UserXClub
from bracket.schema import (
clubs,
courts,
matches,
metadata,
players,
rounds,
stages,
teams,
tournaments,
users,
users_x_clubs,
)
from bracket.sql.users import get_user
from bracket.utils.db import insert_generic
from bracket.utils.dummy_records import (
DUMMY_CLUB,
DUMMY_COURT1,
DUMMY_COURT2,
DUMMY_MATCH1,
DUMMY_MATCH2,
DUMMY_MATCH3,
DUMMY_MATCH4,
DUMMY_PLAYER1,
DUMMY_PLAYER2,
DUMMY_PLAYER3,
DUMMY_PLAYER4,
DUMMY_PLAYER5,
DUMMY_PLAYER6,
DUMMY_PLAYER7,
DUMMY_PLAYER8,
DUMMY_PLAYER9,
DUMMY_ROUND1,
DUMMY_ROUND2,
DUMMY_ROUND3,
DUMMY_STAGE1,
DUMMY_STAGE2,
DUMMY_TEAM1,
DUMMY_TEAM2,
DUMMY_TEAM3,
DUMMY_TEAM4,
DUMMY_TOURNAMENT,
DUMMY_USER,
)
from bracket.utils.logging import logger
from bracket.utils.security import pwd_context
from bracket.utils.types import BaseModelT


async def create_admin_user() -> int:
assert config.admin_email
assert config.admin_password

admin = User(
name='Admin',
email=config.admin_email,
password_hash=pwd_context.hash(config.admin_password),
created=datetime_utc.now(),
)

user: int = await database.execute(query=users.insert(), values=admin.dict())
return user


async def init_db_when_empty() -> int | None:
table_count = await database.fetch_val(
'SELECT count(*) FROM information_schema.tables WHERE table_schema = \'public\''
)
if config.admin_email and config.admin_password:
if (table_count <= 1 and environment != Environment.CI) or (
environment is Environment.DEVELOPMENT and await get_user(config.admin_email) is None
):
logger.warning('Empty db detected, creating tables...')
metadata.create_all(engine)

logger.warning('Empty db detected, creating admin user...')
return await create_admin_user()

return None


async def sql_create_dev_db() -> None:
assert environment is Environment.DEVELOPMENT

logger.warning('Initializing database with dummy records')
await database.connect()
metadata.drop_all(engine)
metadata.create_all(engine)
real_user_id = await init_db_when_empty()

table_lookup: dict[type, Table] = {
User: users,
Club: clubs,
Stage: stages,
Team: teams,
UserXClub: users_x_clubs,
Player: players,
Round: rounds,
Match: matches,
Tournament: tournaments,
Court: courts,
}

async def insert_dummy(obj_to_insert: BaseModelT) -> int:
record_id, _ = await insert_generic(
database, obj_to_insert, table_lookup[type(obj_to_insert)], type(obj_to_insert)
)
return record_id

user_id_1 = await insert_dummy(DUMMY_USER)
club_id_1 = await insert_dummy(DUMMY_CLUB)
await insert_dummy(UserXClub(user_id=user_id_1, club_id=club_id_1))

if real_user_id is not None:
await insert_dummy(UserXClub(user_id=real_user_id, club_id=club_id_1))

tournament_id_1 = await insert_dummy(DUMMY_TOURNAMENT.copy(update={'club_id': club_id_1}))
stage_id_1 = await insert_dummy(DUMMY_STAGE1.copy(update={'tournament_id': tournament_id_1}))
stage_id_2 = await insert_dummy(DUMMY_STAGE2.copy(update={'tournament_id': tournament_id_1}))
team_id_1 = await insert_dummy(DUMMY_TEAM1.copy(update={'tournament_id': tournament_id_1}))
team_id_2 = await insert_dummy(DUMMY_TEAM2.copy(update={'tournament_id': tournament_id_1}))
team_id_3 = await insert_dummy(DUMMY_TEAM3.copy(update={'tournament_id': tournament_id_1}))
team_id_4 = await insert_dummy(DUMMY_TEAM4.copy(update={'tournament_id': tournament_id_1}))

await insert_dummy(DUMMY_PLAYER1.copy(update={'tournament_id': tournament_id_1}))
await insert_dummy(DUMMY_PLAYER2.copy(update={'tournament_id': tournament_id_1}))
await insert_dummy(DUMMY_PLAYER3.copy(update={'tournament_id': tournament_id_1}))
await insert_dummy(DUMMY_PLAYER4.copy(update={'tournament_id': tournament_id_1}))
await insert_dummy(DUMMY_PLAYER5.copy(update={'tournament_id': tournament_id_1}))
await insert_dummy(DUMMY_PLAYER6.copy(update={'tournament_id': tournament_id_1}))
await insert_dummy(DUMMY_PLAYER7.copy(update={'tournament_id': tournament_id_1}))
await insert_dummy(DUMMY_PLAYER8.copy(update={'tournament_id': tournament_id_1}))
await insert_dummy(DUMMY_PLAYER9.copy(update={'tournament_id': tournament_id_1}))

round_id_1 = await insert_dummy(DUMMY_ROUND1.copy(update={'stage_id': stage_id_1}))
round_id_2 = await insert_dummy(DUMMY_ROUND2.copy(update={'stage_id': stage_id_1}))
round_id_3 = await insert_dummy(DUMMY_ROUND3.copy(update={'stage_id': stage_id_2}))

court_id_1 = await insert_dummy(DUMMY_COURT1.copy(update={'tournament_id': tournament_id_1}))
court_id_2 = await insert_dummy(DUMMY_COURT2.copy(update={'tournament_id': tournament_id_1}))

await insert_dummy(
DUMMY_MATCH1.copy(
update={
'round_id': round_id_1,
'team1_id': team_id_1,
'team2_id': team_id_2,
'court_id': court_id_1,
}
),
)
await insert_dummy(
DUMMY_MATCH2.copy(
update={
'round_id': round_id_1,
'team1_id': team_id_3,
'team2_id': team_id_4,
'court_id': court_id_2,
}
),
)
await insert_dummy(
DUMMY_MATCH3.copy(
update={'round_id': round_id_2, 'team1_id': team_id_2, 'team2_id': team_id_4}
),
)
await insert_dummy(
DUMMY_MATCH4.copy(
update={'round_id': round_id_3, 'team1_id': team_id_3, 'team2_id': team_id_1}
),
)

for tournament in await database.fetch_all(tournaments.select()):
await recalculate_elo_for_tournament_id(tournament.id) # type: ignore[attr-defined]
Loading

1 comment on commit 2125466

@vercel
Copy link

@vercel vercel bot commented on 2125466 Sep 12, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please sign in to comment.