8.7 KiB
Image Feed Implementation - Current State
Last Updated: 2025-11-03
Overview
Migration of the image feed from monolithic Meilisearch implementation to the event-engine-common feed framework. The goal is to make getImagesFromFeedSearch behave EXACTLY like getAllImagesIndex for seamless A/B testing.
Current Status: ✅ TypeScript Compilation Fixed
All TypeScript errors related to the feed implementation have been resolved. The feed is now type-safe and ready for testing.
Completed Tasks
-
✅ Input Type Restructuring (
ImageSearchInput)- Changed from extending
GetAllImagesInputto extendingGetInfiniteImagesOutput - Removed
useLogicalReplicafield (not needed by feeds) - Added
useCombinedNsfwLevel,currentUserId,isModeratorfor destructured user data - Updated destructuring in
getImagesFromSearchPreFilterandgetImagesFromSearchPostFilter
- Changed from extending
-
✅ Return Type Compatibility
- Created transformation layer in
getImagesFromFeedSearchto strip extra fields - Removes:
sortAtUnix,publishedAtUnix,existedAtUnix,tagIds,flags,aiNsfwLevel,combinedNsfwLevel - Ensures
typeis cast toMediaTypeenum - Ensures
availabilityis non-undefined (defaults toAvailability.Public)
- Created transformation layer in
-
✅ Conditional Data Fetching (respects
includeparameter)- Tags: Only fetched and populated when
'tags'ininclude(otherwise empty array) - Tag IDs: Only fetched when
'tagIds'ininclude - Profile Pictures: Only fetched when
'profilePictures'ininclude - Cosmetics: Only fetched when
'cosmetics'ininclude - Meta: Only fetched when
'metaSelect'ininclude
- Tags: Only fetched and populated when
-
✅ TypeScript Type Safety (WITHOUT using
any)- Proper type inference for Promise.all with conditional fetches
- Type aliases for cache results:
ProfilePictureData,UserCosmeticData,ImageTagIdsData, etc. - Explicit type casts for conditional promises (e.g.,
Promise<Record<number, { meta: unknown }>>) - Type assertions using
as unknown asfor client compatibility - Removed all implicit
anytypes
-
✅ Controller Integration
- Updated
getInfiniteImagesHandlerinimage.controller.ts - Passes
currentUserIdandisModeratortogetImagesFromFeedSearch - Passes full
userobject togetAllImages - Both branches include
tagIdsin the include array
- Updated
File Changes Summary
Modified Files
-
event-engine-common/feeds/images.feed.ts- Lines 1079-1136: Added proper typing for Promise.all destructuring
- Lines 1075-1077: Added conditional logic for tags/tagIds fetching
- Lines 1191-1204: Tags only populate when explicitly requested in
include
-
src/server/services/image.service.ts- Line 1772: Changed
ImageSearchInputto extendGetInfiniteImagesOutput - Lines 1801-1859:
getImagesFromFeedSearchwith proper type casts and transformation layer - Lines 1862-1907: Removed
useLogicalReplicafrom destructuring ingetImagesFromSearchPreFilter - Lines 2458-2503: Removed
useLogicalReplicafrom destructuring ingetImagesFromSearchPostFilter
- Line 1772: Changed
-
src/server/controllers/image.controller.ts- Lines 253-288: Split controller logic for different input types
- Feed search: passes
currentUserId,isModerator - Standard search: passes full
userobject,useLogicalReplica
Documentation Files
docs/type-comparison.md- Documents differences betweenGetAllImagesInputandImageSearchInputdocs/return-type-comparison.md- Documents structural differences between return types
Key Technical Decisions
1. User Data Handling
- Decision: Use destructured fields (
currentUserId,isModerator) instead of fulluserobject - Rationale: Simpler, more explicit, avoids passing unnecessary session data to feeds
- Impact:
ImageSearchInputextendsGetInfiniteImagesOutputdirectly
2. Tags Behavior
- Decision: Tags are empty array
[]by default, only populated when'tags'ininclude - Rationale: Matches
getAllImagesIndexbehavior exactly - Impact: Conditional fetch in
populateDocumentsbased onincludeparameter
3. Type Safety
- Decision: No use of
anytype, proper type inference throughout - Rationale: User requested "DO not use any" - maintain strict type safety
- Impact: Added type aliases and explicit Promise types for conditional fetches
Architecture
Controller (image.controller.ts)
├─> getImagesFromFeedSearch (NEW)
│ ├─> ImagesFeed.populatedQuery()
│ │ ├─> queryDocuments (Meilisearch query)
│ │ └─> populateDocuments (Fetch user/tag/cosmetic data)
│ └─> Transform to match getAllImagesIndex output
│
└─> getAllImages (EXISTING)
└─> getImagesFromSearch
└─> Meilisearch query + hydration
Testing Status
⚠️ NOT YET TESTED - Ready for testing but needs validation
What to Test
- Basic Query - Verify feed returns results
- Include Parameter - Verify tags only appear when requested
- Return Type - Verify structure matches
getAllImagesIndex - Pagination - Verify cursor-based pagination works
- Filtering - Verify all filters work (NSFW, tags, base models, etc.)
- User Context - Verify
currentUserIdandisModeratoraffect results correctly
Test Endpoints
- Development:
src/pages/api/dev-local/test-image-feed.ts - Detailed:
src/pages/api/dev-local/test-image-feed-detailed.ts
Note: These test endpoints currently have TypeScript errors due to missing required fields in the input object.
Known Issues
TypeScript Errors (Unrelated to Feed)
- 17 total TypeScript errors remaining in the codebase
- All feed-related errors are FIXED ✅
- Remaining errors are mostly Prisma
updateManyAndReturnissues in other services
Test Endpoints
- Need to be updated with proper input types (missing required fields)
Next Steps
Immediate (Before Testing)
- ✅ Fix TypeScript errors in feed implementation
- 🔲 Fix test endpoint TypeScript errors
- 🔲 Run test endpoints to verify feed works
- 🔲 Compare output structure between
getAllImagesIndexandgetImagesFromFeedSearch
After Initial Testing
-
🔲 Add missing fields to
ImageQueryInputif needed:collectionIdcollectionTagIdhideAutoResourceshideManualResourceshiddenfollowedprioritizedUserIdsreactions- Other fields from
GetInfiniteImagesOutput(seedocs/type-comparison.md)
-
🔲 Performance testing
- Compare query times between old and new implementation
- Monitor memory usage
- Check cache hit rates
-
🔲 Feature flag rollout
- Currently controlled by
features.imageIndexFeedflag - Gradual rollout to production
- Currently controlled by
Feature Flag
Flag: features.imageIndexFeed
Location: image.controller.ts:261
const useFeedSearch = features.imageIndexFeed && input.useIndex;
When enabled, uses getImagesFromFeedSearch instead of getAllImages.
Important Code Locations
Feed Implementation
- Feed Definition:
event-engine-common/feeds/images.feed.ts - Type Definitions:
event-engine-common/types/image-feed-types.ts - Cache Service:
event-engine-common/services/cache.ts - Metric Service:
event-engine-common/services/metrics.ts
Integration Points
- Controller:
src/server/controllers/image.controller.ts:253-288 - Service:
src/server/services/image.service.ts:1801-1859
Type Compatibility
- ImageSearchInput:
src/server/services/image.service.ts:1772-1784 - Transformation Layer:
src/server/services/image.service.ts:1831-1849
Dependencies
Required Services
- ✅ Meilisearch (
metricsSearchClient) - ✅ Clickhouse (
clickhouse) - ✅ PostgreSQL (
pgDbWrite) - ✅ Redis (
redis,sysRedis) - ✅ Flipt (
FliptSingleton)
Type Imports
import { ImagesFeed } from '../../../event-engine-common/feeds';
import { MetricService } from '../../../event-engine-common/services/metrics';
import { CacheService } from '../../../event-engine-common/services/cache';
Questions for Tomorrow
- Should we add all missing fields from
GetInfiniteImagesOutputtoImageQueryInput? - Do we need to handle
user.isModeratordifferently (more granular permissions)? - Should we implement caching for feed results?
- What's the rollout strategy for the feature flag?
Rollback Plan
If issues are discovered:
- Immediate: Disable feature flag
features.imageIndexFeed - Code: Revert controller changes (lines 253-288)
- Cleanup: Can leave feed implementation in place for future fixes
All existing functionality remains intact - this is purely additive.