Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add User Status Log #372

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 34 additions & 0 deletions ambuda/models/auth.py
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,40 @@ def __repr__(self):
return f"<Role({self.id}, {self.name!r})>"


class UserStatusLog(AmbudaUserMixin, Base):
Copy link
Contributor

Choose a reason for hiding this comment

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

remove UserMixin here as this is not a user

"""Tracks changes to user statuses."""

__tablename__ = "user_status_log"

#: Primary key.
id = pk()

#: The user whose status was changed.
Copy link
Contributor

Choose a reason for hiding this comment

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

state changes should also indicate the user who made the change (or perhaps ambuda-bot if there isn't one)

user_id = Column(Integer, ForeignKey("users.id"), nullable=False)

#: Timestamp at which this status change occured.
timestamp = Column(DateTime, default=datetime.utcnow, nullable=False)

#: Describes the status change that occurred.
change_description = Column(String, nullable=False)
Copy link
Contributor

Choose a reason for hiding this comment

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

description since the change_ is implicit by dint of this being a log


#: When should this status change expire/revert, defaults to never.
expiry = Column(DateTime, default=None, nullable=True)
Copy link
Contributor

Choose a reason for hiding this comment

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

Since the semantics here are all in change_description which is unstructured text, there's nothing actionable we can do even if we know expiry. This seems like something we could fold into change_description itself


@property
Copy link
Contributor

Choose a reason for hiding this comment

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

Let's wait on these until we know how to use them

def is_expired(self) -> bool:
"""Check if the action has expired."""
return self.expiry and self.expiry < datetime.utcnow()

@property
def is_temporary(self) -> bool:
"""
Check if the action has an expiry and will be reverted
in the future.
"""
return self.expiry


class UserRoles(Base):

"""Secondary table for users and roles."""
Expand Down
40 changes: 40 additions & 0 deletions migrations/versions/e4c982111325_create_user_status_log_table.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
"""Create user status log table

Revision ID: e4c982111325
Revises: 99379162619e
Create Date: 2022-09-24 19:04:16.240264

"""
from alembic import op
import sqlalchemy as sa
from sqlalchemy import orm


# revision identifiers, used by Alembic.
revision = 'e4c982111325'
down_revision = '99379162619e'
branch_labels = None
depends_on = None

def upgrade() -> None:
# ### commands auto generated by Alembic - please adjust! ###
op.create_table(
"user_status_log",
sa.Column("id", sa.Integer(), autoincrement=True, nullable=False),
sa.Column("user_id", sa.Integer(), nullable=False),
sa.Column("timestamp", sa.DateTime(), nullable=False),
sa.Column("change_description", sa.String(), nullable=False),
sa.Column("expiry", sa.DateTime(), nullable=True),
sa.ForeignKeyConstraint(
["user_id"],
["users.id"],
),
sa.PrimaryKeyConstraint("id"),
)
# ### end Alembic commands ###


def downgrade() -> None:
# ### commands auto generated by Alembic - please adjust! ###
op.drop_table("user_status_log")
# ### end Alembic commands ###
52 changes: 52 additions & 0 deletions test/ambuda/test_models.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
from datetime import datetime
import ambuda.database as db
from ambuda.queries import get_session

Expand Down Expand Up @@ -85,3 +86,54 @@ def test_token__set_and_check_token(client):
assert not row.check_token("password2")

_cleanup(session, row)


def test_user_status_log__permanent(client):
session = get_session()
row = db.UserStatusLog(
user_id=1,
change_description="action_one",
)

session.add(row)
session.commit()

assert not row.is_expired
assert not row.is_temporary

_cleanup(session, row)


def test_user_status_log__temporary_unexpired(client):
session = get_session()
row = db.UserStatusLog(
user_id=1,
change_description="action_one",
expiry=datetime.fromisoformat('2055-09-22')
)

session.add(row)
session.commit()

assert not row.is_expired
assert row.is_temporary

_cleanup(session, row)


def test_user_status_log__temporary_expired(client):
session = get_session()
row = db.UserStatusLog(
user_id=1,
change_description="action_one",
expiry=datetime.fromisoformat('2020-09-22')
)

session.add(row)
session.commit()

assert row.is_expired
assert row.is_temporary

_cleanup(session, row)