diff --git a/tahrir_api/dbapi.py b/tahrir_api/dbapi.py index bc10554..d2fcb74 100644 --- a/tahrir_api/dbapi.py +++ b/tahrir_api/dbapi.py @@ -5,13 +5,15 @@ from collections import OrderedDict from datetime import datetime, timedelta, timezone -from sqlalchemy import and_, func, not_, text +from sqlalchemy import and_, func, not_, select, text +from sqlalchemy.exc import NoResultFound from tahrir_messages import BadgeAwardV1, PersonLoginFirstV1, PersonRankAdvanceV1 from .model import ( Assertion, Authorization, Badge, + CurrentValue, Invitation, Issuer, Milestone, @@ -918,6 +920,62 @@ def add_assertion(self, badge_id, person_email, issued_on, issued_for=None): return False + def get_current_value(self, badge_id, person_email): + """ + Return the current value for the given badge and the given person's email + + :type badge_id: str + :param badge_id: The ID of the badge to query + :type person_email: str + :param person_email: The email of the person to query + """ + if not self.badge_exists(badge_id): + raise ValueError(f"No such badge {badge_id!r}") + + person = self.get_person(person_email=person_email) + if person is None: + return None + + query = select(CurrentValue.value).where( + CurrentValue.badge_id == badge_id, + CurrentValue.person_id == person.id, + ) + try: + return self.session.scalar(query).one() + except NoResultFound: + return None + + def set_current_value(self, badge_id, person_email, value): + """Set the current value for the given badge and the given person's email + + :type badge_id: str + :param badge_id: The ID of the badge to query + :type person_email: str + :param person_email: The email of the person to query + :type value: int + :param value: The value to store + """ + if not self.badge_exists(badge_id): + raise ValueError(f"No such badge {badge_id!r}") + + person = self.get_person(person_email=person_email) + if person is None: + self.add_person(email=person_email) + person = self.get_person(person_email=person_email) + + query = select(CurrentValue).where( + CurrentValue.badge_id == badge_id, + CurrentValue.person_id == person.id, + ) + try: + current_value = self.session.scalar(query).one() + except NoResultFound: + current_value = CurrentValue(badge_id=badge_id, value=value) + person.current_values.append(current_value) + self.session.flush() + else: + current_value.value = value + def _adjust_ranks(self, person, old_rank): """Given a person model object and the 'old' rank of that person, adjust the ranks of all persons between the 'old' rank and the present diff --git a/tahrir_api/migrations/versions/51261da641fb_currentvalue.py b/tahrir_api/migrations/versions/51261da641fb_currentvalue.py new file mode 100644 index 0000000..639f624 --- /dev/null +++ b/tahrir_api/migrations/versions/51261da641fb_currentvalue.py @@ -0,0 +1,37 @@ +"""CurrentValue + +Revision ID: 51261da641fb +Revises: 3d3fb9e59e7b +Create Date: 2024-07-23 15:27:48.303528 +""" + +import sqlalchemy as sa +from alembic import op + + +# revision identifiers, used by Alembic. +revision = "51261da641fb" +down_revision = "3d3fb9e59e7b" +branch_labels = None +depends_on = None + + +def upgrade(): + op.create_table( + "current_values", + sa.Column("badge_id", sa.Unicode(length=128), nullable=False), + sa.Column("person_id", sa.Integer(), nullable=False), + sa.Column("value", sa.Integer(), nullable=False), + sa.Column("last_update", sa.DateTime(), nullable=False), + sa.ForeignKeyConstraint( + ["badge_id"], ["badges.id"], name=op.f("fk_current_values_badge_id_badges") + ), + sa.ForeignKeyConstraint( + ["person_id"], ["persons.id"], name=op.f("fk_current_values_person_id_persons") + ), + sa.PrimaryKeyConstraint("badge_id", "person_id", name=op.f("pk_current_values")), + ) + + +def downgrade(): + op.drop_table("current_values") diff --git a/tahrir_api/model.py b/tahrir_api/model.py index a3e0020..20b03a0 100644 --- a/tahrir_api/model.py +++ b/tahrir_api/model.py @@ -52,6 +52,7 @@ class Badge(DeclarativeBase): authorizations = relationship("Authorization", backref="badge") assertions = relationship("Assertion", backref="badge") invitations = relationship("Invitation", backref="badge") + current_values = relationship("CurrentValue", back_populates="badge") created_on = Column(DateTime, nullable=False, default=datetime.datetime.now) tags = Column(Unicode(128)) @@ -144,6 +145,7 @@ class Person(DeclarativeBase): authorizations = relationship("Authorization", backref="person") assertions = relationship("Assertion", backref="person") invitations = relationship("Invitation", backref="person") + current_values = relationship("CurrentValue", back_populates="person") nickname = Column(Unicode(128), unique=True) website = Column(Unicode(128)) bio = Column(Unicode(140)) @@ -217,6 +219,22 @@ class Authorization(DeclarativeBase): person_id = Column(Integer, ForeignKey("persons.id"), nullable=False) +class CurrentValue(DeclarativeBase): + """The current "value" that a user has for a badge they don't yet have . + + Most of the time this is going to be a message count. + """ + + __tablename__ = "current_values" + badge_id = Column(Unicode(128), ForeignKey("badges.id"), primary_key=True, nullable=False) + person_id = Column(Integer, ForeignKey("persons.id"), primary_key=True, nullable=False) + value = Column(Integer, nullable=False) + last_update = Column(DateTime, nullable=False) + + badge = relationship("Badge", back_populates="current_values") + person = relationship("Person", back_populates="current_values") + + def recipient_default(context): person_id = context.current_parameters["person_id"] salt = context.current_parameters["salt"]