From 683977b7a3ae605be8109392920d076220a6ed64 Mon Sep 17 00:00:00 2001 From: Mircea Filip Lungu Date: Wed, 28 Jan 2026 08:56:52 +0100 Subject: [PATCH 1/2] Add backend-maintained daily streak Add daily_streak column to user table and maintain it in update_last_seen_if_needed(). Consecutive days increment the streak; gaps reset it to 1. New GET /daily_streak endpoint returns the stored value. Co-Authored-By: Claude Opus 4.5 --- tools/migrations/26-01-27--add_daily_streak.sql | 1 + zeeguu/api/endpoints/__init__.py | 1 + zeeguu/api/endpoints/daily_streak.py | 14 ++++++++++++++ zeeguu/core/model/user.py | 9 +++++++++ 4 files changed, 25 insertions(+) create mode 100644 tools/migrations/26-01-27--add_daily_streak.sql create mode 100644 zeeguu/api/endpoints/daily_streak.py diff --git a/tools/migrations/26-01-27--add_daily_streak.sql b/tools/migrations/26-01-27--add_daily_streak.sql new file mode 100644 index 000000000..efde72fb0 --- /dev/null +++ b/tools/migrations/26-01-27--add_daily_streak.sql @@ -0,0 +1 @@ +ALTER TABLE user ADD COLUMN daily_streak INT NOT NULL DEFAULT 0; diff --git a/zeeguu/api/endpoints/__init__.py b/zeeguu/api/endpoints/__init__.py index 6da2a965c..2c9ebb382 100644 --- a/zeeguu/api/endpoints/__init__.py +++ b/zeeguu/api/endpoints/__init__.py @@ -47,3 +47,4 @@ from . import crawl_stats from . import user_stats from . import session_history +from . import daily_streak diff --git a/zeeguu/api/endpoints/daily_streak.py b/zeeguu/api/endpoints/daily_streak.py new file mode 100644 index 000000000..117986d8a --- /dev/null +++ b/zeeguu/api/endpoints/daily_streak.py @@ -0,0 +1,14 @@ +import flask + +from zeeguu.api.utils.json_result import json_result +from zeeguu.api.utils.route_wrappers import cross_domain, requires_session +from . import api +from ...core.model import User + + +@api.route("/daily_streak", methods=["GET"]) +@cross_domain +@requires_session +def get_daily_streak(): + user = User.find_by_id(flask.g.user_id) + return json_result({"daily_streak": user.daily_streak or 0}) diff --git a/zeeguu/core/model/user.py b/zeeguu/core/model/user.py index ba6c916aa..1c433331f 100644 --- a/zeeguu/core/model/user.py +++ b/zeeguu/core/model/user.py @@ -55,6 +55,7 @@ class User(db.Model): created_at = db.Column(db.DateTime, nullable=True) last_seen = db.Column(db.DateTime, nullable=True) creation_platform = db.Column(db.SmallInteger, nullable=True) + daily_streak = db.Column(db.Integer, default=0) def __init__( self, @@ -353,11 +354,19 @@ def active_during_recent(self, days: int = 30): def update_last_seen_if_needed(self, session=None): """ Update last_seen timestamp, but only once per day to minimize database writes. + Also maintains the daily_streak counter. """ now = datetime.datetime.now() # Only update if last_seen is None or it's a different day if not self.last_seen or self.last_seen.date() < now.date(): + if not self.last_seen: + self.daily_streak = 1 + elif self.last_seen.date() == now.date() - datetime.timedelta(days=1): + self.daily_streak = (self.daily_streak or 0) + 1 + else: + self.daily_streak = 1 + self.last_seen = now if session: session.add(self) From b71d61157d8cc9a48b1362f4ce1f158c5cbb3f20 Mon Sep 17 00:00:00 2001 From: Mircea Filip Lungu Date: Wed, 28 Jan 2026 13:12:37 +0100 Subject: [PATCH 2/2] Add exercises_completed_this_week endpoint Add method and endpoint to count total exercises completed this week, replacing the previous distinct-words-practiced metric. Co-Authored-By: Claude Opus 4.5 --- zeeguu/api/endpoints/bookmarks_and_words.py | 12 ++++++++++ zeeguu/core/model/user.py | 25 +++++++++++++++++++++ 2 files changed, 37 insertions(+) diff --git a/zeeguu/api/endpoints/bookmarks_and_words.py b/zeeguu/api/endpoints/bookmarks_and_words.py index f4f260982..cfab6d605 100644 --- a/zeeguu/api/endpoints/bookmarks_and_words.py +++ b/zeeguu/api/endpoints/bookmarks_and_words.py @@ -374,6 +374,18 @@ def practiced_user_word_count_this_week(): return json_result(count) +@api.route("/exercises_completed_this_week", methods=["GET"]) +@cross_domain +@requires_session +def exercises_completed_this_week(): + """ + Returns the total number of exercises the user has completed this week. + """ + user = User.find_by_id(flask.g.user_id) + count = user.exercises_completed_this_week() + return json_result(count) + + @api.route("/past_contexts/", methods=["GET"]) @cross_domain @requires_session diff --git a/zeeguu/core/model/user.py b/zeeguu/core/model/user.py index 1c433331f..1c9debc8b 100644 --- a/zeeguu/core/model/user.py +++ b/zeeguu/core/model/user.py @@ -336,6 +336,31 @@ def practiced_user_words_count_this_week(self): return result + def exercises_completed_this_week(self): + """ + Returns the total number of exercises completed this week. + """ + from zeeguu.core.model.user_word import UserWord + from zeeguu.core.model.exercise import Exercise + from zeeguu.core.model.phrase import Phrase + from zeeguu.core.model.meaning import Meaning + + today = datetime.date.today() + start_of_week = today - datetime.timedelta(days=today.weekday()) + + result = ( + db.session.query(Exercise) + .join(UserWord, Exercise.user_word_id == UserWord.id) + .join(Meaning, UserWord.meaning_id == Meaning.id) + .join(Phrase, Meaning.origin_id == Phrase.id) + .filter(UserWord.user_id == self.id) + .filter(Exercise.time >= start_of_week) + .filter(Phrase.language_id == self.learned_language_id) + .count() + ) + + return result + def liked_articles(self): from zeeguu.core.model.user_article import UserArticle