Skip to content

Conversation

@pankajbaid567
Copy link

@pankajbaid567 pankajbaid567 commented Dec 13, 2025

  • Add backend database models for memories with time and location grouping
  • Create API endpoints for generating and retrieving memories
  • Implement memory generation algorithm that groups photos by date and location
  • Add frontend Memories page with responsive card grid layout
  • Create interactive memory cards showing representative images
  • Update routing to include Memories page
  • Add unit tests for memories functionality

Resolves #723

Screen.Recording.2025-12-14.at.1.01.50.AM_compressed.mov

Summary by CodeRabbit

  • New Features

    • Memories section: browseable, time- and location-grouped memories with per-memory galleries (up to 5 representative images), titles, descriptions, date ranges, optional locations, image-count badges, "On This Day" view, loading skeletons and empty state; API endpoints and frontend routes added; cache prepopulation and background regeneration to keep results up to date.
  • Tests

    • New tests and fixtures covering cache/table setup, memory generation/grouping, image retrieval, title generation, location handling, and representative-image limits.

✏️ Tip: You can customize this high-level summary in your review settings.

- Add backend database models for memories with time and location grouping
- Create API endpoints for generating and retrieving memories
- Implement memory generation algorithm that groups photos by date and location
- Add frontend Memories page with responsive card grid layout
- Create interactive memory cards showing representative images
- Update routing to include Memories page
- Add unit tests for memories functionality

Resolves AOSSIE-Org#723
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Dec 13, 2025

Walkthrough

Adds an end-to-end Memories feature: new SQLite-backed memories cache and generator, FastAPI routes and Pydantic schemas, startup integration and background prepopulation, tests and fixtures, frontend endpoints and API functions, a React Memories page, and routing updates.

Changes

Cohort / File(s) Summary
Backend — Memories DB
backend/app/database/memories.py
New module implementing SQLite connection, db_create_memories_table(), db_generate_memories() (parses image metadata, groups by year-month & location, filters groups ≥3, constructs memory objects, persists in a transaction), db_get_memory_images(), db_get_memories_for_current_date(), cache checks, background regeneration, concurrency flags, error handling, and logging.
Backend — Routes
backend/app/routes/memories.py
New FastAPI router exposing GET / (all memories), GET /today (date-relevant memories), GET /{memory_id}/images (images for a memory). Maps DB results to Pydantic schemas and returns standardized responses with error handling.
Backend — Schemas
backend/app/schemas/memories.py
New Pydantic models: MemoryImage, Memory, GetMemoriesResponse, GetMemoryImagesResponse, ErrorResponse.
Backend — Integration & Tests
backend/main.py, backend/tests/test_memories.py, backend/tests/conftest.py
Register memories router and call db_create_memories_table() and db_prepopulate_memories_cache() at startup. Tests and fixtures added for table creation, memory generation (with/without data), image retrieval, grouping by title/location, and image-count checks. conftest seeds images via db_bulk_insert_images and creates related test fixtures.
Frontend — API Endpoints
frontend/src/api/apiEndpoints.ts
Adds memoriesEndpoints with getAllMemories, getTodayMemories, and getMemoryImages(memoryId) endpoint strings.
Frontend — API Functions
frontend/src/api/api-functions/memories.ts, frontend/src/api/api-functions/index.ts
New fetch functions: fetchAllMemories(), fetchTodayMemories(), fetchMemoryImages(memoryId); re-exported from API index.
Frontend — UI & Routing
frontend/src/pages/Memories/Memories.tsx, frontend/src/routes/AppRoutes.tsx
Replaces placeholder with a full Memories page: MemoryCard grid (up to 5 images), skeletons, empty state, loading/error handling; route updated to render component.
Docs / OpenAPI
docs/backend/backend_python/openapi.json
Adds Memories endpoints and schemas to OpenAPI, plus a minor schema composition change for an unrelated parameter.

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant Frontend as Browser (Memories Page)
    participant API as Backend API (/memories)
    participant DB as SQLite DB

    User->>Frontend: Open Memories page
    Frontend->>Frontend: decide fetchAll or fetchToday
    Frontend->>API: GET /memories or /memories/today
    API->>DB: call db_generate_memories() or db_get_memories_for_current_date()
    DB->>DB: read images, parse metadata, group by date/location, filter, persist/read memories
    DB-->>API: return memory objects + representative image IDs
    API->>API: map to Pydantic schemas
    API-->>Frontend: return GetMemoriesResponse
    Frontend->>Frontend: render MemoryCards (thumbnails + metadata)
    Frontend-->>User: display memories UI
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50 minutes

  • Focus points:
    • backend/app/database/memories.py — grouping/filtering logic, date & location parsing, transaction boundaries, background regeneration and concurrency handling.
    • backend/tests/* and backend/tests/conftest.py — fixture seeding, db_bulk_insert_images usage, and cleanup correctness.
    • API-to-schema mapping and error paths in backend/app/routes/memories.py.
    • Frontend frontend/src/pages/Memories/Memories.tsx — image loading, skeleton/empty states, and responsive rendering.

Possibly related PRs

Suggested labels

UI

Suggested reviewers

  • rahulharpal1603

Poem

🐇 I hop through folders, sniff the light,
I gather months and memories bright,
Five tiny frames, a caption to say,
“On this day” — a hop back in play,
Come bounce through moments, soft and light. 📷✨

Pre-merge checks and finishing touches

✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title 'feat: Implement Memories feature (#723)' accurately and concisely summarizes the primary change—implementing the complete Memories feature from frontend to backend.
Linked Issues check ✅ Passed The PR successfully addresses all key requirements from issue #723: backend database models for time/location grouping, API endpoints for generating/retrieving memories, memory generation algorithm, frontend Memories page with card layout, routing updates, and unit tests.
Out of Scope Changes check ✅ Passed All changes are directly related to implementing the Memories feature. Minor structural changes to conftest.py and test_folders.py are supporting changes for test infrastructure. The OpenAPI schema update documents the new endpoints.
Docstring Coverage ✅ Passed Docstring coverage is 86.96% which is sufficient. The required threshold is 80.00%.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 8

🧹 Nitpick comments (11)
backend/app/database/memories.py (4)

21-59: Missing error handling for table creation.

The db_create_memories_table function lacks a try/finally block for connection management, which is inconsistent with other functions in this file and could leave connections open on failure.

 def db_create_memories_table() -> None:
     """Create the memories table to cache generated memories."""
     conn = _connect()
     cursor = conn.cursor()
-
-    cursor.execute(
-        """
-        CREATE TABLE IF NOT EXISTS memories (
-            ...
-        )
-    """
-    )
-    ...
-    conn.commit()
-    conn.close()
+    try:
+        cursor.execute(
+            """
+            CREATE TABLE IF NOT EXISTS memories (
+                ...
+            )
+        """
+        )
+        ...
+        conn.commit()
+    finally:
+        conn.close()

84-86: Move import to top of file.

The from app.utils.images import image_util_parse_metadata import is placed inside the function. While this may avoid circular imports, it's duplicated in multiple functions (lines 86 and 217). Consider restructuring to import at module level or use a lazy import pattern once.


98-106: Date parsing may fail silently for edge cases.

The date parsing handles ISO format with "T" and space-separated format, but:

  1. replace("Z", "+00:00") only handles UTC timezone indicator
  2. Dates with other timezone offsets (e.g., +05:30) after "Z" replacement could fail
  3. The broad except Exception masks specific parsing issues

Consider using a more robust date parsing approach.

-            try:
-                if "T" in date_created:
-                    date_obj = datetime.fromisoformat(date_created.replace("Z", "+00:00"))
-                else:
-                    date_obj = datetime.strptime(date_created.split()[0], "%Y-%m-%d")
-            except Exception as e:
-                logger.warning(f"Could not parse date {date_created}: {e}")
-                continue
+            try:
+                date_str = date_created.replace("Z", "+00:00")
+                if "T" in date_str:
+                    date_obj = datetime.fromisoformat(date_str)
+                else:
+                    date_obj = datetime.strptime(date_str.split()[0], "%Y-%m-%d")
+            except (ValueError, TypeError) as e:
+                logger.warning(f"Could not parse date {date_created}: {e}")
+                continue

255-263: Title overwriting may cause inconsistent display.

When a memory matches the "On This Day" criteria, the original title (which may include location information) is completely overwritten with just the time-based title. This loses the location context that was appended in db_generate_memories.

Consider preserving location info:

             if years_ago > 0:
-                memory["title"] = f"On This Day {years_ago} Year{'s' if years_ago > 1 else ''} Ago"
+                location_suffix = f" • {memory['location']}" if memory.get('location') and memory['location'] != "Unknown Location" else ""
+                memory["title"] = f"On This Day {years_ago} Year{'s' if years_ago > 1 else ''} Ago{location_suffix}"
                 relevant_memories.append(memory)
backend/tests/test_memories.py (2)

1-6: Missing import for db_get_memories_for_current_date.

The function db_get_memories_for_current_date is defined in memories.py but not imported or tested here. Consider adding test coverage for this function.

 from app.database.memories import (
     db_create_memories_table,
     db_generate_memories,
     db_get_memory_images,
+    db_get_memories_for_current_date,
 )

91-96: Fragile assertion for title content.

The assertion checks for hardcoded years "2023", "2024", "2025" in titles. This will break in 2026 or fail for memories from other years.

             # Title should contain year or time reference
-            assert any(
-                indicator in memory["title"].lower()
-                for indicator in ["year", "ago", "2023", "2024", "2025"]
-            )
+            # Title should contain year indicator or a 4-digit year
+            import re
+            title_lower = memory["title"].lower()
+            has_time_ref = "year" in title_lower or "ago" in title_lower
+            has_year = bool(re.search(r'\b20\d{2}\b', memory["title"]))
+            assert has_time_ref or has_year, f"Title missing time reference: {memory['title']}"
frontend/src/pages/Memories/Memories.tsx (3)

2-2: Remove unused dispatch.

The useDispatch import and dispatch variable are declared but never used in the component.

Apply this diff to remove the unused code:

-import { useDispatch } from 'react-redux';
 const Memories = () => {
-  const dispatch = useDispatch();
   const [memories, setMemories] = useState<Memory[]>([]);

Also applies to: 218-218


45-45: Consider adding click handler for memory cards.

The card has cursor-pointer styling but no click handler. Consider adding navigation to a memory detail view or expanding the memory inline for better user interaction.


49-143: Consider extracting image rendering logic.

The image grid layouts have repeated patterns for error handling and path conversion. Consider extracting a reusable MemoryImageThumbnail component to reduce duplication and improve maintainability.

Example helper component:

const MemoryImageThumbnail = ({ 
  image, 
  index, 
  alt, 
  imageLoadError, 
  onError,
  className = "" 
}: { 
  image: MemoryImage; 
  index: number; 
  alt: string; 
  imageLoadError: Set<number>; 
  onError: (idx: number) => void;
  className?: string;
}) => (
  <img
    src={
      imageLoadError.has(index)
        ? '/placeholder.svg'
        : convertFileSrc(image.thumbnail || image.path)
    }
    alt={alt}
    className={cn("h-full w-full object-cover transition-transform duration-300 group-hover:scale-105", className)}
    onError={() => onError(index)}
  />
);
backend/app/routes/memories.py (2)

38-63: Extract duplicate memory transformation logic.

The code for converting database results to Memory and MemoryImage Pydantic models is duplicated between get_all_memories and get_memories_for_today. This duplication makes the code harder to maintain and increases the risk of inconsistencies.

Consider extracting a helper function:

def _transform_memories_to_response(memories_data: List[dict]) -> List[Memory]:
    """Transform database memory records to Memory response models."""
    memories = []
    for memory in memories_data:
        memory_images = [
            MemoryImage(
                id=img["id"],
                path=img["path"],
                thumbnail=img["thumbnail"],
                date=img["date"].isoformat(),
                location=img.get("location")
            )
            for img in memory.get("images", [])
        ]
        
        memories.append(
            Memory(
                id=memory["id"],
                title=memory["title"],
                description=memory["description"],
                start_date=memory["start_date"],
                end_date=memory["end_date"],
                location=memory.get("location"),
                latitude=memory.get("latitude"),
                longitude=memory.get("longitude"),
                image_count=memory["image_count"],
                images=memory_images
            )
        )
    return memories

Then use it in both endpoints:

memories = _transform_memories_to_response(memories_data)

Also applies to: 98-124


71-72: Consider using logger.exception for better debugging.

Using logger.exception() instead of logger.error() automatically includes the full stack trace, making it easier to diagnose issues in production.

Apply this change:

     except Exception as e:
-        logger.error(f"Error in get_all_memories: {e}")
+        logger.exception("Error in get_all_memories")
         raise HTTPException(

Similar changes for the other two endpoints.

Also applies to: 132-133, 180-181

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between d07d817 and 53e960c.

📒 Files selected for processing (10)
  • backend/app/database/memories.py (1 hunks)
  • backend/app/routes/memories.py (1 hunks)
  • backend/app/schemas/memories.py (1 hunks)
  • backend/main.py (3 hunks)
  • backend/tests/test_memories.py (1 hunks)
  • frontend/src/api/api-functions/index.ts (1 hunks)
  • frontend/src/api/api-functions/memories.ts (1 hunks)
  • frontend/src/api/apiEndpoints.ts (1 hunks)
  • frontend/src/pages/Memories/Memories.tsx (1 hunks)
  • frontend/src/routes/AppRoutes.tsx (2 hunks)
🧰 Additional context used
🧬 Code graph analysis (7)
backend/tests/test_memories.py (2)
backend/app/database/memories.py (3)
  • db_create_memories_table (21-59)
  • db_generate_memories (62-194)
  • db_get_memory_images (197-234)
backend/tests/test_folders.py (1)
  • test_db (20-33)
frontend/src/routes/AppRoutes.tsx (1)
frontend/src/constants/routes.ts (1)
  • ROUTES (1-12)
backend/main.py (2)
backend/app/database/memories.py (1)
  • db_create_memories_table (21-59)
backend/app/utils/microservice.py (1)
  • microservice_util_start_sync_service (41-71)
frontend/src/api/api-functions/memories.ts (3)
frontend/src/types/API.ts (1)
  • APIResponse (1-8)
frontend/src/api/axiosConfig.ts (1)
  • apiClient (5-12)
frontend/src/api/apiEndpoints.ts (1)
  • memoriesEndpoints (30-34)
backend/app/database/memories.py (1)
backend/app/utils/images.py (1)
  • image_util_parse_metadata (496-513)
backend/app/routes/memories.py (2)
backend/app/database/memories.py (3)
  • db_generate_memories (62-194)
  • db_get_memory_images (197-234)
  • db_get_memories_for_current_date (237-271)
backend/app/schemas/memories.py (5)
  • Memory (13-23)
  • MemoryImage (5-10)
  • GetMemoriesResponse (26-29)
  • GetMemoryImagesResponse (32-35)
  • ErrorResponse (38-41)
frontend/src/pages/Memories/Memories.tsx (6)
backend/app/schemas/memories.py (2)
  • MemoryImage (5-10)
  • Memory (13-23)
frontend/src/components/ui/badge.tsx (1)
  • Badge (46-46)
frontend/src/components/ui/skeleton.tsx (1)
  • Skeleton (13-13)
frontend/src/hooks/useQueryExtension.ts (1)
  • usePictoQuery (80-108)
frontend/src/api/api-functions/memories.ts (1)
  • fetchAllMemories (5-10)
frontend/src/hooks/useMutationFeedback.tsx (1)
  • useMutationFeedback (60-135)
🔇 Additional comments (6)
frontend/src/api/api-functions/index.ts (1)

7-7: LGTM!

The new memories module export follows the established pattern for API function re-exports.

frontend/src/routes/AppRoutes.tsx (1)

12-12: LGTM!

The Memories route is correctly integrated, replacing the placeholder with the actual component. The import style matches the default export from the Memories module.

Also applies to: 25-25

frontend/src/api/apiEndpoints.ts (1)

30-34: LGTM!

The memories endpoints follow the established patterns in the codebase. The function-based getMemoryImages endpoint correctly mirrors the pattern used by faceClustersEndpoints.getClusterImages. Backend routes confirm all three endpoints are properly implemented:

  • GET /memories/get_all_memories()
  • GET /memories/todayget_memories_for_today()
  • GET /memories/{memory_id}/imagesget_memory_detail()
backend/main.py (1)

22-22: LGTM! Clean integration of memories feature.

The memories module is properly integrated with correct import placement, table creation ordering (after images table for FK constraints), and router registration. The implementation follows existing patterns in the codebase.

Also applies to: 30-30, 57-57, 138-138

frontend/src/api/api-functions/memories.ts (1)

5-26: LGTM! Clean API function implementations.

All three functions follow consistent patterns and properly leverage the existing apiClient and endpoint definitions. The implementations are straightforward and correctly typed with APIResponse.

Note: fetchTodayMemories (lines 12-17) doesn't appear to be used in the provided files. If it's intended for future use, consider adding a comment or tracking it as a planned feature.

backend/app/schemas/memories.py (1)

5-41: LGTM! Clean schema definitions.

The Pydantic models are well-structured and provide clear API contracts for the memories feature. The use of Optional fields is appropriate for the data model.

Optional enhancement: Consider adding field validators to ensure data integrity (e.g., image_count >= 0, date format validation). However, since the data is sourced from a controlled database layer, this is not critical.

- Add database persistence for memories and memory_images tables
- Fix memory-image association storage in db_generate_memories
- Remove unused database connection in db_get_memories_for_current_date
- Add defensive metadata handling in routes to prevent AttributeError
- Add sample_images fixtures to conftest.py for shared test data
- Remove unsafe type casting in Memories component with runtime validation
- Add proper error handling and data validation throughout
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 8

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
backend/tests/conftest.py (1)

17-49: DB isolation is missing: tests currently use the production database app/database/PictoPy.db.

Setting TEST_MODE=true does not redirect DATABASE_PATH. The setup_before_all_tests fixture creates tables directly in your development database. See test_folders.py for the correct pattern: override app.config.settings.DATABASE_PATH to a temporary file at session setup.

Additionally, the sample_images and sample_images_with_location fixtures have a schema mismatch: they insert into column path, but the actual folders table schema defines folder_path (line 25 of folders.py). These fixtures will fail immediately.

🧹 Nitpick comments (6)
frontend/src/pages/Memories/Memories.tsx (3)

13-32: Consider extracting interfaces to a shared types file.

The MemoryImage and Memory interfaces are defined locally in this component. For better maintainability and type consistency across the application, consider moving these to a shared types file (e.g., @/types/memories.ts or @/api/types.ts). This ensures consistency with backend schemas and allows reuse across other components.


49-143: Consider refactoring duplicated image rendering logic.

The four conditional grid layouts (1, 2, 3, and 4+ images) contain similar image rendering code with only layout differences. Consider extracting a reusable MemoryImage sub-component or a render helper function to reduce duplication and improve maintainability.

Example refactor:

const MemoryImage = ({ 
  image, 
  index, 
  alt, 
  imageLoadError, 
  onError 
}: { 
  image: MemoryImage; 
  index: number; 
  alt: string; 
  imageLoadError: Set<number>; 
  onError: (index: number) => void;
}) => (
  <img
    src={
      imageLoadError.has(index)
        ? '/placeholder.svg'
        : convertFileSrc(image.thumbnail || image.path)
    }
    alt={alt}
    className="h-full w-full object-cover transition-transform duration-300 group-hover:scale-105"
    onError={() => onError(index)}
  />
);

236-256: Improve type safety with a type guard function.

While the runtime validation is a good improvement over the previous unsafe type assertion, the code still uses any type (line 243) and a type assertion (line 251). Consider defining a proper TypeScript type guard for cleaner, type-safe validation.

Apply this refactor:

+// Define type guard functions
+const isMemoryImage = (obj: unknown): obj is MemoryImage => {
+  return (
+    typeof obj === 'object' &&
+    obj !== null &&
+    'id' in obj &&
+    typeof (obj as any).id === 'string' &&
+    'path' in obj &&
+    typeof (obj as any).path === 'string'
+  );
+};
+
+const isMemory = (obj: unknown): obj is Memory => {
+  return (
+    typeof obj === 'object' &&
+    obj !== null &&
+    'id' in obj &&
+    typeof (obj as any).id === 'string' &&
+    'title' in obj &&
+    typeof (obj as any).title === 'string' &&
+    'images' in obj &&
+    Array.isArray((obj as any).images) &&
+    (obj as any).images.every(isMemoryImage)
+  );
+};
+
 const Memories = () => {
-  const [memories, setMemories] = useState<Memory[]>([]);
-
   const { data, isLoading, isSuccess, isError, error } = usePictoQuery({
     queryKey: ['memories'],
     queryFn: () => fetchAllMemories(),
   });

-  useEffect(() => {
-    if (isSuccess && data?.data) {
-      // Validate data structure before setting state
-      const memoriesData = data.data;
-      
-      if (Array.isArray(memoriesData)) {
-        // Filter and validate each memory object
-        const validMemories = memoriesData.filter((item: any) => {
-          return (
-            item &&
-            typeof item === 'object' &&
-            typeof item.id === 'string' &&
-            typeof item.title === 'string' &&
-            Array.isArray(item.images)
-          );
-        }) as Memory[];
-        
-        setMemories(validMemories);
-      }
-    }
-  }, [data, isSuccess]);
+  // Use query data directly with validation
+  const memories = useMemo(() => {
+    if (!data?.data || !Array.isArray(data.data)) return [];
+    return data.data.filter(isMemory);
+  }, [data]);

This approach:

  • Eliminates any types and unsafe assertions
  • Removes unnecessary local state
  • Uses useMemo for derived data (more idiomatic React)
  • TypeScript can properly infer types through the type guard
backend/app/database/memories.py (1)

15-18: Add a busy timeout (and consider WAL) to reduce “database is locked” failures under concurrent requests.

SQLite writes here (DELETE+INSERT) can collide with other readers/writers. At minimum, set a timeout; optionally enable WAL.

 def _connect() -> sqlite3.Connection:
-    conn = sqlite3.connect(DATABASE_PATH)
+    conn = sqlite3.connect(DATABASE_PATH, timeout=5)
     conn.execute("PRAGMA foreign_keys = ON")
+    # Optional: better concurrency for read-heavy workloads
+    # conn.execute("PRAGMA journal_mode = WAL")
     return conn
backend/tests/conftest.py (2)

61-108: Avoid redundant sqlite connections/commits around db_bulk_insert_images() (it uses its own transaction).

Both fixtures open sqlite3.connect(DATABASE_PATH), then call db_bulk_insert_images(sample_data) (which opens/commits separately), then conn.commit()—which doesn’t affect the bulk insert. Suggest either:

  • do everything via one connection (requires adding a “conn” parameter to db_bulk_insert_images), or
  • remove the extra conn/commit and only keep a connection for the folder insert.

Also applies to: 123-179


109-118: Consider cleaning up memories / memory_images too (cross-test interference).

These fixtures delete only images and folders. If any test generates memories, cache tables can persist across tests and affect expectations. Add cleanup for memories and memory_images (or make a per-test DB).

Also applies to: 180-188

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 53e960c and 1825752.

⛔ Files ignored due to path filters (1)
  • frontend/package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (4)
  • backend/app/database/memories.py (1 hunks)
  • backend/app/routes/memories.py (1 hunks)
  • backend/tests/conftest.py (3 hunks)
  • frontend/src/pages/Memories/Memories.tsx (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • backend/app/routes/memories.py
🧰 Additional context used
🧬 Code graph analysis (2)
backend/tests/conftest.py (7)
backend/app/database/faces.py (1)
  • db_create_faces_table (29-52)
backend/app/database/images.py (2)
  • db_create_images_table (52-86)
  • db_bulk_insert_images (89-120)
backend/app/database/yolo_mapping.py (1)
  • db_create_YOLO_classes_table (6-35)
backend/app/database/albums.py (2)
  • db_create_albums_table (7-26)
  • db_create_album_images_table (29-48)
backend/app/database/folders.py (1)
  • db_create_folders_table (15-36)
backend/app/database/metadata.py (1)
  • db_create_metadata_table (8-30)
backend/app/database/memories.py (1)
  • db_create_memories_table (21-59)
backend/app/database/memories.py (1)
backend/app/utils/images.py (1)
  • image_util_parse_metadata (496-513)
🔇 Additional comments (2)
frontend/src/pages/Memories/Memories.tsx (2)

188-215: LGTM!

The skeleton and empty state components are well-implemented with appropriate styling and messaging.


273-287: LGTM!

The conditional rendering logic with loading skeletons, populated grid, and empty state is well-structured and provides good UX.

- Add json_valid() guard to ORDER BY queries to prevent malformed JSON errors
- Convert datetime objects to ISO strings for JSON serialization
- Make cache refresh atomic with BEGIN IMMEDIATE transaction
- Optimize db_get_memories_for_current_date to query persisted table
- Fix column name from 'path' to 'folder_path' in test fixtures
- Remove unused Redux dispatch and cn imports from Memories.tsx
- Remove cursor-pointer class from Card without onClick handler
- Add placeholder.svg for image fallback handling

Addresses feedback from PR AOSSIE-Org#743
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 4

♻️ Duplicate comments (2)
frontend/src/pages/Memories/Memories.tsx (2)

233-253: Remove any + as Memory[] by turning the filter into a type guard (and clear state on invalid data).
You’re already validating; you can let TS infer safely without as Memory[] (Line 248) and avoid leaving stale memories when the payload isn’t an array.

   useEffect(() => {
     if (isSuccess && data?.data) {
       // Validate data structure before setting state
       const memoriesData = data.data;
-      
-      if (Array.isArray(memoriesData)) {
-        // Filter and validate each memory object
-        const validMemories = memoriesData.filter((item: any) => {
-          return (
-            item &&
-            typeof item === 'object' &&
-            typeof item.id === 'string' &&
-            typeof item.title === 'string' &&
-            Array.isArray(item.images)
-          );
-        }) as Memory[];
-        
-        setMemories(validMemories);
-      }
+
+      if (!Array.isArray(memoriesData)) {
+        setMemories([]);
+        return;
+      }
+
+      const validMemories = memoriesData.filter((item: unknown): item is Memory => {
+        if (!item || typeof item !== 'object') return false;
+        const m = item as Partial<Memory>;
+        return typeof m.id === 'string' && typeof m.title === 'string' && Array.isArray(m.images);
+      });
+
+      setMemories(validMemories);
     }
   }, [data, isSuccess]);

47-141: /placeholder.svg fallback likely 404s; use a guaranteed asset or inline fallback.
This file still hardcodes '/placeholder.svg' in several places (e.g., Lines 52, 69, 87, 100, 118, 131). If the asset isn’t present in the app’s public bundle, failed image loads will cascade into more failures.

🧹 Nitpick comments (3)
frontend/src/pages/Memories/Memories.tsx (1)

166-172: Date label ignores end_date; multi-day memories will look wrong.
If memories can span a range (you have start_date + end_date), consider rendering a range when they differ.

backend/app/database/memories.py (2)

21-56: Consider indexes / schema tweaks for db_get_memories_for_current_date query.
strftime(...) + ABS(...) will force scans as the table grows; also the “±7 day” logic won’t include cross-month edges (e.g., Jan 2 won’t match Dec 28).

Options:

  • Store start_month / start_day as INTEGER columns (or generated columns) and index them.
  • Or query by a date range using SQLite date functions (and handle month boundaries correctly).
  • Add indexes like CREATE INDEX IF NOT EXISTS idx_memories_start_date ON memories(start_date); and CREATE INDEX IF NOT EXISTS idx_memory_images_memory_rep ON memory_images(memory_id, is_representative);.

Also applies to: 309-321


390-402: Title may be misleading for a ±7-day window.
You always label items “On This Day …” even when they’re “near this day”. Consider keeping the persisted title, or changing the label when not an exact month/day match.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 1825752 and c730208.

⛔ Files ignored due to path filters (1)
  • frontend/public/placeholder.svg is excluded by !**/*.svg
📒 Files selected for processing (3)
  • backend/app/database/memories.py (1 hunks)
  • backend/tests/conftest.py (3 hunks)
  • frontend/src/pages/Memories/Memories.tsx (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • backend/tests/conftest.py
🧰 Additional context used
🧬 Code graph analysis (1)
backend/app/database/memories.py (1)
backend/app/utils/images.py (1)
  • image_util_parse_metadata (496-513)

- Add deterministic tie-breaker (id) to all ORDER BY queries for stable sorting
- Normalize datetime handling to date-only to avoid timezone-aware/naive TypeError
- Change exception handling to best-effort approach (log but don't fail on persistence errors)
- Make thumbnail optional in MemoryImage interface to match actual usage

These changes ensure:
- Stable, deterministic query results even with NULL metadata
- No timezone-related errors when comparing dates
- Graceful degradation if persistence fails (still returns in-memory data)
- TypeScript type safety matches runtime behavior
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

♻️ Duplicate comments (1)
frontend/src/pages/Memories/Memories.tsx (1)

51-52: Missing /placeholder.svg asset causes 404 errors on image load failure.

The fallback path /placeholder.svg is used throughout the image error handling but doesn't exist in the public directory. This will result in broken images when the primary image fails to load.

Consider using a data URL or inline SVG fallback, or add the missing asset to frontend/public/:

+ // At the top of the file, define a fallback
+ const PLACEHOLDER_IMAGE = 'data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100"><rect fill="%23374151" width="100" height="100"/><text x="50" y="55" font-size="14" fill="%239CA3AF" text-anchor="middle">No Image</text></svg>';

  // Then use it in place of '/placeholder.svg'
- ? '/placeholder.svg'
+ ? PLACEHOLDER_IMAGE

Also applies to: 68-70, 86-88, 99-101, 117-119, 130-132

🧹 Nitpick comments (5)
frontend/src/pages/Memories/Memories.tsx (2)

112-141: Grid layout for 4+ images may not display correctly.

When displayImages.length >= 4, the grid uses grid-cols-3 with the first image spanning col-span-2 row-span-2. However, the container for the first image lacks explicit height, which may cause layout issues depending on the aspect ratios of subsequent images.

Consider adding explicit aspect ratio to ensure consistent layout:

 {displayImages.length >= 4 && (
-  <div className="grid grid-cols-3 gap-1">
+  <div className="grid grid-cols-3 grid-rows-2 gap-1">
     <div className="col-span-2 row-span-2 overflow-hidden">
+      <div className="aspect-square">
         <img
           ...
         />
+      </div>
     </div>

218-221: Remove redundant arrow function wrapper in queryFn.

The arrow function wrapper is unnecessary since fetchAllMemories already returns a promise.

   const { data, isLoading, isSuccess, isError, error } = usePictoQuery({
     queryKey: ['memories'],
-    queryFn: () => fetchAllMemories(),
+    queryFn: fetchAllMemories,
   });
backend/app/database/memories.py (3)

88-89: Import inside function adds overhead on each call.

The image_util_parse_metadata import is placed inside the function body. While this avoids circular imports, it adds import overhead on every invocation of db_generate_memories().

If there's no circular dependency concern, move to top-level imports:

 # App-specific imports
 from app.config.settings import DATABASE_PATH
 from app.logging.setup_logging import get_logger
+from app.utils.images import image_util_parse_metadata

Then remove the local imports at lines 89 and 277.


372-372: Repeated import inside loop iteration.

The from app.utils.images import image_util_parse_metadata appears inside db_get_memories_for_current_date at line 372, which is inside the row iteration loop's scope. While Python caches imports, this is still suboptimal placement.

Move to top-level or at least before the loop:

+        from app.utils.images import image_util_parse_metadata
+        
         for row in rows:
             ...
-            from app.utils.images import image_util_parse_metadata

141-143: Minimum 3 images threshold is reasonable but not configurable.

The hardcoded threshold of 3 images may need adjustment based on user feedback. Consider extracting to a constant.

+MIN_IMAGES_FOR_MEMORY = 3
+
 def db_generate_memories() -> List[Dict[str, Any]]:
     ...
-            if len(images_in_group) < 3:  # Skip groups with too few images
+            if len(images_in_group) < MIN_IMAGES_FOR_MEMORY:
                 continue
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between c730208 and b39f89b.

📒 Files selected for processing (2)
  • backend/app/database/memories.py (1 hunks)
  • frontend/src/pages/Memories/Memories.tsx (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
backend/app/database/memories.py (1)
backend/app/utils/images.py (1)
  • image_util_parse_metadata (496-513)
🔇 Additional comments (8)
frontend/src/pages/Memories/Memories.tsx (3)

32-37: Consider using a Map instead of Set for better type safety with index tracking.

The imageLoadError state uses a Set<number> which works, but creates a new Set on each error. This is fine for small image counts (max 5), so no functional issue.


186-200: Skeleton component looks good.

Clean implementation of the loading skeleton that matches the card layout structure.


233-253: Data validation logic is well-implemented.

The validation now properly checks the array structure and validates each memory object before setting state, addressing the previous unsafe type assertion concern.

backend/app/database/memories.py (5)

15-18: Connection helper is clean and enables foreign keys correctly.


21-59: Table creation logic is correct.

The schema properly defines the memories table with appropriate columns and a junction table for many-to-many relationships with ON DELETE CASCADE.


254-294: db_get_memory_images implementation is correct and clean.

Good use of parameterized queries, proper error handling, and the ORDER BY includes tie-breaker as previously suggested.


240-243: Best-effort persistence approach is reasonable.

The decision to log errors but not re-raise during persistence failure allows the in-memory data to still be returned, which is a sensible tradeoff. The comment clearly documents this choice.


199-204: No issue with transaction handling. The BEGIN IMMEDIATE at line 199 is correctly placed at the start of a fresh transaction. The connection is created at line 67 with conn = _connect(), and only read-only SELECT operations occur before the explicit transaction start. The prior call to db_create_memories_table() uses its own separate connection, so there is no coupling or conflict with auto-commit behavior.

- Add module-level threading lock to prevent concurrent cache regenerations
- Implement background thread for async memory cache regeneration
- Add db_prepopulate_memories_cache() for startup cache initialization
- Fix month-boundary edge case using Julian day comparison (e.g., Dec 30 -> Jan 5)
- Return immediately on cache miss instead of blocking request handlers
- Add db_check_memories_cache_exists() helper function

Benefits:
- Non-blocking request handlers (no synchronous regeneration on cache miss)
- Single-regeneration guard prevents multiple concurrent regenerations
- Startup pre-population option prevents initial cache misses
- Cross-month matching works correctly at month boundaries
- Better performance and user experience during cache operations
- Move test_db fixture from test_folders.py to conftest.py (proper implementation)
- Remove duplicate test_db fixture from test_folders.py
- Fix test_get_memory_images to insert real test data before assertions
- Add proper cleanup in test_get_memory_images
- Replace conditional assertions with actual assertions that always run
- Test now properly verifies image retrieval with required fields

Benefits:
- Shared test_db fixture across all test modules (pytest auto-discovery)
- No conditional test logic that might not execute
- Proper test isolation with cleanup
- Tests actually verify behavior instead of skipping assertions
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🧹 Nitpick comments (1)
backend/tests/test_memories.py (1)

150-165: Consider strengthening location grouping validation.

The test verifies that location fields exist but doesn't validate that images are actually grouped by location. Since sample_images_with_location creates 4 images at each of 3 locations, you could assert that memories are created per location and that images within each memory share coordinates.

Example strengthening:

def test_memory_grouping_by_location(test_db, sample_images_with_location):
    """Test that memories are grouped by location."""
    db_create_memories_table()
    memories = db_generate_memories()
    
    # With 3 distinct locations × 4 images each, expect memories grouped by location
    assert len(memories) >= 3, "Should create memories for distinct locations"
    
    for memory in memories:
        if memory.get("location") and memory.get("latitude"):
            # Verify all images in this memory share the same location
            lat = memory["latitude"]
            lon = memory["longitude"]
            images = db_get_memory_images(memory["id"])
            for img in images:
                img_lat = img["metadata"].get("latitude")
                img_lon = img["metadata"].get("longitude")
                if img_lat and img_lon:
                    assert abs(img_lat - lat) < 0.01, "Images should share location"
                    assert abs(img_lon - lon) < 0.01, "Images should share location"
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 565ea60 and 1cce34b.

📒 Files selected for processing (3)
  • backend/tests/conftest.py (3 hunks)
  • backend/tests/test_folders.py (0 hunks)
  • backend/tests/test_memories.py (1 hunks)
💤 Files with no reviewable changes (1)
  • backend/tests/test_folders.py
🚧 Files skipped from review as they are similar to previous changes (1)
  • backend/tests/conftest.py
🧰 Additional context used
🧬 Code graph analysis (1)
backend/tests/test_memories.py (2)
backend/app/database/memories.py (3)
  • db_create_memories_table (26-64)
  • db_generate_memories (67-256)
  • db_get_memory_images (259-299)
backend/tests/conftest.py (3)
  • test_db (53-66)
  • sample_images (70-128)
  • sample_images_with_location (132-199)
🔇 Additional comments (5)
backend/tests/test_memories.py (5)

1-6: LGTM!

Import statements are clean and appropriate for the test module.


9-30: LGTM!

The test correctly verifies that both memories and memory_images tables are created by querying sqlite_master.


33-38: LGTM!

The test correctly validates that db_generate_memories() returns an empty list when no images are present.


60-129: LGTM!

The test now properly validates db_get_memory_images() by directly inserting test data and asserting on concrete results. The fix addresses the previous review concern about conditional assertions. Good cleanup logic ensures test isolation.


168-177: LGTM!

The test correctly validates that image_count represents the total images in a memory group while the images array contains at most 5 representatives.

- Remove 'if len(memories) > 0' conditional logic from test_generate_memories_with_data
- Remove 'if len(memories) > 0' conditional logic from test_memory_title_generation
- Add explicit assertions that memories were generated before field checks
- Add clear assertion messages for better debugging
- Tests now fail properly when no memories are generated instead of passing silently

Changes:
- test_generate_memories_with_data: Assert len(memories) > 0 before accessing fields
- test_memory_title_generation: Assert len(memories) > 0 before iterating
- Both tests now have descriptive error messages when assertions fail

Benefits:
- Tests actually verify behavior instead of conditionally skipping
- Failures are more informative with clear messages
- No silent passes when functionality is broken
- Better test reliability and coverage
…ick-to-view functionality, fetch all images, uniform card sizes
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (4)
backend/tests/test_memories.py (1)

156-171: Strengthen location grouping validation.

The test collects unique locations but doesn't verify that images are actually grouped by location. It only checks that location is not None when present. Consider asserting that:

  • Multiple distinct locations result in separate memories
  • Images within each memory share the same/nearby location coordinates
  • The grouping algorithm correctly separates images by location thresholds

Example enhancement:

 def test_memory_grouping_by_location(test_db, sample_images_with_location):
     """Test that memories are grouped by location."""
     db_create_memories_table()
     memories = db_generate_memories()
     
-    # If we have multiple locations, they should be in separate memories
-    locations = set()
-    for memory in memories:
-        if memory.get("location"):
-            locations.add(memory["location"])
-    
-    # Each memory should have a consistent location
-    for memory in memories:
-        if memory.get("location") and len(memory["images"]) > 0:
-            # All images in this memory should have similar locations
-            assert memory["location"] is not None
+    if len(memories) > 1:
+        # Verify that memories with different locations are separate
+        locations = [m.get("location") for m in memories if m.get("location")]
+        assert len(locations) == len(set(locations)), "Each memory should have unique location"
+        
+        # Verify images within each memory share similar coordinates
+        for memory in memories:
+            if memory.get("latitude") and len(memory["images"]) > 1:
+                # Check coordinate consistency within memory
+                lat, lon = memory["latitude"], memory["longitude"]
+                assert lat is not None and lon is not None
frontend/src/pages/Memories/Memories.tsx (2)

342-366: Add keyboard navigation for modal accessibility.

The fullscreen image modal can only be closed by clicking the X button or backdrop. Add ESC key handling and consider focus management for better accessibility.

Apply this enhancement:

+  // Add keyboard handler for ESC key
+  useEffect(() => {
+    if (selectedImageIndex === null) return;
+    
+    const handleKeyDown = (e: KeyboardEvent) => {
+      if (e.key === 'Escape') {
+        setSelectedImageIndex(null);
+      }
+    };
+    
+    window.addEventListener('keydown', handleKeyDown);
+    return () => window.removeEventListener('keydown', handleKeyDown);
+  }, [selectedImageIndex]);
+
   return (
     // ... component JSX

Also consider adding role="dialog" and aria-modal="true" to the modal container for screen reader support.


230-256: Consider validating date and improving type safety in image mapping.

The mapping at line 241 allows empty strings for dates (img.metadata?.date_created || ''), which may cause issues in date formatting or sorting. Also, the any type at line 237 bypasses type checking.

Consider adding runtime validation:

   const loadAllImages = async () => {
     setIsLoadingImages(true);
     try {
       const response = await fetchMemoryImages(memory.id);
       if (response.success && response.data && Array.isArray(response.data)) {
-        // Map the response to MemoryImage format
-        const images: MemoryImage[] = response.data.map((img: any) => ({
+        const images: MemoryImage[] = response.data
+          .filter((img: any) => img.id && img.path) // Validate required fields
+          .map((img: any) => ({
             id: img.id,
             path: img.path,
             thumbnail: img.thumbnail,
-            date: img.metadata?.date_created || '',
+            date: img.metadata?.date_created || new Date().toISOString(),
             location: img.metadata?.location,
           }));
         setAllImages(images);
       }

Alternatively, define a proper type for the API response instead of using any.

docs/backend/backend_python/openapi.json (1)

1120-1128: Consider simplifying allOf wrapper for single reference.

The InputType parameter uses allOf with a single $ref to add a title. While valid in OpenAPI 3.1, this can be simplified since OpenAPI 3.1 allows $ref siblings.

If desired, simplify to:

 "schema": {
-  "allOf": [
-    {
-      "$ref": "#/components/schemas/InputType"
-    }
-  ],
+  "$ref": "#/components/schemas/InputType",
   "description": "Choose input type: 'path' or 'base64'",
   "default": "path",
   "title": "Input Type"
 }

However, the current structure is not incorrect—just more verbose.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 1cce34b and d6caded.

📒 Files selected for processing (4)
  • backend/app/routes/memories.py (1 hunks)
  • backend/tests/test_memories.py (1 hunks)
  • docs/backend/backend_python/openapi.json (5 hunks)
  • frontend/src/pages/Memories/Memories.tsx (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • backend/app/routes/memories.py

Comment on lines +2517 to +2555
"MemoryImage": {
"properties": {
"id": {
"type": "string",
"title": "Id"
},
"path": {
"type": "string",
"title": "Path"
},
"thumbnail": {
"type": "string",
"title": "Thumbnail"
},
"date": {
"type": "string",
"title": "Date"
},
"location": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"title": "Location"
}
},
"type": "object",
"required": [
"id",
"path",
"thumbnail",
"date"
],
"title": "MemoryImage"
},
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

# First, check the openapi.json file at the specified lines
wc -l docs/backend/backend_python/openapi.json

Repository: AOSSIE-Org/PictoPy

Length of output: 107


🏁 Script executed:

# Read the MemoryImage schema section
sed -n '2517,2555p' docs/backend/backend_python/openapi.json

Repository: AOSSIE-Org/PictoPy

Length of output: 897


🏁 Script executed:

# Find the frontend Memories.tsx file
fd -i memories.tsx

Repository: AOSSIE-Org/PictoPy

Length of output: 102


🏁 Script executed:

# Search for backend MemoryImage schema definition
fd -E node_modules -type f -name "*.py" | xargs rg -l "MemoryImage" | head -20

Repository: AOSSIE-Org/PictoPy

Length of output: 470


🏁 Script executed:

# Read frontend Memories.tsx to find the TypeScript interface (around line 15)
head -50 frontend/src/pages/Memories/Memories.tsx | cat -n

Repository: AOSSIE-Org/PictoPy

Length of output: 2049


🏁 Script executed:

# Read backend schema definition for MemoryImage
cat backend/app/schemas/memories.py

Repository: AOSSIE-Org/PictoPy

Length of output: 815


🏁 Script executed:

# Check how MemoryImage is instantiated in backend
cat backend/app/routes/memories.py

Repository: AOSSIE-Org/PictoPy

Length of output: 6267


🏁 Script executed:

# Check if frontend has defensive fallback for thumbnail as claimed
rg -n "thumbnail.*\|\|.*path|path.*fallback" frontend/src/pages/Memories/Memories.tsx

Repository: AOSSIE-Org/PictoPy

Length of output: 628


🏁 Script executed:

# Also check the image rendering parts more broadly
sed -n '50,150p' frontend/src/pages/Memories/Memories.tsx | cat -n

Repository: AOSSIE-Org/PictoPy

Length of output: 5122


Schema mismatch: MemoryImage.thumbnail required vs optional.

The OpenAPI schema declares thumbnail as required (lines 2527-2530, 2548-2553), and the backend Pydantic schema also requires it (thumbnail: str). However, the frontend TypeScript interface declares it as optional (thumbnail?: string in Memories.tsx line 15). Throughout the frontend, the code defensively handles missing thumbnails with img.thumbnail || img.path fallbacks at 7 locations (lines 56, 72, 90, 103, 121, 134, 330 in Memories.tsx), and the backend uses .get("thumbnail", "") which provides an empty string fallback.

This reveals a contract violation:

  • OpenAPI and Pydantic schemas promise thumbnail is required
  • Frontend and backend defensive code suggest it can be missing or falsy
  • If thumbnail is always present, the defensive coding is unnecessary
  • If thumbnail can be absent, the schemas should mark it as optional

Align the OpenAPI schema, Pydantic model, and actual backend implementation: either ensure thumbnail is always returned as a non-empty value, or make it optional in all three places.

🤖 Prompt for AI Agents
In docs/backend/backend_python/openapi.json around lines 2517 to 2555, the
MemoryImage schema wrongly marks "thumbnail" as required; remove "thumbnail"
from the "required" array and change its property to allow null (e.g., make it
anyOf [{"type":"string"},{"type":"null"}]) so the OpenAPI matches runtime. Also
update the backend Pydantic model to declare thumbnail as Optional[str] (with a
default, e.g., "" or None) and ensure the serialization consistently returns
.get("thumbnail", "") (or an explicit fallback) so frontend consumers keep their
defensive img.thumbnail || img.path handling.

@pankajbaid567 pankajbaid567 mentioned this pull request Dec 15, 2025
6 tasks
@rahulharpal1603
Copy link
Contributor

I am sorry, but this is not the outcome we were expecting. You have just implemented month-wise grouping, from what it looks like on the video.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Implement the Memories page

3 participants