Compare commits
3 Commits
3d17b6ef75
...
228e0043d5
| Author | SHA1 | Date | |
|---|---|---|---|
| 228e0043d5 | |||
| 7d57f00e5d | |||
| 0090862359 |
@@ -2,9 +2,10 @@
|
||||
|
||||
docker build -t wordlinator:latest .
|
||||
|
||||
if [ "$1" = "--debug" ]; then
|
||||
if [ "$1" == "--debug" ]; then
|
||||
shift
|
||||
docker run -d --rm -p 8050:8050 -e DEBUG=true -e DB_PORT -e DB_HOST -e DB_PASS "$@" --name wordlinator wordlinator:latest
|
||||
echo "Running debug mode..."
|
||||
docker run -d --rm -p 8050:8050 -e DASH_DEBUG=true -e DB_PORT -e DB_HOST -e DB_PASS "$@" --name wordlinator wordlinator:latest
|
||||
else
|
||||
docker run -d --rm -p 8050:8050 -e DB_PORT -e DB_HOST -e DB_PASS "$@" --name wordlinator wordlinator:latest
|
||||
fi
|
||||
|
||||
@@ -85,140 +85,155 @@ class WordleDb:
|
||||
return list(User.select())
|
||||
|
||||
def get_users_by_round(self, round_no=None, round_id=None):
|
||||
query = (
|
||||
User.select(User, Player.user_id, Game.game)
|
||||
.join(Player, on=(Player.user_id == User.user_id))
|
||||
.join(Game, on=(Game.game_id == Player.game_id))
|
||||
)
|
||||
if round_no:
|
||||
query = query.filter(Game.game == round_no)
|
||||
elif round_id:
|
||||
query = query.filter(Game.game_id == round_id)
|
||||
return list(query)
|
||||
with db.atomic():
|
||||
query = (
|
||||
User.select(User, Player.user_id, Game.game)
|
||||
.join(Player, on=(Player.user_id == User.user_id))
|
||||
.join(Game, on=(Game.game_id == Player.game_id))
|
||||
)
|
||||
if round_no:
|
||||
query = query.filter(Game.game == round_no)
|
||||
elif round_id:
|
||||
query = query.filter(Game.game_id == round_id)
|
||||
return list(query)
|
||||
|
||||
def get_user_id(self, username):
|
||||
user = self.get_user(username)
|
||||
return user.twitter_id if user else None
|
||||
with db.atomic():
|
||||
user = self.get_user(username)
|
||||
return user.twitter_id if user else None
|
||||
|
||||
def add_user(self, username, user_id, check_twitter=True):
|
||||
return User.create(
|
||||
username=username, twitter_id=user_id, check_twitter=check_twitter
|
||||
)
|
||||
with db.atomic():
|
||||
return User.create(
|
||||
username=username, twitter_id=user_id, check_twitter=check_twitter
|
||||
)
|
||||
|
||||
def get_rounds(self):
|
||||
return list(sorted(Game.select(), key=lambda d: d.start_date))
|
||||
with db.atomic():
|
||||
return list(sorted(Game.select(), key=lambda d: d.start_date))
|
||||
|
||||
def get_or_create_round(self, round_no, start_date=None):
|
||||
try:
|
||||
return Game.get(Game.game == round_no)
|
||||
except peewee.DoesNotExist:
|
||||
start_date = (
|
||||
start_date or wordlinator.utils.WORDLE_GOLF_ROUND_DATES[round_no - 1]
|
||||
)
|
||||
return Game.create(game=round_no, start_date=start_date)
|
||||
with db.atomic():
|
||||
try:
|
||||
return Game.get(Game.game == round_no)
|
||||
except peewee.DoesNotExist:
|
||||
start_date = (
|
||||
start_date
|
||||
or wordlinator.utils.WORDLE_GOLF_ROUND_DATES[round_no - 1]
|
||||
)
|
||||
return Game.create(game=round_no, start_date=start_date)
|
||||
|
||||
def get_or_create_hole(self, round_no, hole_no):
|
||||
round = self.get_or_create_round(round_no)
|
||||
with db.atomic():
|
||||
round = self.get_or_create_round(round_no)
|
||||
|
||||
try:
|
||||
return Hole.get(Hole.hole == hole_no, Hole.game_id == round.game_id)
|
||||
except peewee.DoesNotExist:
|
||||
return Hole.create(hole=hole_no, game_id=round.game_id)
|
||||
try:
|
||||
return Hole.get(Hole.hole == hole_no, Hole.game_id == round.game_id)
|
||||
except peewee.DoesNotExist:
|
||||
return Hole.create(hole=hole_no, game_id=round.game_id)
|
||||
|
||||
def get_holes(self, round_no, ensure_all=False):
|
||||
round = self.get_or_create_round(round_no)
|
||||
if ensure_all:
|
||||
self.create_round_holes(round_no)
|
||||
return list(Hole.select().filter(game_id=round.game_id))
|
||||
with db.atomic():
|
||||
round = self.get_or_create_round(round_no)
|
||||
if ensure_all:
|
||||
self.create_round_holes(round_no)
|
||||
return list(Hole.select().filter(game_id=round.game_id))
|
||||
|
||||
def create_round_holes(self, round_no):
|
||||
for hole_no in range(1, 19):
|
||||
self.get_or_create_hole(round_no, hole_no)
|
||||
with db.atomic():
|
||||
for hole_no in range(1, 19):
|
||||
self.get_or_create_hole(round_no, hole_no)
|
||||
|
||||
def get_or_create_player_round(self, user_id, game_id):
|
||||
try:
|
||||
return Player.get(Player.user_id == user_id, Player.game_id == game_id)
|
||||
except peewee.DoesNotExist:
|
||||
return Player.create(user_id=user_id, game_id=game_id)
|
||||
with db.atomic():
|
||||
try:
|
||||
return Player.get(Player.user_id == user_id, Player.game_id == game_id)
|
||||
except peewee.DoesNotExist:
|
||||
return Player.create(user_id=user_id, game_id=game_id)
|
||||
|
||||
def add_user_to_round(self, username, round_no):
|
||||
user = self.get_user(username)
|
||||
if not user:
|
||||
raise ValueError(f"No user found with username {username}")
|
||||
round = self.get_or_create_round(round_no)
|
||||
return self.get_or_create_player_round(user.user_id, round.game_id)
|
||||
with db.atomic():
|
||||
user = self.get_user(username)
|
||||
if not user:
|
||||
raise ValueError(f"No user found with username {username}")
|
||||
round = self.get_or_create_round(round_no)
|
||||
return self.get_or_create_player_round(user.user_id, round.game_id)
|
||||
|
||||
def remove_user_from_round(self, username, round_no):
|
||||
user = self.get_user(username)
|
||||
if not user:
|
||||
raise ValueError(f"No user found with username {username}")
|
||||
round = self.get_or_create_round(round_no)
|
||||
try:
|
||||
player = Player.get(user_id=user.user_id, game_id=round.game_id)
|
||||
player.delete_instance()
|
||||
except peewee.DoesNotExist:
|
||||
return
|
||||
with db.atomic():
|
||||
user = self.get_user(username)
|
||||
if not user:
|
||||
raise ValueError(f"No user found with username {username}")
|
||||
round = self.get_or_create_round(round_no)
|
||||
try:
|
||||
player = Player.get(user_id=user.user_id, game_id=round.game_id)
|
||||
player.delete_instance()
|
||||
except peewee.DoesNotExist:
|
||||
return
|
||||
|
||||
def copy_players_from_round(self, from_round_no, to_round_no):
|
||||
to_round = self.get_or_create_round(to_round_no)
|
||||
with db.atomic():
|
||||
to_round = self.get_or_create_round(to_round_no)
|
||||
|
||||
for user in self.get_users_by_round(from_round_no):
|
||||
self.get_or_create_player_round(user.user_id, to_round.game_id)
|
||||
for user in self.get_users_by_round(from_round_no):
|
||||
self.get_or_create_player_round(user.user_id, to_round.game_id)
|
||||
|
||||
def add_score(self, username, game, hole, score):
|
||||
if not score:
|
||||
return
|
||||
with db.atomic():
|
||||
if not score:
|
||||
return
|
||||
|
||||
user = self.get_user(username)
|
||||
if not user:
|
||||
raise ValueError(f'No Such User "{username}"')
|
||||
hole = self.get_or_create_hole(game, hole)
|
||||
user = self.get_user(username)
|
||||
if not user:
|
||||
raise ValueError(f'No Such User "{username}"')
|
||||
hole = self.get_or_create_hole(game, hole)
|
||||
|
||||
try:
|
||||
score_obj = Score.get(
|
||||
Score.user_id == user.user_id,
|
||||
Score.game_id == hole.game_id,
|
||||
Score.hole_id == hole.hole_id,
|
||||
)
|
||||
score_obj.score = score
|
||||
score_obj.save()
|
||||
return score_obj
|
||||
except peewee.DoesNotExist:
|
||||
return Score.create(
|
||||
score=score,
|
||||
user_id=user.user_id,
|
||||
game_id=hole.game_id,
|
||||
hole_id=hole.hole_id,
|
||||
)
|
||||
try:
|
||||
score_obj = Score.get(
|
||||
Score.user_id == user.user_id,
|
||||
Score.game_id == hole.game_id,
|
||||
Score.hole_id == hole.hole_id,
|
||||
)
|
||||
score_obj.score = score
|
||||
score_obj.save()
|
||||
return score_obj
|
||||
except peewee.DoesNotExist:
|
||||
return Score.create(
|
||||
score=score,
|
||||
user_id=user.user_id,
|
||||
game_id=hole.game_id,
|
||||
hole_id=hole.hole_id,
|
||||
)
|
||||
|
||||
def get_scores(self, round_no=None, round_id=None):
|
||||
if round_no:
|
||||
round = self.get_or_create_round(round_no)
|
||||
elif round_id:
|
||||
round = Game.get_by_id(round_id)
|
||||
else:
|
||||
raise ValueError("Must provide Round Number or Round ID")
|
||||
res = (
|
||||
Score.select(
|
||||
Score,
|
||||
Hole.hole,
|
||||
User.username,
|
||||
Player.game_id,
|
||||
with db.atomic():
|
||||
if round_no:
|
||||
round = self.get_or_create_round(round_no)
|
||||
elif round_id:
|
||||
round = Game.get_by_id(round_id)
|
||||
else:
|
||||
raise ValueError("Must provide Round Number or Round ID")
|
||||
res = (
|
||||
Score.select(
|
||||
Score,
|
||||
Hole.hole,
|
||||
User.username,
|
||||
Player.game_id,
|
||||
)
|
||||
.join(Player, on=(Score.user_id == Player.user_id))
|
||||
.switch(Score)
|
||||
.join(Hole, on=(Score.hole_id == Hole.hole_id))
|
||||
.switch(Score)
|
||||
.join(User, on=(Score.user_id == User.user_id))
|
||||
.filter(Player.game_id == round.game_id)
|
||||
.filter(Score.game_id == round.game_id)
|
||||
)
|
||||
.join(Player, on=(Score.user_id == Player.user_id))
|
||||
.switch(Score)
|
||||
.join(Hole, on=(Score.hole_id == Hole.hole_id))
|
||||
.switch(Score)
|
||||
.join(User, on=(Score.user_id == User.user_id))
|
||||
.filter(Player.game_id == round.game_id)
|
||||
.filter(Score.game_id == round.game_id)
|
||||
)
|
||||
return list(res)
|
||||
return list(res) if res else []
|
||||
|
||||
def bulk_insert_scores(self, scores: typing.List[typing.Dict]):
|
||||
with db.atomic():
|
||||
with db.atomic() as txn:
|
||||
for batch in peewee.chunked(scores, 50):
|
||||
Score.insert_many(batch).execute()
|
||||
Score.insert_many(batch).execute(txn)
|
||||
|
||||
def bulk_update_scores(self, scores: typing.List[Score]):
|
||||
with db.atomic():
|
||||
@@ -226,24 +241,25 @@ class WordleDb:
|
||||
score.save()
|
||||
|
||||
def get_users_without_score(self, round_no, hole_no, tweetable=True):
|
||||
hole = self.get_or_create_hole(round_no, hole_no)
|
||||
# Find users who *have* played in this round,
|
||||
# but have no score on the current hole
|
||||
query_str = """SELECT u.username, player.game_id
|
||||
FROM user_tbl u
|
||||
JOIN player ON player.user_id = u.user_id
|
||||
WHERE (
|
||||
player.game_id = {}
|
||||
) AND NOT EXISTS (
|
||||
SELECT FROM score WHERE score.user_id = u.user_id AND score.hole_id = {}
|
||||
)
|
||||
""".format(
|
||||
hole.game_id, hole.hole_id
|
||||
)
|
||||
with db.atomic() as txn:
|
||||
hole = self.get_or_create_hole(round_no, hole_no)
|
||||
# Find users who *have* played in this round,
|
||||
# but have no score on the current hole
|
||||
query_str = """SELECT u.username, player.game_id
|
||||
FROM user_tbl u
|
||||
JOIN player ON player.user_id = u.user_id
|
||||
WHERE (
|
||||
player.game_id = {}
|
||||
) AND NOT EXISTS (
|
||||
SELECT FROM score WHERE score.user_id = u.user_id AND score.hole_id = {}
|
||||
)
|
||||
""".format(
|
||||
hole.game_id, hole.hole_id
|
||||
)
|
||||
|
||||
if tweetable:
|
||||
# Restrict to users who post scores on twitter
|
||||
query_str += " AND u.check_twitter = true"
|
||||
if tweetable:
|
||||
# Restrict to users who post scores on twitter
|
||||
query_str += " AND u.check_twitter = true"
|
||||
|
||||
res = db.execute_sql(query_str)
|
||||
return [r[0] for r in res]
|
||||
res = txn.execute_sql(query_str)
|
||||
return [r[0] for r in res]
|
||||
|
||||
@@ -16,7 +16,7 @@ import wordlinator.utils
|
||||
import wordlinator.utils.scores
|
||||
import wordlinator.utils.web
|
||||
|
||||
TTL_TIME = 10 if os.getenv("DEBUG") else 600
|
||||
TTL_TIME = 30 if os.getenv("DEBUG") else 600
|
||||
LEADERBOARD_COUNT = 20
|
||||
|
||||
###################
|
||||
@@ -39,7 +39,7 @@ long_callback_manager = dash.long_callback.DiskcacheLongCallbackManager(
|
||||
)
|
||||
|
||||
|
||||
@functools.lru_cache()
|
||||
@functools.lru_cache(maxsize=1)
|
||||
def _games_from_db(ttl_hash=None):
|
||||
return db.WordleDb().get_rounds()
|
||||
|
||||
@@ -48,7 +48,7 @@ def games_from_db():
|
||||
return _games_from_db()
|
||||
|
||||
|
||||
@functools.lru_cache()
|
||||
@functools.lru_cache(maxsize=1)
|
||||
def _wordle_today(ttl_hash=None):
|
||||
today = wordlinator.utils.get_wordle_today()
|
||||
if today.golf_hole:
|
||||
@@ -64,22 +64,17 @@ def wordle_today():
|
||||
return _wordle_today(get_ttl_hash())
|
||||
|
||||
|
||||
@functools.lru_cache()
|
||||
@functools.lru_cache(maxsize=3)
|
||||
def _scores_from_db(round_id, ttl_hash=None):
|
||||
return db.WordleDb().get_scores(round_id=round_id)
|
||||
|
||||
|
||||
@functools.lru_cache()
|
||||
def _players_from_db(round_id, ttl_hash=None):
|
||||
return db.WordleDb().get_users_by_round(round_id=round_id)
|
||||
wordle_db = db.WordleDb()
|
||||
scores = wordle_db.get_scores(round_id=round_id)
|
||||
users = wordle_db.get_users_by_round(round_id=round_id)
|
||||
usernames = [u.username for u in users]
|
||||
return wordlinator.utils.scores.ScoreMatrix(scores, usernames=usernames)
|
||||
|
||||
|
||||
def scores_from_db(round_id):
|
||||
users = _players_from_db(round_id)
|
||||
usernames = [u.username for u in users]
|
||||
return wordlinator.utils.scores.ScoreMatrix(
|
||||
_scores_from_db(round_id, get_ttl_hash()), usernames=usernames
|
||||
)
|
||||
return _scores_from_db(round_id)
|
||||
|
||||
|
||||
#######################
|
||||
@@ -276,25 +271,37 @@ app.layout = dash.html.Div(
|
||||
f"Leaderboard - Top {LEADERBOARD_COUNT}",
|
||||
style={"textAlign": "center"},
|
||||
),
|
||||
dash.html.Div("Loading...", id="leaderboard"),
|
||||
dash.dcc.Loading(
|
||||
id="leaderboard-loading",
|
||||
children=dash.html.Div("Loading...", id="leaderboard"),
|
||||
),
|
||||
]
|
||||
),
|
||||
dash.html.Div(
|
||||
[
|
||||
dash.html.H2("User Scores", style={"textAlign": "center"}),
|
||||
dash.html.Div("Loading...", id="user-scores"),
|
||||
dash.dcc.Loading(
|
||||
id="user-scores-loading",
|
||||
children=dash.html.Div("Loading...", id="user-scores"),
|
||||
),
|
||||
]
|
||||
),
|
||||
dash.html.Div(
|
||||
[
|
||||
dash.html.H2("Score Graph", style={"textAlign": "center"}),
|
||||
dash.html.Div("Loading...", id="stats-graph"),
|
||||
dash.dcc.Loading(
|
||||
id="stats-graph-loading",
|
||||
children=dash.html.Div("Loading...", id="stats-graph"),
|
||||
),
|
||||
]
|
||||
),
|
||||
dash.html.Div(
|
||||
[
|
||||
dash.html.H2("Daily Stats", style={"textAlign": "center"}),
|
||||
dash.html.Div("Loading...", id="daily-stats"),
|
||||
dash.dcc.Loading(
|
||||
id="daily-stats-loading",
|
||||
children=dash.html.Div("Loading...", id="daily-stats"),
|
||||
),
|
||||
]
|
||||
),
|
||||
]
|
||||
|
||||
Reference in New Issue
Block a user