diff --git a/backend/app/alembic/versions/fe56fa70289e_add_created_at_to_user_and_item.py b/backend/app/alembic/versions/fe56fa70289e_add_created_at_to_user_and_item.py new file mode 100644 index 0000000000..3e15754825 --- /dev/null +++ b/backend/app/alembic/versions/fe56fa70289e_add_created_at_to_user_and_item.py @@ -0,0 +1,31 @@ +"""Add created_at to User and Item + +Revision ID: fe56fa70289e +Revises: 1a31ce608336 +Create Date: 2026-01-23 15:50:37.171462 + +""" +from alembic import op +import sqlalchemy as sa +import sqlmodel.sql.sqltypes + + +# revision identifiers, used by Alembic. +revision = 'fe56fa70289e' +down_revision = '1a31ce608336' +branch_labels = None +depends_on = None + + +def upgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.add_column('item', sa.Column('created_at', sa.DateTime(timezone=True), nullable=True)) + op.add_column('user', sa.Column('created_at', sa.DateTime(timezone=True), nullable=True)) + # ### end Alembic commands ### + + +def downgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.drop_column('user', 'created_at') + op.drop_column('item', 'created_at') + # ### end Alembic commands ### diff --git a/backend/app/api/routes/items.py b/backend/app/api/routes/items.py index 3d4e4e07b5..2b2ce57772 100644 --- a/backend/app/api/routes/items.py +++ b/backend/app/api/routes/items.py @@ -21,7 +21,9 @@ def read_items( if current_user.is_superuser: count_statement = select(func.count()).select_from(Item) count = session.exec(count_statement).one() - statement = select(Item).offset(skip).limit(limit) + statement = ( + select(Item).order_by(Item.created_at.desc()).offset(skip).limit(limit) + ) items = session.exec(statement).all() else: count_statement = ( @@ -33,6 +35,7 @@ def read_items( statement = ( select(Item) .where(Item.owner_id == current_user.id) + .order_by(Item.created_at.desc()) .offset(skip) .limit(limit) ) diff --git a/backend/app/api/routes/users.py b/backend/app/api/routes/users.py index 68f87ee253..61727949c8 100644 --- a/backend/app/api/routes/users.py +++ b/backend/app/api/routes/users.py @@ -42,7 +42,7 @@ def read_users(session: SessionDep, skip: int = 0, limit: int = 100) -> Any: count_statement = select(func.count()).select_from(User) count = session.exec(count_statement).one() - statement = select(User).offset(skip).limit(limit) + statement = select(User).order_by(User.created_at.desc()).offset(skip).limit(limit) users = session.exec(statement).all() return UsersPublic(data=users, count=count) diff --git a/backend/app/models.py b/backend/app/models.py index 2d060ba0b4..b5132e0e2c 100644 --- a/backend/app/models.py +++ b/backend/app/models.py @@ -1,9 +1,15 @@ import uuid +from datetime import datetime, timezone from pydantic import EmailStr +from sqlalchemy import DateTime from sqlmodel import Field, Relationship, SQLModel +def get_datetime_utc() -> datetime: + return datetime.now(timezone.utc) + + # Shared properties class UserBase(SQLModel): email: EmailStr = Field(unique=True, index=True, max_length=255) @@ -43,12 +49,17 @@ class UpdatePassword(SQLModel): class User(UserBase, table=True): id: uuid.UUID = Field(default_factory=uuid.uuid4, primary_key=True) hashed_password: str + created_at: datetime | None = Field( + default_factory=get_datetime_utc, + sa_type=DateTime(timezone=True), # type: ignore + ) items: list["Item"] = Relationship(back_populates="owner", cascade_delete=True) # Properties to return via API, id is always required class UserPublic(UserBase): id: uuid.UUID + created_at: datetime | None = None class UsersPublic(SQLModel): @@ -75,6 +86,10 @@ class ItemUpdate(ItemBase): # Database model, database table inferred from class name class Item(ItemBase, table=True): id: uuid.UUID = Field(default_factory=uuid.uuid4, primary_key=True) + created_at: datetime | None = Field( + default_factory=get_datetime_utc, + sa_type=DateTime(timezone=True), # type: ignore + ) owner_id: uuid.UUID = Field( foreign_key="user.id", nullable=False, ondelete="CASCADE" ) @@ -85,6 +100,7 @@ class Item(ItemBase, table=True): class ItemPublic(ItemBase): id: uuid.UUID owner_id: uuid.UUID + created_at: datetime | None = None class ItemsPublic(SQLModel): diff --git a/frontend/src/client/schemas.gen.ts b/frontend/src/client/schemas.gen.ts index fba9429f53..5c0c9c4a4e 100644 --- a/frontend/src/client/schemas.gen.ts +++ b/frontend/src/client/schemas.gen.ts @@ -126,6 +126,18 @@ export const ItemPublicSchema = { type: 'string', format: 'uuid', title: 'Owner Id' + }, + created_at: { + anyOf: [ + { + type: 'string', + format: 'date-time' + }, + { + type: 'null' + } + ], + title: 'Created At' } }, type: 'object', @@ -352,6 +364,18 @@ export const UserPublicSchema = { type: 'string', format: 'uuid', title: 'Id' + }, + created_at: { + anyOf: [ + { + type: 'string', + format: 'date-time' + }, + { + type: 'null' + } + ], + title: 'Created At' } }, type: 'object', diff --git a/frontend/src/client/types.gen.ts b/frontend/src/client/types.gen.ts index e5cf34c34c..e62b56cad3 100644 --- a/frontend/src/client/types.gen.ts +++ b/frontend/src/client/types.gen.ts @@ -23,6 +23,7 @@ export type ItemPublic = { description?: (string | null); id: string; owner_id: string; + created_at?: (string | null); }; export type ItemsPublic = { @@ -75,6 +76,7 @@ export type UserPublic = { is_superuser?: boolean; full_name?: (string | null); id: string; + created_at?: (string | null); }; export type UserRegister = {