From 5cb71c920fe3269f1cf607b1953da902b17a64ad Mon Sep 17 00:00:00 2001 From: "Brandon T. Kowalski" Date: Sat, 17 Jan 2026 23:25:02 -0500 Subject: [PATCH 1/2] Added `updated_after` query parameter for normal and smart collections and migration to add corresponding indexes to each table. --- .../0065_collections_updated_at_idx.py | 33 +++++++++++++++++ backend/endpoints/collections.py | 36 ++++++++++++++----- .../handler/database/collections_handler.py | 8 +++++ 3 files changed, 69 insertions(+), 8 deletions(-) create mode 100644 backend/alembic/versions/0065_collections_updated_at_idx.py diff --git a/backend/alembic/versions/0065_collections_updated_at_idx.py b/backend/alembic/versions/0065_collections_updated_at_idx.py new file mode 100644 index 000000000..d889e7e1c --- /dev/null +++ b/backend/alembic/versions/0065_collections_updated_at_idx.py @@ -0,0 +1,33 @@ +"""Add indexes on updated_at for collections and smart_collections tables + +Revision ID: 0065_collections_updated_at_idx +Revises: 0064_add_updated_at_indexes +Create Date: 2026-01-17 + +""" + +from alembic import op + +# revision identifiers, used by Alembic. +revision = "0065_collections_updated_at_idx" +down_revision = "0064_add_updated_at_indexes" +branch_labels = None +depends_on = None + + +def upgrade(): + with op.batch_alter_table("collections", schema=None) as batch_op: + batch_op.create_index("ix_collections_updated_at", ["updated_at"], unique=False) + + with op.batch_alter_table("smart_collections", schema=None) as batch_op: + batch_op.create_index( + "ix_smart_collections_updated_at", ["updated_at"], unique=False + ) + + +def downgrade(): + with op.batch_alter_table("collections", schema=None) as batch_op: + batch_op.drop_index("ix_collections_updated_at") + + with op.batch_alter_table("smart_collections", schema=None) as batch_op: + batch_op.drop_index("ix_smart_collections_updated_at") diff --git a/backend/endpoints/collections.py b/backend/endpoints/collections.py index 4cae8badf..ade17f01d 100644 --- a/backend/endpoints/collections.py +++ b/backend/endpoints/collections.py @@ -1,9 +1,10 @@ import json +from datetime import datetime from io import BytesIO from typing import Annotated from fastapi import Path as PathVar -from fastapi import Request, UploadFile, status +from fastapi import Query, Request, UploadFile, status from decorators.auth import protected_route from endpoints.responses.collection import ( @@ -144,18 +145,26 @@ async def add_smart_collection( @protected_route(router.get, "", [Scope.COLLECTIONS_READ]) -def get_collections(request: Request) -> list[CollectionSchema]: +def get_collections( + request: Request, + updated_after: Annotated[ + datetime | None, + Query( + description="Filter collections updated after this datetime (ISO 8601 format with timezone information)." + ), + ] = None, +) -> list[CollectionSchema]: """Get collections endpoint Args: request (Request): Fastapi Request object - id (int, optional): Collection id. Defaults to None. + updated_after: Filter smart collections updated after this datetime Returns: list[CollectionSchema]: List of collections """ - collections = db_collection_handler.get_collections() + collections = db_collection_handler.get_collections(updated_after=updated_after) return CollectionSchema.for_user(request.user.id, [c for c in collections]) @@ -181,17 +190,28 @@ def get_virtual_collections( @protected_route(router.get, "/smart", [Scope.COLLECTIONS_READ]) -def get_smart_collections(request: Request) -> list[SmartCollectionSchema]: +def get_smart_collections( + request: Request, + updated_after: Annotated[ + datetime | None, + Query( + description="Filter smart collections updated after this datetime (ISO 8601 format with timezone information)." + ), + ] = None, +) -> list[SmartCollectionSchema]: """Get smart collections endpoint Args: request (Request): Fastapi Request object + updated_after: Filter smart collections updated after this datetime Returns: list[SmartCollectionSchema]: List of smart collections """ - smart_collections = db_collection_handler.get_smart_collections(request.user.id) + smart_collections = db_collection_handler.get_smart_collections( + request.user.id, updated_after=updated_after + ) return SmartCollectionSchema.for_user( request.user.id, [s for s in smart_collections] @@ -216,7 +236,7 @@ def get_collection(request: Request, id: int) -> CollectionSchema: if collection.user_id != request.user.id and not collection.is_public: raise CollectionPermissionError(id) - + return CollectionSchema.model_validate(collection) @@ -414,7 +434,7 @@ async def delete_collection( collection = db_collection_handler.get_collection(id) if not collection: raise CollectionNotFoundInDatabaseException(id) - + if collection.user_id != request.user.id: raise CollectionPermissionError(id) diff --git a/backend/handler/database/collections_handler.py b/backend/handler/database/collections_handler.py index d654a7bcc..effa1e7a8 100644 --- a/backend/handler/database/collections_handler.py +++ b/backend/handler/database/collections_handler.py @@ -1,5 +1,6 @@ import functools from collections.abc import Sequence +from datetime import datetime from typing import Any from sqlalchemy import delete, insert, literal, or_, select, update @@ -85,9 +86,12 @@ class DBCollectionsHandler(DBBaseHandler): @with_roms def get_collections( self, + updated_after: datetime | None = None, query: Query = None, # type: ignore session: Session = None, # type: ignore ) -> Sequence[Collection]: + if updated_after: + query = query.filter(Collection.updated_at > updated_after) return session.scalars(query.order_by(Collection.name.asc())).unique().all() @begin_session @@ -201,6 +205,7 @@ class DBCollectionsHandler(DBBaseHandler): def get_smart_collections( self, user_id: int | None = None, + updated_after: datetime | None = None, session: Session = None, # type: ignore ) -> Sequence[SmartCollection]: query = select(SmartCollection).order_by(SmartCollection.name.asc()) @@ -211,6 +216,9 @@ class DBCollectionsHandler(DBBaseHandler): (SmartCollection.user_id == user_id) | SmartCollection.is_public ) + if updated_after: + query = query.filter(SmartCollection.updated_at > updated_after) + return session.scalars(query).unique().all() @begin_session From 842e627bcf1bb6b6b45f7167509bf6ac2c5e503e Mon Sep 17 00:00:00 2001 From: "Brandon T. Kowalski" Date: Sat, 17 Jan 2026 23:46:54 -0500 Subject: [PATCH 2/2] Fix copy-paste issue gemini discovered --- backend/endpoints/collections.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/endpoints/collections.py b/backend/endpoints/collections.py index ade17f01d..6f1e21cba 100644 --- a/backend/endpoints/collections.py +++ b/backend/endpoints/collections.py @@ -158,7 +158,7 @@ def get_collections( Args: request (Request): Fastapi Request object - updated_after: Filter smart collections updated after this datetime + updated_after: Filter collections updated after this datetime Returns: list[CollectionSchema]: List of collections