8.8 KiB
Image Feed Migration Plan
Overview
Migrate getImagesFromSearchPostFilter functionality to use the new event-engine-common Feed system. This will provide a unified, type-safe interface for querying, populating, and creating image documents in Meilisearch.
This migration also includes migrating the Meilisearch population job (metrics-images.search-index.ts) to use the same Feed system's createDocuments method.
Current Implementation Analysis
1. getImagesFromSearchPostFilter (src/server/services/image.service.ts:2371)
Responsibilities:
- Queries Meilisearch with complex filters
- Post-processes results (existence checks, permission filtering)
- Populates with metrics from ClickHouse
- Returns paginated results with cursor
Key Features:
- Adaptive batch sizing for post-filtering
- Feature-flagged existence checking (Redis cache + DB fallback)
- NSFW level filtering (browsing levels)
- Complex permission filtering (private/blocked content, scheduled posts)
- Period-based filtering
- Tag/tool/technique filtering
- Model version filtering (auto/manual resources)
- Remix filtering
- POI/minor content filtering
- Moderator-specific features
2. metrics-images.search-index.ts (src/server/search-index/metrics-images.search-index.ts)
Responsibilities:
- Fetches base image data from PostgreSQL
- Fetches metrics from ClickHouse
- Fetches tags, tools, techniques, and model versions
- Transforms and combines all data for Meilisearch indexing
Document Structure:
{
id, index, postId, url, nsfwLevel, aiNsfwLevel, nsfwLevelLocked,
width, height, hash, hideMeta, sortAt, type, userId, publishedAt,
hasMeta, onSite, postedToId, needsReview, minor, promptNsfw,
blockedFor, remixOfId, hasPositivePrompt, availability, poi,
acceptableMinor,
// Transformed:
combinedNsfwLevel, baseModel, modelVersionIds, modelVersionIdsManual,
toolIds, techniqueIds, publishedAtUnix, existedAtUnix, sortAtUnix,
tagIds, flags, reactionCount, commentCount, collectedCount
}
Required Types to Port
From Civitai Main Codebase
-
Enums:
ImageSort- sorting optionsNsfwLevel- NSFW content levelsAvailability- content availability (Public, Private, etc.)BlockedReason- reasons for blocking contentMediaType- image/video types
-
Input Types:
ImageSearchInput- complete filter/query input- Derived from
GetInfiniteImagesOutput+ additional fields
-
Document Types:
ImageMetricsSearchIndexRecord- Meilisearch document structureSearchBaseImage- base image data from PostgreSQL
-
Helper Types:
- Browsing level flags/arrays
- NSFW restricted base models
Implementation Plan
Phase 1: Type Definitions
File: event-engine-common/types/image-feed-types.ts
Port necessary types and enums that don't already exist in event-engine-common:
- Image search input filters
- Image sort options
- NSFW/availability enums
- Document types
Phase 2: Schema Definition
File: event-engine-common/feeds/image.feed.ts
Define comprehensive schema matching the current Meilisearch index:
const schema = {
// Primary
id: { type: 'number', primary: true, filterable: true },
// Basic fields
sortAt: { type: 'Date', sortable: true },
sortAtUnix: { type: 'number', filterable: true },
type: { type: 'string', filterable: true },
userId: { type: 'number', filterable: true },
postId: { type: 'number', filterable: true },
// Model/Resource fields
modelVersionIds: { type: 'array', arrayType: 'number', filterable: true },
modelVersionIdsManual: { type: 'array', arrayType: 'number', filterable: true },
postedToId: { type: 'number', filterable: true },
baseModel: { type: 'string', filterable: true },
// NSFW/Content Safety
nsfwLevel: { type: 'number', filterable: true },
combinedNsfwLevel: { type: 'number', filterable: true },
availability: { type: 'string', filterable: true },
blockedFor: { type: 'string', filterable: true },
poi: { type: 'boolean', filterable: true },
minor: { type: 'boolean', filterable: true },
// Tags/Tools/Techniques
tagIds: { type: 'array', arrayType: 'number', filterable: true },
toolIds: { type: 'array', arrayType: 'number', filterable: true },
techniqueIds: { type: 'array', arrayType: 'number', filterable: true },
// Metadata
hasMeta: { type: 'boolean', filterable: true },
onSite: { type: 'boolean', filterable: true },
publishedAtUnix: { type: 'number', filterable: true },
existedAtUnix: { type: 'number', filterable: true },
remixOfId: { type: 'number', filterable: true },
// Flags
'flags.promptNsfw': { type: 'boolean', filterable: true },
// Metrics
reactionCount: { type: 'number', sortable: true },
commentCount: { type: 'number', sortable: true },
collectedCount: { type: 'number', sortable: true },
} as const;
Phase 3: createDocuments Implementation
Replicate the logic from metrics-images.search-index.ts:
- Fetch base image data from PostgreSQL (similar to pullData step 0)
- Fetch metrics from ClickHouse (step 1)
- Fetch tags from cache (step 2)
- Fetch tools/techniques from PostgreSQL (step 3)
- Fetch model versions from PostgreSQL (step 4)
- Transform and combine all data (transformData function)
Key considerations:
- Handle both 'full' and 'metrics' update types
- Use batching for large ID sets
- Proper error handling
Phase 4: queryDocuments Implementation
Replicate filter logic from getImagesFromSearchPostFilter:
-
Build Meilisearch filters:
- NSFW level filtering (browsingLevel → combinedNsfwLevel/nsfwLevel)
- NSFW license restrictions (restricted base models)
- Model version filtering (postedToId, modelVersionIds, modelVersionIdsManual)
- Remix filtering (remixOfId, remixesOnly, nonRemixesOnly)
- Tag/tool/technique filtering
- Type filtering
- Period filtering (sortAtUnix)
- User filtering (userId, excludedUserIds, followed, hidden)
- POI/minor filtering
- Metadata filtering (hasMeta, onSite, requiringMeta)
- Publishing status filtering (publishedAtUnix)
- Moderator features (blockedFor, scheduled, notPublished)
-
Build sort orders:
- Map ImageSort enum to Meilisearch sorts
- Add secondary sort by ID for consistency
-
Handle pagination:
- Use cursor from context
- Return results matching limit
Note: Post-filtering logic (existence checks, permission validation) will be handled in populateDocuments.
Phase 5: populateDocuments Implementation
Enhance documents with additional data:
-
Fetch image metrics from cache (imageMetricsCache)
-
Build stats object with all-time counts:
- likeCountAllTime, heartCountAllTime, etc.
- commentCountAllTime, collectedCountAllTime
- tippedAmountCountAllTime
-
Required enhancements:
- User data (username, profile pictures)
- Tag names
- Resource details
- Cosmetics
-
Post-filtering logic:
- Existence checks (via Redis cache + DB fallback)
- Permission validation (private/blocked content)
- Scheduled post filtering (for non-owners)
- NSFW level validation (unscanned content)
Return Type: Populated image with all necessary data for display in the feed.
Phase 6: Integration & Testing
- Export Feed class:
export const ImageFeed = createFeed({
entityType: 'Image',
name: 'metrics-images',
schema,
createDocuments,
queryDocuments,
populateDocuments,
});
- Test scenarios:
- Basic queries with various filters
- Pagination
- Document creation/updates
- Performance benchmarking vs current implementation
Key Differences from Current Sample
The existing event-engine-common/feeds/image.feed.ts is a simple example. The new implementation will:
- Much larger schema - 30+ fields vs 11 in example
- Complex filtering - 20+ filter types vs 3 in example
- Multi-step data fetching - 5 data sources vs 2 in example
- Advanced pagination - cursor-based with adaptive batching
- Metrics population - full stats object with all reaction types
Migration Strategy
- Parallel implementation - Keep existing code working
- Feature flag - Use existing FEED_POST_FILTER flag to route to new implementation
- Gradual rollout - Test with small percentage of traffic
- Monitoring - Compare performance and results
- Full migration - Once validated, remove old code
Implementation Decisions
Based on feedback:
- Post-filtering logic: Implemented in populateDocuments using ctx.pg, ctx.cache
- prioritizedUserIds: Not implemented (not currently supported in getImagesFromSearchPostFilter)
- Metrics-only update: Optional optimization, may implement later
- Flipt client: Optional interface can be added to Feed context in base.ts for feature-flagged existence checks