8.9 KiB
Image Feed Implementation Summary
Overview
I've successfully migrated the getImagesFromSearchPostFilter functionality to the new event-engine-common Feed system. The implementation provides a unified, type-safe interface for querying, populating, and creating image documents in Meilisearch.
What Was Implemented
1. Type Definitions (event-engine-common/types/image-feed-types.ts)
Ported all necessary types and enums from the main codebase:
-
Enums:
ImageSort- sorting options (Most Reactions, Most Comments, Most Collected, Newest, Oldest)NsfwLevel- NSFW content levels (PG, PG13, R, X, XXX, Blocked)Availability- content availability (Public, Private, Unsearchable)BlockedReason- reasons for blocking (TOS, Moderated, CSAM, AiNotVerified)MediaType- media types (image, video, audio)
-
Document Types:
ImageDocument- Meilisearch document (30+ fields)PopulatedImage- Fully populated image with stats, user, tags, cosmeticsSearchBaseImage- Base image data from PostgreSQL
-
Input Types:
ImageQueryInput- Complete filter options (20+ filter types)
-
Helper Functions:
includesNsfwContent()- Check if browsing level includes NSFWbrowsingLevelToArray()- Convert flag to array of levelsonlySelectableLevels()- Filter out non-selectable levelssnapToInterval()- Round timestamp for better caching
2. Cache Definitions (event-engine-common/caches/imageData.cache.ts)
Created 5 new caches for the Image Feed:
imageTagIds- Tag IDs associated with imagestagData- Full tag information (name, type, nsfwLevel)cosmeticData- Cosmetic informationuserCosmetics- Equipped cosmetics for usersprofilePictures- User profile picture data
All caches use the Feed-compatible createCache interface with 24-hour TTL.
3. Image Feed (event-engine-common/feeds/images.feed.ts)
Implemented comprehensive feed with three main methods:
createDocuments
Replicates logic from metrics-images.search-index.ts:
Process:
- Fetch base image data from PostgreSQL (sortAt, hasMeta, onSite, etc.)
- Fetch metrics from ClickHouse via metric service
- Fetch tags from cache
- Fetch tools/techniques from PostgreSQL
- Fetch model versions from PostgreSQL
- Transform and combine all data into Meilisearch documents
Features:
- Supports 'full' and 'metrics' update types
- Batching for large ID sets (1000 per batch)
- Proper POI detection (image.poi ?? resource.poi)
- Combined NSFW level calculation
- Flags extraction (promptNsfw)
queryDocuments
Replicates filter logic from getImagesFromSearchPostFilter:
Supports 20+ Filter Types:
- NSFW level filtering (browsingLevel → combinedNsfwLevel/nsfwLevel)
- Model version filtering (postedToId, modelVersionIds, modelVersionIdsManual)
- Remix filtering (remixOfId, remixesOnly, nonRemixesOnly)
- Tag/tool/technique filtering
- Type filtering (image/video/audio)
- Period filtering (Day, Week, Month, Year, AllTime)
- User filtering (userId, excludedUserIds, followed, hidden)
- POI/minor filtering
- Metadata filtering (hasMeta, onSite, requiringMeta)
- Publishing status filtering (notPublished, scheduled)
- Moderator features (blockedFor, poiOnly, minorOnly)
Features:
- Database lookups for hidden/followed images
- Username to userId conversion
- NSFW license restrictions placeholder
- Multiple sort orders (reactions, comments, collected, newest, oldest)
- Pagination via context
populateDocuments
Enhances documents with additional data:
Fetches:
- Metrics from ClickHouse (via metric service)
- User data (username, avatar, deletedAt)
- Profile pictures
- User cosmetics (equipped cosmetics)
- Tag data (full tag information)
- Cosmetic data
Returns:
- Fully populated images with:
- Stats object (all reaction counts, comments, collections, tips)
- User object (username, image, deletedAt, profilePictureId)
- Tags array (id, name, type, nsfwLevel)
- Cosmetics array (id, name, type, data, source)
File Structure
event-engine-common/
├── types/
│ └── image-feed-types.ts ← New types and enums
├── caches/
│ ├── imageData.cache.ts ← New caches
│ └── index.ts ← Updated exports
└── feeds/
├── images.feed.ts ← New comprehensive feed
└── index.ts ← Updated exports
Usage Example
import { ImagesFeed } from 'event-engine-common/feeds';
import { meilisearch, clickhouse, pg, metricService, cacheService } from '...';
// Initialize feed
const feed = new ImagesFeed(
meilisearch,
clickhouse,
pg,
metricService,
cacheService
);
// Query images with filters
const images = await feed.populatedQuery({
limit: 100,
sort: 'Most Reactions',
browsingLevel: NsfwLevel.PG | NsfwLevel.PG13,
period: 'Week',
tags: [123, 456],
currentUserId: 789,
});
// Upsert images to Meilisearch
await feed.upsert([1, 2, 3, 4, 5], 'full');
// Delete images from Meilisearch
await feed.delete([1, 2, 3]);
Schema (30+ Fields)
The Meilisearch index contains:
Primary: id, index
Basic: sortAt, sortAtUnix, type, userId, postId, url, width, height, hash, hideMeta
Model/Resources: modelVersionIds, modelVersionIdsManual, postedToId, baseModel
NSFW/Safety: nsfwLevel, combinedNsfwLevel, availability, blockedFor, poi, minor
Tags/Tools/Techniques: tagIds, toolIds, techniqueIds
Metadata: hasMeta, onSite, publishedAtUnix, existedAtUnix, remixOfId, flags.promptNsfw
Metrics: reactionCount, commentCount, collectedCount
Migration Path
For Search Job Migration
Replace the current imagesMetricsDetailsSearchIndex with:
// Instead of using createSearchIndexUpdateProcessor
import { ImagesFeed } from 'event-engine-common/feeds';
// Use feed.upsert() for batch updates
await feed.upsert(imageIds, 'full');
For API Query Migration
Replace getImagesFromSearchPostFilter with:
// Old
const { data, nextCursor } = await getImagesFromSearchPostFilter(input);
// New
const feed = new ImagesFeed(...);
const images = await feed.populatedQuery({
limit: input.limit,
sort: input.sort,
browsingLevel: input.browsingLevel,
// ... all other filters
});
Key Differences from Original
Improvements
- Type-safe - All types inferred from config
- Modular - Caches can be reused across feeds
- Testable - Each method can be tested independently
- Consistent - Same pattern as other feeds
- Maintainable - Clear separation of concerns
Limitations/TODOs
- NSFW License Restrictions - Commented out, needs dynamic configuration
- Cursor-based pagination - Meilisearch uses offset-based, may need adjustment
- Adaptive batch sizing - Original has adaptive batching for post-filtering, not implemented
- Post-filtering logic - Existence checks, permission validation planned but not implemented in populateDocuments yet
- Flipt integration - Feature flag support not added to base Feed context
Next Steps
-
Add post-filtering to populateDocuments:
- Existence checks (Redis cache + DB fallback)
- Permission validation (private/blocked content)
- Scheduled post filtering (for non-owners)
- NSFW level validation (unscanned content)
-
Add Flipt client to Feed context:
- Optional interface in
feeds/base.ts - Support for feature-flagged existence checks
- Optional interface in
-
NSFW restricted base models:
- Add configuration option or fetch from database
- Implement filtering in queryDocuments
-
Testing:
- Unit tests for each method
- Integration tests with real Meilisearch
- Performance comparison with current implementation
-
Integration:
- Update API to use new feed
- Update search job to use feed.upsert()
- Feature flag rollout
Questions/Decisions Made
- ✅ Post-filtering logic: Will be in populateDocuments (per feedback)
- ✅ prioritizedUserIds: Not implemented (not in current implementation)
- ⏳ Metrics-only update: Optional, may implement later
- ⏳ Flipt client: Optional interface can be added to base.ts
Files Changed
- Created:
event-engine-common/types/image-feed-types.ts - Created:
event-engine-common/caches/imageData.cache.ts - Created:
event-engine-common/feeds/images.feed.ts - Updated:
event-engine-common/caches/index.ts - Updated:
event-engine-common/feeds/index.ts - Created:
docs/image-feed-migration-plan.md - Created:
docs/image-feed-implementation-summary.md
Estimated Impact
- Search Job: Can be simplified to use
feed.upsert()instead of complex multi-step processor - API: Cleaner, more maintainable code with type safety
- Performance: Should be similar or better due to efficient caching and batching
- Future Feeds: Can follow same pattern for Posts, Articles, etc.