1734 lines
110 KiB
JavaScript

"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.closeFriends = exports.privacySettings = exports.userSocialLinks = exports.SOCIAL_PLATFORMS = exports.userGalleryImages = exports.userPresence = exports.videoRecommendations = exports.pokes = exports.userBlocks = exports.friendships = exports.FRIENDSHIP_STATUS = exports.adClicks = exports.adImpressions = exports.ads = exports.playlistViews = exports.featuredPlaylists = exports.playlistVideos = exports.playlists = exports.contentReports = exports.REPORT_STATUS = exports.REPORT_TYPES = exports.moderationWordLists = exports.WORD_FILTER_LEVELS = exports.rateLimits = exports.appSettings = exports.chatThreadReadStatus = exports.userDailyActivity = exports.highlightCooldowns = exports.videoReactions = exports.REACTION_TYPES = exports.userFinishes = exports.userStats = exports.userAchievements = exports.emailChangeTokens = exports.passwordResetTokens = exports.emailVerificationTokens = exports.commentModeration = exports.sessionBans = exports.authTokens = exports.mediaUsers = exports.v2Users = exports.views = exports.comments = exports.upvotes = exports.sessions = exports.publicMedia = exports.jobs = exports.compilations = exports.videos = exports.DIRECTORY_TYPES = void 0;
exports.PIPELINE_STEP_STATUS = exports.PIPELINE_STATUS = exports.performerDiscrepancies = exports.DISCREPANCY_RESOLUTIONS = exports.DISCREPANCY_STATUS = exports.DISCREPANCY_TYPES = exports.performerFaces = exports.FACE_STATUS = exports.creators = exports.tagGenerationJobs = exports.watchPartyInvites = exports.INVITE_STATUS = exports.watchPartyReactions = exports.watchPartyChatMessages = exports.watchPartyParticipants = exports.watchPartySessions = exports.LOOP_MODE = exports.WATCH_PARTY_STATUS = exports.digestClipTags = exports.digestSuggestedTags = exports.SUGGESTED_TAG_STATUS = exports.videoTags = exports.videoSegments = exports.videoTagTimeline = exports.VOCAL_CATEGORIES = exports.SEGMENT_TYPES = exports.videoSceneCuts = exports.digestGeneratedScenes = exports.digestOutputFolders = exports.digestCompilations = exports.CAPTION_SIZES = exports.CAPTION_POSITIONS = exports.digestGeneratedClips = exports.digestSelectedClips = exports.CLIP_SOURCES = exports.CLIP_STATUS = exports.CLIP_TYPES = exports.digestVideoTags = exports.videoDigests = exports.DIGEST_STATUS = exports.publicMediaPerformers = exports.userTagPreferences = exports.userUploadSuggestedTags = exports.publicMediaTags = exports.tags = exports.tagCategories = exports.uploadInvites = exports.UPLOAD_INVITE_STATUS = exports.userUploads = exports.USER_UPLOAD_STATUS = void 0;
exports.videoOcrResults = exports.OCR_TEXT_TYPES = exports.publishedInboxFiles = exports.INBOX_FILE_TYPES = exports.geoBlockingRules = exports.GEO_BLOCKING_MODES = exports.notificationPreferences = exports.notifications = exports.NOTIFICATION_TYPES = exports.paymentAuditLog = exports.payments = exports.invoices = exports.userSubscriptions = exports.subscriptionPlans = exports.pipelineTemplates = exports.resourceSnapshots = exports.pipelineStepEvents = exports.pipelineSteps = exports.pipelines = void 0;
const pg_core_1 = require("drizzle-orm/pg-core");
// Directory type enum for efficient filtering (replaces LIKE patterns)
exports.DIRECTORY_TYPES = ['studios', 'gifs', 'private', 'inbox', 'curated', 'playback', 'compilations', 'videos', 'highlights'];
exports.videos = (0, pg_core_1.pgTable)('videos', {
id: (0, pg_core_1.serial)('id').primaryKey(),
path: (0, pg_core_1.text)('path').notNull().unique(),
filename: (0, pg_core_1.text)('filename').notNull(),
producer: (0, pg_core_1.text)('producer'),
creator: (0, pg_core_1.text)('creator'),
title: (0, pg_core_1.text)('title'),
durationSeconds: (0, pg_core_1.integer)('duration_seconds'),
quality: (0, pg_core_1.text)('quality'),
orientation: (0, pg_core_1.text)('orientation'),
hasAudio: (0, pg_core_1.boolean)('has_audio').default(true),
fileSize: (0, pg_core_1.bigint)('file_size', { mode: 'number' }),
fileHash: (0, pg_core_1.text)('file_hash'),
width: (0, pg_core_1.integer)('width'),
height: (0, pg_core_1.integer)('height'),
lastValidated: (0, pg_core_1.timestamp)('last_validated', { withTimezone: true }),
isValid: (0, pg_core_1.boolean)('is_valid').default(true),
thumbnailPath: (0, pg_core_1.text)('thumbnail_path'),
createdAt: (0, pg_core_1.timestamp)('created_at', { withTimezone: true }).defaultNow(),
tags: (0, pg_core_1.jsonb)('tags').$type(),
// Directory type for efficient filtering (indexed)
directoryType: (0, pg_core_1.text)('directory_type').$type(),
// Historical engagement stats (preserved when moved from public media)
publicViewCount: (0, pg_core_1.integer)('public_view_count'),
publicUpvoteCount: (0, pg_core_1.integer)('public_upvote_count'),
publicCommentCount: (0, pg_core_1.integer)('public_comment_count'),
publicCompletionCount: (0, pg_core_1.integer)('public_completion_count'),
publicTotalWatchTime: (0, pg_core_1.integer)('public_total_watch_time'),
movedFromPublicAt: (0, pg_core_1.timestamp)('moved_from_public_at', { withTimezone: true }),
// Name standardization tracking
originalFilename: (0, pg_core_1.text)('original_filename'),
originalPath: (0, pg_core_1.text)('original_path'),
standardizedAt: (0, pg_core_1.timestamp)('standardized_at', { withTimezone: true }),
}, (table) => ({
orientationIdx: (0, pg_core_1.index)('idx_orientation').on(table.orientation),
producerIdx: (0, pg_core_1.index)('idx_producer').on(table.producer),
isValidIdx: (0, pg_core_1.index)('idx_is_valid').on(table.isValid),
directoryTypeIdx: (0, pg_core_1.index)('idx_directory_type').on(table.directoryType),
fingerprintIdx: (0, pg_core_1.index)('idx_videos_fingerprint').on(table.durationSeconds, table.fileSize, table.width, table.height),
// Composite index for common filtering pattern (directory + valid + orientation)
directoryValidOrientationIdx: (0, pg_core_1.index)('idx_videos_directory_valid_orientation').on(table.directoryType, table.isValid, table.orientation),
}));
exports.compilations = (0, pg_core_1.pgTable)('compilations', {
id: (0, pg_core_1.serial)('id').primaryKey(),
filename: (0, pg_core_1.text)('filename').notNull(),
path: (0, pg_core_1.text)('path'),
durationSeconds: (0, pg_core_1.integer)('duration_seconds'),
videoIds: (0, pg_core_1.jsonb)('video_ids').$type(),
settings: (0, pg_core_1.jsonb)('settings').$type(),
createdAt: (0, pg_core_1.timestamp)('created_at', { withTimezone: true }).defaultNow(),
});
exports.jobs = (0, pg_core_1.pgTable)('jobs', {
id: (0, pg_core_1.serial)('id').primaryKey(),
type: (0, pg_core_1.text)('type').notNull(),
status: (0, pg_core_1.text)('status').default('pending').$type(),
progress: (0, pg_core_1.integer)('progress').default(0),
log: (0, pg_core_1.text)('log'),
params: (0, pg_core_1.jsonb)('params').$type(),
startedAt: (0, pg_core_1.timestamp)('started_at', { withTimezone: true }),
completedAt: (0, pg_core_1.timestamp)('completed_at', { withTimezone: true }),
createdAt: (0, pg_core_1.timestamp)('created_at', { withTimezone: true }).defaultNow(),
// Queue management columns
resourceCategory: (0, pg_core_1.text)('resource_category').default('cpu').$type(),
vramRequired: (0, pg_core_1.integer)('vram_required').default(0),
queuePosition: (0, pg_core_1.integer)('queue_position'),
waitingReason: (0, pg_core_1.text)('waiting_reason'),
priority: (0, pg_core_1.integer)('priority').default(5),
// Pipeline integration (optional - for jobs created via pipeline)
pipelineId: (0, pg_core_1.integer)('pipeline_id'),
pipelineStepId: (0, pg_core_1.integer)('pipeline_step_id'),
}, (table) => ({
queueIdx: (0, pg_core_1.index)('idx_jobs_queue').on(table.status, table.priority, table.createdAt),
resourceIdx: (0, pg_core_1.index)('idx_jobs_resource').on(table.resourceCategory, table.status),
pipelineIdx: (0, pg_core_1.index)('idx_jobs_pipeline').on(table.pipelineId),
}));
// ============================================
// Public Gallery Tables
// ============================================
exports.publicMedia = (0, pg_core_1.pgTable)('public_media', {
id: (0, pg_core_1.serial)('id').primaryKey(),
path: (0, pg_core_1.text)('path').notNull().unique(),
filename: (0, pg_core_1.text)('filename').notNull(),
category: (0, pg_core_1.text)('category').notNull(),
durationSeconds: (0, pg_core_1.integer)('duration_seconds'),
quality: (0, pg_core_1.text)('quality'),
orientation: (0, pg_core_1.text)('orientation'),
thumbnailPath: (0, pg_core_1.text)('thumbnail_path'),
fileSize: (0, pg_core_1.bigint)('file_size', { mode: 'number' }),
// Denormalized counters for performance
viewCount: (0, pg_core_1.integer)('view_count').default(0),
upvoteCount: (0, pg_core_1.integer)('upvote_count').default(0),
commentCount: (0, pg_core_1.integer)('comment_count').default(0),
finishCount: (0, pg_core_1.integer)('finish_count').default(0),
totalWatchTime: (0, pg_core_1.integer)('total_watch_time').default(0),
createdAt: (0, pg_core_1.timestamp)('created_at', { withTimezone: true }).defaultNow(),
// Locked video system
isLocked: (0, pg_core_1.boolean)('is_locked').default(false),
lockedAt: (0, pg_core_1.timestamp)('locked_at', { withTimezone: true }),
lockedBy: (0, pg_core_1.integer)('locked_by'),
// Manual ordering for playlist (curated category)
position: (0, pg_core_1.integer)('position').default(0),
// Track uploader for quickies
uploaderId: (0, pg_core_1.integer)('uploader_id'),
}, (table) => ({
categoryIdx: (0, pg_core_1.index)('idx_public_media_category').on(table.category),
pathIdx: (0, pg_core_1.index)('idx_public_media_path').on(table.path),
isLockedIdx: (0, pg_core_1.index)('idx_public_media_is_locked').on(table.isLocked),
positionIdx: (0, pg_core_1.index)('idx_public_media_position').on(table.position),
// Quickies performance indexes
uploaderIdx: (0, pg_core_1.index)('idx_public_media_uploader').on(table.uploaderId),
categoryDateIdx: (0, pg_core_1.index)('idx_public_media_category_date').on(table.category, table.createdAt),
orientationIdx: (0, pg_core_1.index)('idx_public_media_orientation').on(table.orientation),
// Composite index for common filtering (category + locked + date)
categoryLockedDateIdx: (0, pg_core_1.index)('idx_public_media_category_locked_date').on(table.category, table.isLocked, table.createdAt),
}));
exports.sessions = (0, pg_core_1.pgTable)('sessions', {
id: (0, pg_core_1.text)('id').primaryKey(), // UUID from client
createdAt: (0, pg_core_1.timestamp)('created_at', { withTimezone: true }).defaultNow(),
lastSeenAt: (0, pg_core_1.timestamp)('last_seen_at', { withTimezone: true }),
// Device tracking
ipAddress: (0, pg_core_1.text)('ip_address'),
userAgent: (0, pg_core_1.text)('user_agent'),
deviceType: (0, pg_core_1.text)('device_type'), // 'desktop' | 'mobile' | 'tablet'
browser: (0, pg_core_1.text)('browser'),
os: (0, pg_core_1.text)('os'),
// Geography (from GeoIP)
country: (0, pg_core_1.text)('country'), // ISO code: 'US'
countryName: (0, pg_core_1.text)('country_name'), // 'United States'
region: (0, pg_core_1.text)('region'), // 'CA'
city: (0, pg_core_1.text)('city'), // 'Los Angeles'
timezone: (0, pg_core_1.text)('timezone'), // 'America/Los_Angeles'
latitude: (0, pg_core_1.real)('latitude'),
longitude: (0, pg_core_1.real)('longitude'),
// User correlation
userId: (0, pg_core_1.integer)('user_id'), // Links session to authenticated user
// Analytics
firstSeenAt: (0, pg_core_1.timestamp)('first_seen_at', { withTimezone: true }), // For return visitor detection
visitCount: (0, pg_core_1.integer)('visit_count').default(1), // Increment on return visits
}, (table) => ({
userIdIdx: (0, pg_core_1.index)('idx_sessions_user_id').on(table.userId),
countryIdx: (0, pg_core_1.index)('idx_sessions_country').on(table.country),
}));
exports.upvotes = (0, pg_core_1.pgTable)('upvotes', {
id: (0, pg_core_1.serial)('id').primaryKey(),
mediaId: (0, pg_core_1.integer)('media_id').notNull(),
sessionId: (0, pg_core_1.text)('session_id').notNull(),
createdAt: (0, pg_core_1.timestamp)('created_at', { withTimezone: true }).defaultNow(),
}, (table) => ({
uniqueVoteIdx: (0, pg_core_1.index)('idx_upvotes_unique').on(table.mediaId, table.sessionId),
mediaIdx: (0, pg_core_1.index)('idx_upvotes_media').on(table.mediaId),
}));
exports.comments = (0, pg_core_1.pgTable)('comments', {
id: (0, pg_core_1.serial)('id').primaryKey(),
mediaId: (0, pg_core_1.integer)('media_id').notNull(),
sessionId: (0, pg_core_1.text)('session_id').notNull(),
userId: (0, pg_core_1.integer)('user_id'),
content: (0, pg_core_1.text)('content').notNull(),
createdAt: (0, pg_core_1.timestamp)('created_at', { withTimezone: true }).defaultNow(),
// Content safety fields for LLM-based moderation
safetyStatus: (0, pg_core_1.text)('safety_status').default('pending'),
safetyCheckedAt: (0, pg_core_1.timestamp)('safety_checked_at', { withTimezone: true }),
safetyCategories: (0, pg_core_1.jsonb)('safety_categories').$type(),
safetyReasoning: (0, pg_core_1.text)('safety_reasoning'),
// Hidden comment fields for auto-moderation
isHidden: (0, pg_core_1.boolean)('is_hidden').default(false),
hiddenAt: (0, pg_core_1.timestamp)('hidden_at', { withTimezone: true }),
hiddenReason: (0, pg_core_1.text)('hidden_reason'),
// Admin moderation notes (internal, not shown to users)
moderationNotes: (0, pg_core_1.text)('moderation_notes'),
}, (table) => ({
mediaIdx: (0, pg_core_1.index)('idx_comments_media').on(table.mediaId),
sessionIdx: (0, pg_core_1.index)('idx_comments_session').on(table.sessionId),
userIdx: (0, pg_core_1.index)('idx_comments_user').on(table.userId),
safetyStatusIdx: (0, pg_core_1.index)('idx_comments_safety_status').on(table.safetyStatus),
isHiddenIdx: (0, pg_core_1.index)('idx_comments_is_hidden').on(table.isHidden),
}));
exports.views = (0, pg_core_1.pgTable)('views', {
id: (0, pg_core_1.serial)('id').primaryKey(),
mediaId: (0, pg_core_1.integer)('media_id').notNull(),
sessionId: (0, pg_core_1.text)('session_id').notNull(),
watchTimeSeconds: (0, pg_core_1.integer)('watch_time_seconds').default(0),
lastUpdated: (0, pg_core_1.timestamp)('last_updated', { withTimezone: true }).defaultNow(),
createdAt: (0, pg_core_1.timestamp)('created_at', { withTimezone: true }).defaultNow(),
}, (table) => ({
uniqueViewIdx: (0, pg_core_1.index)('idx_views_unique').on(table.mediaId, table.sessionId),
mediaIdx: (0, pg_core_1.index)('idx_views_media').on(table.mediaId),
}));
// ============================================
// User Management Tables
// ============================================
// V2 Users table (for authentication only)
// This matches the Prisma User model to allow Drizzle queries for auth
// Table name: 'users' (created by Prisma migrations)
exports.v2Users = (0, pg_core_1.pgTable)('users', {
id: (0, pg_core_1.text)('id').primaryKey(),
email: (0, pg_core_1.text)('email').notNull().unique(),
password: (0, pg_core_1.text)('password').notNull(),
name: (0, pg_core_1.text)('name'),
phone: (0, pg_core_1.text)('phone'),
role: (0, pg_core_1.text)('role').notNull().default('USER'),
status: (0, pg_core_1.text)('status').notNull().default('ACTIVE'),
permissions: (0, pg_core_1.jsonb)('permissions'),
createdVia: (0, pg_core_1.text)('createdVia').notNull().default('STANDARD'),
expiresAt: (0, pg_core_1.timestamp)('expiresAt', { withTimezone: true }),
expireDays: (0, pg_core_1.integer)('expireDays'),
lastLoginAt: (0, pg_core_1.timestamp)('lastLoginAt', { withTimezone: true }),
emailVerified: (0, pg_core_1.boolean)('emailVerified').notNull().default(false),
createdAt: (0, pg_core_1.timestamp)('createdAt', { withTimezone: true }).notNull().defaultNow(),
updatedAt: (0, pg_core_1.timestamp)('updatedAt', { withTimezone: true }).notNull().defaultNow(),
}, (table) => ({
emailIdx: (0, pg_core_1.index)('User_email_key').on(table.email),
}));
// Media-specific users table (for future media-api user management)
// NOTE: This is separate from v2Users and not currently used
// If/when media-api needs its own user system, this table can be created
exports.mediaUsers = (0, pg_core_1.pgTable)('media_users', {
id: (0, pg_core_1.serial)('id').primaryKey(),
username: (0, pg_core_1.text)('username').notNull().unique(),
email: (0, pg_core_1.text)('email').notNull().unique(),
passwordHash: (0, pg_core_1.text)('password_hash').notNull(),
role: (0, pg_core_1.text)('role').notNull().default('user'),
status: (0, pg_core_1.text)('status').notNull().default('pending'),
emailVerified: (0, pg_core_1.boolean)('email_verified').default(false),
linkedSessionId: (0, pg_core_1.text)('linked_session_id'),
createdAt: (0, pg_core_1.timestamp)('created_at', { withTimezone: true }).defaultNow(),
approvedAt: (0, pg_core_1.timestamp)('approved_at', { withTimezone: true }),
approvedBy: (0, pg_core_1.integer)('approved_by'),
lastLoginAt: (0, pg_core_1.timestamp)('last_login_at', { withTimezone: true }),
// Trusted user fields
isTrusted: (0, pg_core_1.boolean)('is_trusted').default(false),
maxUploadDurationSeconds: (0, pg_core_1.integer)('max_upload_duration_seconds').default(60),
autoAcceptUploads: (0, pg_core_1.boolean)('auto_accept_uploads').default(false),
trustedAt: (0, pg_core_1.timestamp)('trusted_at', { withTimezone: true }),
trustedBy: (0, pg_core_1.integer)('trusted_by'),
defaultPlaylistId: (0, pg_core_1.integer)('default_playlist_id'),
// Social links visibility
socialLinksPublic: (0, pg_core_1.boolean)('social_links_public').default(false),
// Subscription fields
subscriptionStatus: (0, pg_core_1.text)('subscription_status').default('none'), // none, active, grace_period, delinquent, lifetime
subscriptionPlanId: (0, pg_core_1.integer)('subscription_plan_id'),
subscriptionEndDate: (0, pg_core_1.timestamp)('subscription_end_date', { withTimezone: true }),
lifetimeMember: (0, pg_core_1.boolean)('lifetime_member').default(false),
totalPaidCAD: (0, pg_core_1.integer)('total_paid_cad').default(0), // Lifetime total in cents
// Moderation tracking
lastModerationAction: (0, pg_core_1.timestamp)('last_moderation_action', { withTimezone: true }),
}, (table) => ({
emailIdx: (0, pg_core_1.index)('idx_media_users_email').on(table.email),
usernameIdx: (0, pg_core_1.index)('idx_media_users_username').on(table.username),
statusIdx: (0, pg_core_1.index)('idx_media_users_status').on(table.status),
trustedIdx: (0, pg_core_1.index)('idx_media_users_trusted').on(table.isTrusted),
subscriptionStatusIdx: (0, pg_core_1.index)('idx_media_users_subscription_status').on(table.subscriptionStatus),
}));
exports.authTokens = (0, pg_core_1.pgTable)('auth_tokens', {
id: (0, pg_core_1.serial)('id').primaryKey(),
userId: (0, pg_core_1.integer)('user_id').notNull(),
token: (0, pg_core_1.text)('token').notNull().unique(),
type: (0, pg_core_1.text)('type').notNull(),
expiresAt: (0, pg_core_1.timestamp)('expires_at', { withTimezone: true }).notNull(),
createdAt: (0, pg_core_1.timestamp)('created_at', { withTimezone: true }).defaultNow(),
}, (table) => ({
tokenIdx: (0, pg_core_1.index)('idx_auth_tokens_token').on(table.token),
userIdx: (0, pg_core_1.index)('idx_auth_tokens_user').on(table.userId),
}));
exports.sessionBans = (0, pg_core_1.pgTable)('session_bans', {
id: (0, pg_core_1.serial)('id').primaryKey(),
sessionId: (0, pg_core_1.text)('session_id').notNull(),
reason: (0, pg_core_1.text)('reason'),
bannedBy: (0, pg_core_1.integer)('banned_by'),
createdAt: (0, pg_core_1.timestamp)('created_at', { withTimezone: true }).defaultNow(),
expiresAt: (0, pg_core_1.timestamp)('expires_at', { withTimezone: true }), // null = permanent
}, (table) => ({
sessionIdx: (0, pg_core_1.index)('idx_session_bans_session').on(table.sessionId),
}));
exports.commentModeration = (0, pg_core_1.pgTable)('comment_moderation', {
id: (0, pg_core_1.serial)('id').primaryKey(),
commentId: (0, pg_core_1.integer)('comment_id').notNull(),
status: (0, pg_core_1.text)('status').notNull().default('pending'),
moderatedBy: (0, pg_core_1.integer)('moderated_by'),
moderatedAt: (0, pg_core_1.timestamp)('moderated_at', { withTimezone: true }),
reason: (0, pg_core_1.text)('reason'),
}, (table) => ({
commentIdx: (0, pg_core_1.index)('idx_comment_moderation_comment').on(table.commentId),
statusIdx: (0, pg_core_1.index)('idx_comment_moderation_status').on(table.status),
}));
// Email Verification Tokens
exports.emailVerificationTokens = (0, pg_core_1.pgTable)('email_verification_tokens', {
id: (0, pg_core_1.serial)('id').primaryKey(),
userId: (0, pg_core_1.integer)('user_id').notNull(),
token: (0, pg_core_1.text)('token').notNull().unique(),
expiresAt: (0, pg_core_1.timestamp)('expires_at', { withTimezone: true }).notNull(),
createdAt: (0, pg_core_1.timestamp)('created_at', { withTimezone: true }).defaultNow(),
}, (table) => ({
tokenIdx: (0, pg_core_1.index)('idx_email_verification_tokens_token').on(table.token),
userIdx: (0, pg_core_1.index)('idx_email_verification_tokens_user').on(table.userId),
}));
// Password Reset Tokens
exports.passwordResetTokens = (0, pg_core_1.pgTable)('password_reset_tokens', {
id: (0, pg_core_1.serial)('id').primaryKey(),
userId: (0, pg_core_1.integer)('user_id').notNull(),
token: (0, pg_core_1.text)('token').notNull().unique(),
expiresAt: (0, pg_core_1.timestamp)('expires_at', { withTimezone: true }).notNull(),
createdAt: (0, pg_core_1.timestamp)('created_at', { withTimezone: true }).defaultNow(),
usedAt: (0, pg_core_1.timestamp)('used_at', { withTimezone: true }),
}, (table) => ({
tokenIdx: (0, pg_core_1.index)('idx_password_reset_tokens_token').on(table.token),
userIdx: (0, pg_core_1.index)('idx_password_reset_tokens_user').on(table.userId),
}));
// Email Change Tokens
exports.emailChangeTokens = (0, pg_core_1.pgTable)('email_change_tokens', {
id: (0, pg_core_1.serial)('id').primaryKey(),
userId: (0, pg_core_1.integer)('user_id').notNull(),
newEmail: (0, pg_core_1.text)('new_email').notNull(),
token: (0, pg_core_1.text)('token').notNull().unique(),
expiresAt: (0, pg_core_1.timestamp)('expires_at', { withTimezone: true }).notNull(),
createdAt: (0, pg_core_1.timestamp)('created_at', { withTimezone: true }).defaultNow(),
}, (table) => ({
tokenIdx: (0, pg_core_1.index)('idx_email_change_tokens_token').on(table.token),
userIdx: (0, pg_core_1.index)('idx_email_change_tokens_user').on(table.userId),
}));
// ============================================
// Achievement & Dashboard Tables
// ============================================
exports.userAchievements = (0, pg_core_1.pgTable)('user_achievements', {
id: (0, pg_core_1.serial)('id').primaryKey(),
userId: (0, pg_core_1.integer)('user_id').notNull(),
achievementId: (0, pg_core_1.text)('achievement_id').notNull(),
unlockedAt: (0, pg_core_1.timestamp)('unlocked_at', { withTimezone: true }).notNull(),
progress: (0, pg_core_1.integer)('progress').default(0),
notified: (0, pg_core_1.boolean)('notified').default(false),
}, (table) => ({
userIdx: (0, pg_core_1.index)('idx_user_achievements_user').on(table.userId),
// Unique constraint for ON CONFLICT support in batch inserts
userAchievementUnique: (0, pg_core_1.uniqueIndex)('idx_user_achievements_unique').on(table.userId, table.achievementId),
}));
exports.userStats = (0, pg_core_1.pgTable)('user_stats', {
id: (0, pg_core_1.serial)('id').primaryKey(),
userId: (0, pg_core_1.integer)('user_id').notNull().unique(),
totalWatchTimeSeconds: (0, pg_core_1.integer)('total_watch_time_seconds').default(0),
totalVideosWatched: (0, pg_core_1.integer)('total_videos_watched').default(0),
totalUpvotesGiven: (0, pg_core_1.integer)('total_upvotes_given').default(0),
totalCommentsMade: (0, pg_core_1.integer)('total_comments_made').default(0),
totalFinishes: (0, pg_core_1.integer)('total_finishes').default(0),
currentDayStreak: (0, pg_core_1.integer)('current_day_streak').default(0),
longestDayStreak: (0, pg_core_1.integer)('longest_day_streak').default(0),
lastActiveDate: (0, pg_core_1.text)('last_active_date'), // YYYY-MM-DD format
longestSingleSession: (0, pg_core_1.integer)('longest_single_session').default(0),
categoriesCompleted: (0, pg_core_1.jsonb)('categories_completed').$type(),
nightOwlCount: (0, pg_core_1.integer)('night_owl_count').default(0),
earlyBirdCount: (0, pg_core_1.integer)('early_bird_count').default(0),
updatedAt: (0, pg_core_1.timestamp)('updated_at', { withTimezone: true }),
});
exports.userFinishes = (0, pg_core_1.pgTable)('user_finishes', {
id: (0, pg_core_1.serial)('id').primaryKey(),
userId: (0, pg_core_1.integer)('user_id').notNull(),
mediaId: (0, pg_core_1.integer)('media_id'),
sessionId: (0, pg_core_1.text)('session_id'),
createdAt: (0, pg_core_1.timestamp)('created_at', { withTimezone: true }).notNull(),
}, (table) => ({
userIdx: (0, pg_core_1.index)('idx_user_finishes_user').on(table.userId),
dateIdx: (0, pg_core_1.index)('idx_user_finishes_date').on(table.createdAt),
}));
// ============================================
// Video Reactions Table
// ============================================
exports.REACTION_TYPES = ['like', 'love', 'laugh', 'wow', 'sad', 'angry'];
exports.videoReactions = (0, pg_core_1.pgTable)('video_reactions', {
id: (0, pg_core_1.serial)('id').primaryKey(),
userId: (0, pg_core_1.integer)('user_id').notNull(),
mediaId: (0, pg_core_1.integer)('media_id').notNull(),
reactionType: (0, pg_core_1.text)('reaction_type').notNull(),
videoTimestamp: (0, pg_core_1.integer)('video_timestamp').notNull(), // seconds into the video when reaction was clicked
createdAt: (0, pg_core_1.timestamp)('created_at', { withTimezone: true }).notNull(),
}, (table) => ({
userMediaTypeIdx: (0, pg_core_1.index)('idx_video_reactions_user_media_type').on(table.userId, table.mediaId, table.reactionType),
mediaTimestampIdx: (0, pg_core_1.index)('idx_video_reactions_media_timestamp').on(table.mediaId, table.videoTimestamp),
mediaIdx: (0, pg_core_1.index)('idx_video_reactions_media').on(table.mediaId),
createdAtIdx: (0, pg_core_1.index)('idx_video_reactions_created').on(table.createdAt),
}));
// ============================================
// Quickie Generation Cooldowns Table
// ============================================
exports.highlightCooldowns = (0, pg_core_1.pgTable)('highlight_cooldowns', {
id: (0, pg_core_1.serial)('id').primaryKey(),
userId: (0, pg_core_1.integer)('user_id').notNull(),
lastGeneratedAt: (0, pg_core_1.timestamp)('last_generated_at', { withTimezone: true }).notNull(),
}, (table) => ({
userIdx: (0, pg_core_1.index)('idx_highlight_cooldowns_user').on(table.userId),
}));
exports.userDailyActivity = (0, pg_core_1.pgTable)('user_daily_activity', {
id: (0, pg_core_1.serial)('id').primaryKey(),
userId: (0, pg_core_1.integer)('user_id').notNull(),
activityDate: (0, pg_core_1.text)('activity_date').notNull(), // YYYY-MM-DD
watchTimeSeconds: (0, pg_core_1.integer)('watch_time_seconds').default(0),
videosWatched: (0, pg_core_1.integer)('videos_watched').default(0),
firstActivityHour: (0, pg_core_1.integer)('first_activity_hour'),
createdAt: (0, pg_core_1.timestamp)('created_at', { withTimezone: true }),
}, (table) => ({
uniqueIdx: (0, pg_core_1.index)('idx_user_daily_activity_unique').on(table.userId, table.activityDate),
}));
// ============================================
// Chat Thread Tracking Table
// ============================================
exports.chatThreadReadStatus = (0, pg_core_1.pgTable)('chat_thread_read_status', {
id: (0, pg_core_1.serial)('id').primaryKey(),
userId: (0, pg_core_1.integer)('user_id').notNull(),
mediaId: (0, pg_core_1.integer)('media_id').notNull(),
lastSeenAt: (0, pg_core_1.timestamp)('last_seen_at', { withTimezone: true }).notNull(),
}, (table) => ({
uniqueIdx: (0, pg_core_1.index)('idx_chat_thread_read_unique').on(table.userId, table.mediaId),
userIdx: (0, pg_core_1.index)('idx_chat_thread_read_user').on(table.userId),
}));
// ============================================
// App Settings Table
// ============================================
exports.appSettings = (0, pg_core_1.pgTable)('app_settings', {
key: (0, pg_core_1.text)('key').primaryKey(),
value: (0, pg_core_1.text)('value').notNull(),
updatedAt: (0, pg_core_1.timestamp)('updated_at', { withTimezone: true }).defaultNow(),
});
// ============================================
// Rate Limiting Table (for persistent rate limits)
// ============================================
exports.rateLimits = (0, pg_core_1.pgTable)('rate_limits', {
id: (0, pg_core_1.serial)('id').primaryKey(),
key: (0, pg_core_1.text)('key').notNull().unique(),
count: (0, pg_core_1.integer)('count').notNull().default(1),
resetAt: (0, pg_core_1.timestamp)('reset_at', { withTimezone: true }).notNull(),
}, (table) => ({
keyIdx: (0, pg_core_1.uniqueIndex)('idx_rate_limits_key').on(table.key),
resetAtIdx: (0, pg_core_1.index)('idx_rate_limits_reset_at').on(table.resetAt),
}));
// ============================================
// Moderation Word Lists Table
// ============================================
exports.WORD_FILTER_LEVELS = ['low', 'medium', 'high', 'custom'];
exports.moderationWordLists = (0, pg_core_1.pgTable)('moderation_word_lists', {
id: (0, pg_core_1.serial)('id').primaryKey(),
level: (0, pg_core_1.text)('level').notNull(),
word: (0, pg_core_1.text)('word').notNull(),
createdAt: (0, pg_core_1.timestamp)('created_at', { withTimezone: true }).defaultNow(),
createdBy: (0, pg_core_1.integer)('created_by'),
}, (table) => ({
levelIdx: (0, pg_core_1.index)('idx_moderation_word_lists_level').on(table.level),
wordIdx: (0, pg_core_1.index)('idx_moderation_word_lists_word').on(table.word),
}));
// ============================================
// Content Reports Table
// ============================================
exports.REPORT_TYPES = ['inappropriate', 'spam', 'copyright', 'illegal', 'false_info', 'other'];
exports.REPORT_STATUS = ['pending', 'reviewed', 'actioned', 'dismissed'];
exports.contentReports = (0, pg_core_1.pgTable)('content_reports', {
id: (0, pg_core_1.serial)('id').primaryKey(),
mediaId: (0, pg_core_1.integer)('media_id').notNull(),
sessionId: (0, pg_core_1.text)('session_id'),
userId: (0, pg_core_1.integer)('user_id'),
reportType: (0, pg_core_1.text)('report_type').notNull(),
description: (0, pg_core_1.text)('description'),
status: (0, pg_core_1.text)('status').notNull().default('pending'),
resolvedBy: (0, pg_core_1.integer)('resolved_by'),
resolvedAt: (0, pg_core_1.timestamp)('resolved_at', { withTimezone: true }),
resolutionNotes: (0, pg_core_1.text)('resolution_notes'),
createdAt: (0, pg_core_1.timestamp)('created_at', { withTimezone: true }).defaultNow(),
}, (table) => ({
mediaIdx: (0, pg_core_1.index)('idx_content_reports_media').on(table.mediaId),
statusIdx: (0, pg_core_1.index)('idx_content_reports_status').on(table.status),
sessionIdx: (0, pg_core_1.index)('idx_content_reports_session').on(table.sessionId),
createdAtIdx: (0, pg_core_1.index)('idx_content_reports_created').on(table.createdAt),
}));
// ============================================
// Playlist Tables
// ============================================
exports.playlists = (0, pg_core_1.pgTable)('playlists', {
id: (0, pg_core_1.serial)('id').primaryKey(),
userId: (0, pg_core_1.integer)('user_id').notNull(),
name: (0, pg_core_1.text)('name').notNull(),
description: (0, pg_core_1.text)('description'),
isPublic: (0, pg_core_1.boolean)('is_public').default(false),
shareToken: (0, pg_core_1.text)('share_token').unique(),
thumbnailMediaId: (0, pg_core_1.integer)('thumbnail_media_id'),
// Denormalized counters
videoCount: (0, pg_core_1.integer)('video_count').default(0),
totalDurationSeconds: (0, pg_core_1.integer)('total_duration_seconds').default(0),
viewCount: (0, pg_core_1.integer)('view_count').default(0),
createdAt: (0, pg_core_1.timestamp)('created_at', { withTimezone: true }).defaultNow(),
updatedAt: (0, pg_core_1.timestamp)('updated_at', { withTimezone: true }),
}, (table) => ({
userIdx: (0, pg_core_1.index)('idx_playlists_user').on(table.userId),
publicIdx: (0, pg_core_1.index)('idx_playlists_public').on(table.isPublic),
shareTokenIdx: (0, pg_core_1.index)('idx_playlists_share_token').on(table.shareToken),
// Prevent duplicate playlist names per user (e.g., multiple "My Quickies")
userNameIdx: (0, pg_core_1.uniqueIndex)('idx_playlists_user_name').on(table.userId, table.name),
}));
exports.playlistVideos = (0, pg_core_1.pgTable)('playlist_videos', {
id: (0, pg_core_1.serial)('id').primaryKey(),
playlistId: (0, pg_core_1.integer)('playlist_id').notNull(),
mediaId: (0, pg_core_1.integer)('media_id').notNull(),
position: (0, pg_core_1.integer)('position').notNull().default(0),
addedAt: (0, pg_core_1.timestamp)('added_at', { withTimezone: true }).defaultNow(),
}, (table) => ({
playlistIdx: (0, pg_core_1.index)('idx_playlist_videos_playlist').on(table.playlistId),
mediaIdx: (0, pg_core_1.index)('idx_playlist_videos_media').on(table.mediaId),
uniqueIdx: (0, pg_core_1.index)('idx_playlist_videos_unique').on(table.playlistId, table.mediaId),
}));
exports.featuredPlaylists = (0, pg_core_1.pgTable)('featured_playlists', {
id: (0, pg_core_1.serial)('id').primaryKey(),
playlistId: (0, pg_core_1.integer)('playlist_id').notNull().unique(),
position: (0, pg_core_1.integer)('position').notNull().default(0),
featuredBy: (0, pg_core_1.integer)('featured_by'),
featuredAt: (0, pg_core_1.timestamp)('featured_at', { withTimezone: true }),
}, (table) => ({
positionIdx: (0, pg_core_1.index)('idx_featured_playlists_position').on(table.position),
}));
exports.playlistViews = (0, pg_core_1.pgTable)('playlist_views', {
id: (0, pg_core_1.serial)('id').primaryKey(),
playlistId: (0, pg_core_1.integer)('playlist_id').notNull(),
sessionId: (0, pg_core_1.text)('session_id').notNull(),
createdAt: (0, pg_core_1.timestamp)('created_at', { withTimezone: true }).defaultNow(),
}, (table) => ({
playlistIdx: (0, pg_core_1.index)('idx_playlist_views_playlist').on(table.playlistId),
uniqueIdx: (0, pg_core_1.index)('idx_playlist_views_unique').on(table.playlistId, table.sessionId),
}));
// ============================================
// Advertisement Tables
// ============================================
exports.ads = (0, pg_core_1.pgTable)('ads', {
id: (0, pg_core_1.serial)('id').primaryKey(),
type: (0, pg_core_1.text)('type').notNull(),
// For make_account: variant identifier (e.g., 'playlist_promo', 'chat_promo')
// For custom: null (uses imagePath instead)
variant: (0, pg_core_1.text)('variant'),
// Custom ad fields
imagePath: (0, pg_core_1.text)('image_path'),
linkUrl: (0, pg_core_1.text)('link_url'),
title: (0, pg_core_1.text)('title'), // For alt text and admin reference
// Visibility: 'everyone', 'members_only', 'non_members'
visibility: (0, pg_core_1.text)('visibility').notNull().default('everyone'),
// Status
isActive: (0, pg_core_1.boolean)('is_active').default(true),
// Ordering for rotation
position: (0, pg_core_1.integer)('position').default(0),
// Denormalized counters for performance
impressionCount: (0, pg_core_1.integer)('impression_count').default(0),
clickCount: (0, pg_core_1.integer)('click_count').default(0),
// Timestamps
createdAt: (0, pg_core_1.timestamp)('created_at', { withTimezone: true }).defaultNow(),
updatedAt: (0, pg_core_1.timestamp)('updated_at', { withTimezone: true }),
}, (table) => ({
typeIdx: (0, pg_core_1.index)('idx_ads_type').on(table.type),
isActiveIdx: (0, pg_core_1.index)('idx_ads_is_active').on(table.isActive),
visibilityIdx: (0, pg_core_1.index)('idx_ads_visibility').on(table.visibility),
}));
exports.adImpressions = (0, pg_core_1.pgTable)('ad_impressions', {
id: (0, pg_core_1.serial)('id').primaryKey(),
adId: (0, pg_core_1.integer)('ad_id').notNull().references(() => exports.ads.id, { onDelete: 'cascade' }),
sessionId: (0, pg_core_1.text)('session_id'),
userId: (0, pg_core_1.integer)('user_id'),
createdAt: (0, pg_core_1.timestamp)('created_at', { withTimezone: true }).defaultNow(),
}, (table) => ({
adIdx: (0, pg_core_1.index)('idx_ad_impressions_ad').on(table.adId),
sessionIdx: (0, pg_core_1.index)('idx_ad_impressions_session').on(table.sessionId),
dateIdx: (0, pg_core_1.index)('idx_ad_impressions_date').on(table.createdAt),
}));
exports.adClicks = (0, pg_core_1.pgTable)('ad_clicks', {
id: (0, pg_core_1.serial)('id').primaryKey(),
adId: (0, pg_core_1.integer)('ad_id').notNull().references(() => exports.ads.id, { onDelete: 'cascade' }),
sessionId: (0, pg_core_1.text)('session_id'),
userId: (0, pg_core_1.integer)('user_id'),
createdAt: (0, pg_core_1.timestamp)('created_at', { withTimezone: true }).defaultNow(),
}, (table) => ({
adIdx: (0, pg_core_1.index)('idx_ad_clicks_ad').on(table.adId),
sessionIdx: (0, pg_core_1.index)('idx_ad_clicks_session').on(table.sessionId),
dateIdx: (0, pg_core_1.index)('idx_ad_clicks_date').on(table.createdAt),
}));
// ============================================
// Friends & Social Tables
// ============================================
exports.FRIENDSHIP_STATUS = ['pending', 'accepted', 'declined'];
exports.friendships = (0, pg_core_1.pgTable)('friendships', {
id: (0, pg_core_1.serial)('id').primaryKey(),
userId: (0, pg_core_1.integer)('user_id').notNull(),
friendId: (0, pg_core_1.integer)('friend_id').notNull(),
status: (0, pg_core_1.text)('status').notNull().default('pending'),
createdAt: (0, pg_core_1.timestamp)('created_at', { withTimezone: true }).defaultNow(),
acceptedAt: (0, pg_core_1.timestamp)('accepted_at', { withTimezone: true }),
}, (table) => ({
userFriendIdx: (0, pg_core_1.index)('idx_friendships_user_friend').on(table.userId, table.friendId),
userIdx: (0, pg_core_1.index)('idx_friendships_user').on(table.userId),
friendIdx: (0, pg_core_1.index)('idx_friendships_friend').on(table.friendId),
statusIdx: (0, pg_core_1.index)('idx_friendships_status').on(table.status),
}));
exports.userBlocks = (0, pg_core_1.pgTable)('user_blocks', {
id: (0, pg_core_1.serial)('id').primaryKey(),
userId: (0, pg_core_1.integer)('user_id').notNull(),
blockedUserId: (0, pg_core_1.integer)('blocked_user_id').notNull(),
createdAt: (0, pg_core_1.timestamp)('created_at', { withTimezone: true }).defaultNow(),
}, (table) => ({
uniqueBlockIdx: (0, pg_core_1.index)('idx_user_blocks_unique').on(table.userId, table.blockedUserId),
userIdx: (0, pg_core_1.index)('idx_user_blocks_user').on(table.userId),
}));
exports.pokes = (0, pg_core_1.pgTable)('pokes', {
id: (0, pg_core_1.serial)('id').primaryKey(),
fromUserId: (0, pg_core_1.integer)('from_user_id').notNull(),
toUserId: (0, pg_core_1.integer)('to_user_id').notNull(),
isRead: (0, pg_core_1.boolean)('is_read').default(false),
createdAt: (0, pg_core_1.timestamp)('created_at', { withTimezone: true }).defaultNow(),
}, (table) => ({
toUserIdx: (0, pg_core_1.index)('idx_pokes_to_user').on(table.toUserId),
fromUserIdx: (0, pg_core_1.index)('idx_pokes_from_user').on(table.fromUserId),
}));
// Video recommendations - share videos with friends
exports.videoRecommendations = (0, pg_core_1.pgTable)('video_recommendations', {
id: (0, pg_core_1.serial)('id').primaryKey(),
fromUserId: (0, pg_core_1.integer)('from_user_id').notNull(),
toUserId: (0, pg_core_1.integer)('to_user_id').notNull(),
mediaId: (0, pg_core_1.integer)('media_id').notNull(),
message: (0, pg_core_1.text)('message'),
isRead: (0, pg_core_1.boolean)('is_read').default(false),
createdAt: (0, pg_core_1.timestamp)('created_at', { withTimezone: true }).defaultNow(),
}, (table) => ({
toUserIdx: (0, pg_core_1.index)('idx_video_recommendations_to_user').on(table.toUserId),
fromUserIdx: (0, pg_core_1.index)('idx_video_recommendations_from_user').on(table.fromUserId),
mediaIdx: (0, pg_core_1.index)('idx_video_recommendations_media').on(table.mediaId),
}));
exports.userPresence = (0, pg_core_1.pgTable)('user_presence', {
id: (0, pg_core_1.serial)('id').primaryKey(),
userId: (0, pg_core_1.integer)('user_id').notNull().unique(),
isOnline: (0, pg_core_1.boolean)('is_online').default(false),
currentMediaId: (0, pg_core_1.integer)('current_media_id'),
lastActivityAt: (0, pg_core_1.timestamp)('last_activity_at', { withTimezone: true }),
lastVideoChangeAt: (0, pg_core_1.timestamp)('last_video_change_at', { withTimezone: true }),
}, (table) => ({
onlineIdx: (0, pg_core_1.index)('idx_user_presence_online').on(table.isOnline),
userIdx: (0, pg_core_1.index)('idx_user_presence_user').on(table.userId),
}));
exports.userGalleryImages = (0, pg_core_1.pgTable)('user_gallery_images', {
id: (0, pg_core_1.serial)('id').primaryKey(),
userId: (0, pg_core_1.integer)('user_id').notNull(),
filename: (0, pg_core_1.text)('filename').notNull(),
originalFilename: (0, pg_core_1.text)('original_filename'),
position: (0, pg_core_1.integer)('position').notNull().default(0),
uploadedAt: (0, pg_core_1.timestamp)('uploaded_at', { withTimezone: true }).defaultNow(),
}, (table) => ({
userIdx: (0, pg_core_1.index)('idx_user_gallery_user').on(table.userId),
positionIdx: (0, pg_core_1.index)('idx_user_gallery_position').on(table.userId, table.position),
}));
// Social link platforms
exports.SOCIAL_PLATFORMS = [
'twitter', 'instagram', 'onlyfans', 'fansly', 'reddit',
'discord', 'tiktok', 'youtube', 'twitch', 'snapchat',
'linktree', 'custom'
];
exports.userSocialLinks = (0, pg_core_1.pgTable)('user_social_links', {
id: (0, pg_core_1.serial)('id').primaryKey(),
userId: (0, pg_core_1.integer)('user_id').notNull(),
platform: (0, pg_core_1.text)('platform').notNull(), // One of SOCIAL_PLATFORMS
url: (0, pg_core_1.text)('url').notNull(),
displayName: (0, pg_core_1.text)('display_name'), // Optional custom label
position: (0, pg_core_1.integer)('position').notNull().default(0),
createdAt: (0, pg_core_1.timestamp)('created_at', { withTimezone: true }).defaultNow(),
}, (table) => ({
userIdx: (0, pg_core_1.index)('idx_user_social_links_user').on(table.userId),
positionIdx: (0, pg_core_1.index)('idx_user_social_links_position').on(table.userId, table.position),
}));
// ============================================
// Privacy Settings Tables
// ============================================
exports.privacySettings = (0, pg_core_1.pgTable)('privacy_settings', {
id: (0, pg_core_1.serial)('id').primaryKey(),
userId: (0, pg_core_1.integer)('user_id').notNull().unique().references(() => exports.mediaUsers.id, { onDelete: 'cascade' }),
// Online Status
showOnlineStatus: (0, pg_core_1.boolean)('show_online_status').default(true),
showCurrentlyWatching: (0, pg_core_1.boolean)('show_currently_watching').default(true),
// Activity Visibility
showInFriendActivity: (0, pg_core_1.boolean)('show_in_friend_activity').default(true),
anonymizePublicComments: (0, pg_core_1.boolean)('anonymize_public_comments').default(false),
hidePublicReactions: (0, pg_core_1.boolean)('hide_public_reactions').default(false),
hidePublicFinishes: (0, pg_core_1.boolean)('hide_public_finishes').default(false),
// Friend Requests
allowFriendRequests: (0, pg_core_1.boolean)('allow_friend_requests').default(true),
// Close Friends restrictions
closeFriendsOnlyWatching: (0, pg_core_1.boolean)('close_friends_only_watching').default(false),
createdAt: (0, pg_core_1.timestamp)('created_at', { withTimezone: true }).defaultNow(),
updatedAt: (0, pg_core_1.timestamp)('updated_at', { withTimezone: true }),
}, (table) => ({
userIdx: (0, pg_core_1.index)('idx_privacy_settings_user').on(table.userId),
}));
exports.closeFriends = (0, pg_core_1.pgTable)('close_friends', {
id: (0, pg_core_1.serial)('id').primaryKey(),
userId: (0, pg_core_1.integer)('user_id').notNull().references(() => exports.mediaUsers.id, { onDelete: 'cascade' }),
closeFriendId: (0, pg_core_1.integer)('close_friend_id').notNull().references(() => exports.mediaUsers.id, { onDelete: 'cascade' }),
addedAt: (0, pg_core_1.timestamp)('added_at', { withTimezone: true }).defaultNow(),
}, (table) => ({
uniqueIdx: (0, pg_core_1.uniqueIndex)('idx_close_friends_unique').on(table.userId, table.closeFriendId),
userIdx: (0, pg_core_1.index)('idx_close_friends_user').on(table.userId),
}));
// ============================================
// User Uploads (Trusted User Feature)
// ============================================
exports.USER_UPLOAD_STATUS = ['pending', 'approved', 'rejected'];
exports.userUploads = (0, pg_core_1.pgTable)('user_uploads', {
id: (0, pg_core_1.serial)('id').primaryKey(),
userId: (0, pg_core_1.integer)('user_id').notNull(),
filename: (0, pg_core_1.text)('filename').notNull(),
originalFilename: (0, pg_core_1.text)('original_filename'),
path: (0, pg_core_1.text)('path').notNull(),
durationSeconds: (0, pg_core_1.integer)('duration_seconds'),
quality: (0, pg_core_1.text)('quality'),
orientation: (0, pg_core_1.text)('orientation'),
fileSize: (0, pg_core_1.bigint)('file_size', { mode: 'number' }),
thumbnailPath: (0, pg_core_1.text)('thumbnail_path'),
status: (0, pg_core_1.text)('status').notNull().default('pending'),
reviewedBy: (0, pg_core_1.integer)('reviewed_by'),
reviewedAt: (0, pg_core_1.timestamp)('reviewed_at', { withTimezone: true }),
reviewNotes: (0, pg_core_1.text)('review_notes'),
publicMediaId: (0, pg_core_1.integer)('public_media_id'),
uploadInviteId: (0, pg_core_1.integer)('upload_invite_id'),
createdAt: (0, pg_core_1.timestamp)('created_at', { withTimezone: true }).defaultNow(),
}, (table) => ({
userIdx: (0, pg_core_1.index)('idx_user_uploads_user').on(table.userId),
statusIdx: (0, pg_core_1.index)('idx_user_uploads_status').on(table.status),
createdAtIdx: (0, pg_core_1.index)('idx_user_uploads_created').on(table.createdAt),
inviteIdx: (0, pg_core_1.index)('idx_user_uploads_invite').on(table.uploadInviteId),
}));
// ============================================
// Upload Invites (Shareable Upload Links)
// ============================================
exports.UPLOAD_INVITE_STATUS = ['active', 'inactive', 'expired'];
exports.uploadInvites = (0, pg_core_1.pgTable)('upload_invites', {
id: (0, pg_core_1.serial)('id').primaryKey(),
code: (0, pg_core_1.text)('code').notNull().unique(),
label: (0, pg_core_1.text)('label'),
createdBy: (0, pg_core_1.integer)('created_by').notNull(),
status: (0, pg_core_1.text)('status').notNull().default('active'),
maxUploads: (0, pg_core_1.integer)('max_uploads'),
uploadCount: (0, pg_core_1.integer)('upload_count').notNull().default(0),
expiresAt: (0, pg_core_1.timestamp)('expires_at', { withTimezone: true }),
createdAt: (0, pg_core_1.timestamp)('created_at', { withTimezone: true }).defaultNow(),
}, (table) => ({
codeIdx: (0, pg_core_1.index)('idx_upload_invites_code').on(table.code),
statusIdx: (0, pg_core_1.index)('idx_upload_invites_status').on(table.status),
createdByIdx: (0, pg_core_1.index)('idx_upload_invites_created_by').on(table.createdBy),
}));
// ============================================
// Tag System Tables
// ============================================
exports.tagCategories = (0, pg_core_1.pgTable)('tag_categories', {
id: (0, pg_core_1.serial)('id').primaryKey(),
name: (0, pg_core_1.text)('name').notNull().unique(),
displayOrder: (0, pg_core_1.integer)('display_order').notNull().default(0),
createdAt: (0, pg_core_1.timestamp)('created_at', { withTimezone: true }).defaultNow(),
}, (table) => ({
displayOrderIdx: (0, pg_core_1.index)('idx_tag_categories_display_order').on(table.displayOrder),
}));
exports.tags = (0, pg_core_1.pgTable)('tags', {
id: (0, pg_core_1.serial)('id').primaryKey(),
categoryId: (0, pg_core_1.integer)('category_id').notNull(),
name: (0, pg_core_1.text)('name').notNull(),
displayOrder: (0, pg_core_1.integer)('display_order').notNull().default(0),
createdAt: (0, pg_core_1.timestamp)('created_at', { withTimezone: true }).defaultNow(),
}, (table) => ({
categoryIdx: (0, pg_core_1.index)('idx_tags_category').on(table.categoryId),
displayOrderIdx: (0, pg_core_1.index)('idx_tags_display_order').on(table.displayOrder),
uniqueNamePerCategory: (0, pg_core_1.index)('idx_tags_unique_name').on(table.categoryId, table.name),
}));
exports.publicMediaTags = (0, pg_core_1.pgTable)('public_media_tags', {
id: (0, pg_core_1.serial)('id').primaryKey(),
mediaId: (0, pg_core_1.integer)('media_id').notNull(),
tagId: (0, pg_core_1.integer)('tag_id').notNull(),
addedAt: (0, pg_core_1.timestamp)('added_at', { withTimezone: true }).defaultNow(),
}, (table) => ({
mediaIdx: (0, pg_core_1.index)('idx_public_media_tags_media').on(table.mediaId),
tagIdx: (0, pg_core_1.index)('idx_public_media_tags_tag').on(table.tagId),
uniqueIdx: (0, pg_core_1.index)('idx_public_media_tags_unique').on(table.mediaId, table.tagId),
}));
exports.userUploadSuggestedTags = (0, pg_core_1.pgTable)('user_upload_suggested_tags', {
id: (0, pg_core_1.serial)('id').primaryKey(),
uploadId: (0, pg_core_1.integer)('upload_id').notNull(),
tagId: (0, pg_core_1.integer)('tag_id').notNull(),
suggestedAt: (0, pg_core_1.timestamp)('suggested_at', { withTimezone: true }).defaultNow(),
}, (table) => ({
uploadIdx: (0, pg_core_1.index)('idx_user_upload_suggested_tags_upload').on(table.uploadId),
tagIdx: (0, pg_core_1.index)('idx_user_upload_suggested_tags_tag').on(table.tagId),
uniqueIdx: (0, pg_core_1.index)('idx_user_upload_suggested_tags_unique').on(table.uploadId, table.tagId),
}));
exports.userTagPreferences = (0, pg_core_1.pgTable)('user_tag_preferences', {
id: (0, pg_core_1.serial)('id').primaryKey(),
userId: (0, pg_core_1.integer)('user_id').notNull(),
tagId: (0, pg_core_1.integer)('tag_id').notNull(),
savedAt: (0, pg_core_1.timestamp)('saved_at', { withTimezone: true }).defaultNow(),
}, (table) => ({
userIdx: (0, pg_core_1.index)('idx_user_tag_preferences_user').on(table.userId),
tagIdx: (0, pg_core_1.index)('idx_user_tag_preferences_tag').on(table.tagId),
uniqueIdx: (0, pg_core_1.index)('idx_user_tag_preferences_unique').on(table.userId, table.tagId),
}));
// ============================================
// Public Media Performers Junction
// ============================================
/**
* Links public media items to performers for "Browse Actors" feature.
*/
exports.publicMediaPerformers = (0, pg_core_1.pgTable)('public_media_performers', {
id: (0, pg_core_1.serial)('id').primaryKey(),
mediaId: (0, pg_core_1.integer)('media_id').notNull(),
performerId: (0, pg_core_1.integer)('performer_id').notNull(),
addedAt: (0, pg_core_1.timestamp)('added_at', { withTimezone: true }).defaultNow(),
}, (table) => ({
mediaIdx: (0, pg_core_1.index)('idx_public_media_performers_media').on(table.mediaId),
performerIdx: (0, pg_core_1.index)('idx_public_media_performers_performer').on(table.performerId),
uniqueIdx: (0, pg_core_1.uniqueIndex)('idx_public_media_performers_unique').on(table.mediaId, table.performerId),
}));
// ============================================
// Video Digest Tables
// ============================================
exports.DIGEST_STATUS = [
'pending', 'scene_detection', 'extracting', 'analyzing',
'face_detection', 'transcribing', 'segmenting', 'synthesizing',
'completed', 'failed', 'cancelled'
];
exports.videoDigests = (0, pg_core_1.pgTable)('video_digests', {
id: (0, pg_core_1.serial)('id').primaryKey(),
videoId: (0, pg_core_1.integer)('video_id').notNull(),
jobId: (0, pg_core_1.integer)('job_id'),
status: (0, pg_core_1.text)('status').notNull().default('pending'),
progress: (0, pg_core_1.integer)('progress').default(0),
frameCount: (0, pg_core_1.integer)('frame_count'),
config: (0, pg_core_1.jsonb)('config').$type(),
frameAnalyses: (0, pg_core_1.jsonb)('frame_analyses').$type(),
transcript: (0, pg_core_1.jsonb)('transcript').$type(),
tags: (0, pg_core_1.jsonb)('tags').$type(),
suggestedClips: (0, pg_core_1.jsonb)('suggested_clips').$type(),
adCutSpec: (0, pg_core_1.jsonb)('ad_cut_spec').$type(),
// Per-frame stage results for observability (persisted during analysis)
stageResults: (0, pg_core_1.jsonb)('stage_results').$type(),
createdAt: (0, pg_core_1.timestamp)('created_at', { withTimezone: true }).defaultNow(),
startedAt: (0, pg_core_1.timestamp)('started_at', { withTimezone: true }),
completedAt: (0, pg_core_1.timestamp)('completed_at', { withTimezone: true }),
error: (0, pg_core_1.text)('error'),
}, (table) => ({
videoIdx: (0, pg_core_1.index)('idx_video_digests_video').on(table.videoId),
statusIdx: (0, pg_core_1.index)('idx_video_digests_status').on(table.status),
createdAtIdx: (0, pg_core_1.index)('idx_video_digests_created').on(table.createdAt),
}));
exports.digestVideoTags = (0, pg_core_1.pgTable)('digest_video_tags', {
id: (0, pg_core_1.serial)('id').primaryKey(),
digestId: (0, pg_core_1.integer)('digest_id').notNull(),
videoId: (0, pg_core_1.integer)('video_id').notNull(),
category: (0, pg_core_1.text)('category').notNull(),
value: (0, pg_core_1.text)('value').notNull(),
confidence: (0, pg_core_1.integer)('confidence'),
source: (0, pg_core_1.text)('source').default('digest'),
evidence: (0, pg_core_1.jsonb)('evidence').$type(),
createdAt: (0, pg_core_1.timestamp)('created_at', { withTimezone: true }).defaultNow(),
}, (table) => ({
digestIdx: (0, pg_core_1.index)('idx_digest_video_tags_digest').on(table.digestId),
videoIdx: (0, pg_core_1.index)('idx_digest_video_tags_video').on(table.videoId),
categoryIdx: (0, pg_core_1.index)('idx_digest_video_tags_category').on(table.category),
valueIdx: (0, pg_core_1.index)('idx_digest_video_tags_value').on(table.value),
categoryValueIdx: (0, pg_core_1.index)('idx_digest_video_tags_cat_val').on(table.category, table.value),
}));
// ============================================
// Digest Clip Selection & Generation Tables
// ============================================
exports.CLIP_TYPES = ['hook', 'intro', 'action', 'climax', 'highlight'];
exports.CLIP_STATUS = ['pending', 'processing', 'completed', 'failed'];
exports.CLIP_SOURCES = ['machine', 'manual'];
// Stage 1: Selected clips from digest analysis (metadata only, no extraction yet)
exports.digestSelectedClips = (0, pg_core_1.pgTable)('digest_selected_clips', {
id: (0, pg_core_1.serial)('id').primaryKey(),
digestId: (0, pg_core_1.integer)('digest_id').notNull(),
videoId: (0, pg_core_1.integer)('video_id').notNull(),
clipType: (0, pg_core_1.text)('clip_type').notNull(),
startTime: (0, pg_core_1.integer)('start_time').notNull(), // seconds
endTime: (0, pg_core_1.integer)('end_time').notNull(),
duration: (0, pg_core_1.integer)('duration').notNull(),
reason: (0, pg_core_1.text)('reason'),
interestScore: (0, pg_core_1.integer)('interest_score'),
position: (0, pg_core_1.text)('position'), // For action clips (e.g., 'doggy style')
transcriptHint: (0, pg_core_1.text)('transcript_hint'), // For climax clips (keyword that triggered selection)
tags: (0, pg_core_1.jsonb)('tags').$type(),
source: (0, pg_core_1.text)('source').notNull().default('machine'),
isIncluded: (0, pg_core_1.integer)('is_included').notNull().default(1), // 1 = included in generation, 0 = excluded
isHook: (0, pg_core_1.integer)('is_hook').notNull().default(0), // 1 = this clip is the cinematic hook (teaser at start)
sequenceOrder: (0, pg_core_1.integer)('sequence_order').notNull().default(0), // Order in final compilation (0-based)
hookSourceClipId: (0, pg_core_1.integer)('hook_source_clip_id'), // If set, this is a hook copy cloned from the referenced clip
createdAt: (0, pg_core_1.timestamp)('created_at', { withTimezone: true }).defaultNow(),
}, (table) => ({
digestIdx: (0, pg_core_1.index)('idx_digest_selected_clips_digest').on(table.digestId),
videoIdx: (0, pg_core_1.index)('idx_digest_selected_clips_video').on(table.videoId),
clipTypeIdx: (0, pg_core_1.index)('idx_digest_selected_clips_type').on(table.clipType),
sourceIdx: (0, pg_core_1.index)('idx_digest_selected_clips_source').on(table.source),
sequenceOrderIdx: (0, pg_core_1.index)('idx_digest_selected_clips_sequence').on(table.sequenceOrder),
isHookIdx: (0, pg_core_1.index)('idx_digest_selected_clips_is_hook').on(table.isHook),
hookSourceIdx: (0, pg_core_1.index)('idx_digest_selected_clips_hook_source').on(table.hookSourceClipId),
}));
// Stage 2: Generated clips (after FFmpeg extraction)
exports.digestGeneratedClips = (0, pg_core_1.pgTable)('digest_generated_clips', {
id: (0, pg_core_1.serial)('id').primaryKey(),
selectedClipId: (0, pg_core_1.integer)('selected_clip_id').notNull(),
digestId: (0, pg_core_1.integer)('digest_id').notNull(),
folderId: (0, pg_core_1.integer)('folder_id'), // References digestOutputFolders.id
clipPath: (0, pg_core_1.text)('clip_path'),
gifPath: (0, pg_core_1.text)('gif_path'), // Only set if duration <= 60s
status: (0, pg_core_1.text)('status').notNull().default('pending'),
error: (0, pg_core_1.text)('error'),
createdAt: (0, pg_core_1.timestamp)('created_at', { withTimezone: true }).defaultNow(),
completedAt: (0, pg_core_1.timestamp)('completed_at', { withTimezone: true }),
// Publishing tracking
publishedToPublicMediaId: (0, pg_core_1.integer)('published_to_public_media_id'),
publishedAt: (0, pg_core_1.timestamp)('published_at', { withTimezone: true }),
}, (table) => ({
selectedClipIdx: (0, pg_core_1.index)('idx_digest_generated_clips_selected').on(table.selectedClipId),
digestIdx: (0, pg_core_1.index)('idx_digest_generated_clips_digest').on(table.digestId),
statusIdx: (0, pg_core_1.index)('idx_digest_generated_clips_status').on(table.status),
folderIdx: (0, pg_core_1.index)('idx_digest_generated_clips_folder').on(table.folderId),
}));
// Caption style type for digest compilations
exports.CAPTION_POSITIONS = ['bottom', 'top'];
exports.CAPTION_SIZES = ['small', 'medium', 'large'];
// Stage 2: Compilation videos
exports.digestCompilations = (0, pg_core_1.pgTable)('digest_compilations', {
id: (0, pg_core_1.serial)('id').primaryKey(),
digestId: (0, pg_core_1.integer)('digest_id').notNull(),
videoId: (0, pg_core_1.integer)('video_id').notNull(),
folderId: (0, pg_core_1.integer)('folder_id'), // References digestOutputFolders.id
filename: (0, pg_core_1.text)('filename').notNull(),
name: (0, pg_core_1.text)('name'), // Optional user-provided name for the compilation
path: (0, pg_core_1.text)('path').notNull(),
durationSeconds: (0, pg_core_1.integer)('duration_seconds'),
orientation: (0, pg_core_1.text)('orientation'),
status: (0, pg_core_1.text)('status').notNull().default('pending'),
error: (0, pg_core_1.text)('error'),
// Caption burning options
hasCaptions: (0, pg_core_1.integer)('has_captions').notNull().default(0), // 1 = captions burned in
captionStyle: (0, pg_core_1.jsonb)('caption_style').$type(),
// Closing ad frame options
closingAdPath: (0, pg_core_1.text)('closing_ad_path'), // Path to uploaded ad frame image
closingAdDuration: (0, pg_core_1.integer)('closing_ad_duration'), // Duration in seconds (3-10)
// Aggregated tags from constituent segments
tags: (0, pg_core_1.jsonb)('tags').$type(),
createdAt: (0, pg_core_1.timestamp)('created_at', { withTimezone: true }).defaultNow(),
completedAt: (0, pg_core_1.timestamp)('completed_at', { withTimezone: true }),
}, (table) => ({
digestIdx: (0, pg_core_1.index)('idx_digest_compilations_digest').on(table.digestId),
videoIdx: (0, pg_core_1.index)('idx_digest_compilations_video').on(table.videoId),
statusIdx: (0, pg_core_1.index)('idx_digest_compilations_status').on(table.status),
folderIdx: (0, pg_core_1.index)('idx_digest_compilations_folder').on(table.folderId),
}));
// ============================================
// Digest Output Folders Table
// ============================================
exports.digestOutputFolders = (0, pg_core_1.pgTable)('digest_output_folders', {
id: (0, pg_core_1.serial)('id').primaryKey(),
digestId: (0, pg_core_1.integer)('digest_id').notNull(),
videoId: (0, pg_core_1.integer)('video_id').notNull(),
folderPath: (0, pg_core_1.text)('folder_path').notNull(),
folderName: (0, pg_core_1.text)('folder_name').notNull(),
folderType: (0, pg_core_1.text)('folder_type').notNull().default('clips'), // 'clips' | 'scenes' | 'compilation'
clipCount: (0, pg_core_1.integer)('clip_count').default(0),
compilationCount: (0, pg_core_1.integer)('compilation_count').default(0),
totalSize: (0, pg_core_1.integer)('total_size'),
createdAt: (0, pg_core_1.timestamp)('created_at', { withTimezone: true }).defaultNow(),
}, (table) => ({
digestIdx: (0, pg_core_1.index)('idx_digest_output_folders_digest').on(table.digestId),
videoIdx: (0, pg_core_1.index)('idx_digest_output_folders_video').on(table.videoId),
folderPathIdx: (0, pg_core_1.index)('idx_digest_output_folders_path').on(table.folderPath),
}));
// ============================================
// Digest Generated Scenes Table
// ============================================
// Tracks individual scene files extracted from digests with derived tags
exports.digestGeneratedScenes = (0, pg_core_1.pgTable)('digest_generated_scenes', {
id: (0, pg_core_1.serial)('id').primaryKey(),
digestId: (0, pg_core_1.integer)('digest_id').notNull(),
videoId: (0, pg_core_1.integer)('video_id').notNull(),
folderId: (0, pg_core_1.integer)('folder_id'), // References digestOutputFolders.id
sceneNumber: (0, pg_core_1.integer)('scene_number').notNull(),
scenePath: (0, pg_core_1.text)('scene_path'),
startTime: (0, pg_core_1.real)('start_time').notNull(),
endTime: (0, pg_core_1.real)('end_time').notNull(),
duration: (0, pg_core_1.real)('duration').notNull(),
// Tags derived from videoTagTimeline for this time range
tags: (0, pg_core_1.jsonb)('tags').$type(),
dominantPosition: (0, pg_core_1.text)('dominant_position'),
createdAt: (0, pg_core_1.timestamp)('created_at', { withTimezone: true }).defaultNow(),
// Publishing tracking
publishedToPublicMediaId: (0, pg_core_1.integer)('published_to_public_media_id'),
publishedAt: (0, pg_core_1.timestamp)('published_at', { withTimezone: true }),
}, (table) => ({
digestIdx: (0, pg_core_1.index)('idx_digest_generated_scenes_digest').on(table.digestId),
videoIdx: (0, pg_core_1.index)('idx_digest_generated_scenes_video').on(table.videoId),
folderIdx: (0, pg_core_1.index)('idx_digest_generated_scenes_folder').on(table.folderId),
scenePathIdx: (0, pg_core_1.index)('idx_digest_generated_scenes_path').on(table.scenePath),
}));
// Caches detected scene cuts per video for clean clip transitions
// Extended for combined multi-detector scene detection
exports.videoSceneCuts = (0, pg_core_1.pgTable)('video_scene_cuts', {
id: (0, pg_core_1.serial)('id').primaryKey(),
videoId: (0, pg_core_1.integer)('video_id').notNull(),
// Array of cut timestamps in seconds (includes 0 and video end)
cuts: (0, pg_core_1.jsonb)('cuts').$type().notNull(),
// Number of scenes detected
sceneCount: (0, pg_core_1.integer)('scene_count').notNull(),
// Video duration at time of detection
duration: (0, pg_core_1.real)('duration').notNull(),
// Detection configuration
detector: (0, pg_core_1.text)('detector').notNull().default('content'), // 'content', 'adaptive', 'transnetv2', 'combined'
threshold: (0, pg_core_1.real)('threshold').notNull().default(27.0),
// ============================================
// Combined Detection Fields (for multi-detector merging)
// ============================================
// Individual detector results for debugging/analysis
transnetCuts: (0, pg_core_1.jsonb)('transnet_cuts').$type(),
pyscenedetectCuts: (0, pg_core_1.jsonb)('pyscenedetect_cuts').$type(),
clipCuts: (0, pg_core_1.jsonb)('clip_cuts').$type(),
// Detailed merged cuts with source attribution
mergedCutsDetailed: (0, pg_core_1.jsonb)('merged_cuts_detailed').$type(),
// Merge configuration
mergeStrategy: (0, pg_core_1.text)('merge_strategy').default('weighted'), // 'union' | 'intersection' | 'weighted' | 'majority'
toleranceSeconds: (0, pg_core_1.real)('tolerance_seconds').default(0.75),
// Which detectors were used (for cache validation)
detectorsUsed: (0, pg_core_1.jsonb)('detectors_used').$type(),
// Total processing time for combined detection
processingTimeMs: (0, pg_core_1.integer)('processing_time_ms'),
// Timestamps
createdAt: (0, pg_core_1.timestamp)('created_at', { withTimezone: true }).defaultNow(),
}, (table) => ({
videoIdx: (0, pg_core_1.index)('idx_video_scene_cuts_video').on(table.videoId),
}));
// ============================================
// Video Segmentation Tables (Multi-Method Analysis)
// ============================================
// Segment types for multi-method clip detection
exports.SEGMENT_TYPES = ['scene', 'tag_change', 'vocal', 'fixed_interval'];
// Vocal moment categories for transcript-based segments
exports.VOCAL_CATEGORIES = ['dialogue', 'dirty_talk', 'climax', 'interview', 'moan'];
// Stores JoyTag results at regular intervals for change detection
exports.videoTagTimeline = (0, pg_core_1.pgTable)('video_tag_timeline', {
id: (0, pg_core_1.serial)('id').primaryKey(),
videoId: (0, pg_core_1.integer)('video_id').notNull(),
digestId: (0, pg_core_1.integer)('digest_id').notNull(),
timestamp: (0, pg_core_1.real)('timestamp').notNull(), // Seconds from video start
// JoyTag results at this timestamp
tags: (0, pg_core_1.jsonb)('tags').$type().notNull(),
// Derived/computed fields for efficient querying
dominantPosition: (0, pg_core_1.text)('dominant_position'), // Most confident sexual position tag
performerCount: (0, pg_core_1.integer)('performer_count'),
intensity: (0, pg_core_1.integer)('intensity'), // 0-10 score based on tag analysis
hasDialogue: (0, pg_core_1.boolean)('has_dialogue').default(false),
hasEyeContact: (0, pg_core_1.boolean)('has_eye_contact').default(false),
// Frame reference
framePath: (0, pg_core_1.text)('frame_path'),
createdAt: (0, pg_core_1.timestamp)('created_at', { withTimezone: true }).defaultNow(),
}, (table) => ({
videoIdx: (0, pg_core_1.index)('idx_video_tag_timeline_video').on(table.videoId),
digestIdx: (0, pg_core_1.index)('idx_video_tag_timeline_digest').on(table.digestId),
timestampIdx: (0, pg_core_1.index)('idx_video_tag_timeline_timestamp').on(table.videoId, table.timestamp),
positionIdx: (0, pg_core_1.index)('idx_video_tag_timeline_position').on(table.dominantPosition),
}));
// Stores detected segments by type (scene, tag_change, vocal, fixed_interval)
exports.videoSegments = (0, pg_core_1.pgTable)('video_segments', {
id: (0, pg_core_1.serial)('id').primaryKey(),
digestId: (0, pg_core_1.integer)('digest_id').notNull(),
videoId: (0, pg_core_1.integer)('video_id').notNull(),
segmentType: (0, pg_core_1.text)('segment_type').notNull().$type(),
startTime: (0, pg_core_1.real)('start_time').notNull(), // Seconds
endTime: (0, pg_core_1.real)('end_time').notNull(), // Seconds
duration: (0, pg_core_1.real)('duration').notNull(), // Seconds
// Segment metadata (varies by type)
tags: (0, pg_core_1.jsonb)('tags').$type(),
transcriptSnippet: (0, pg_core_1.text)('transcript_snippet'),
vocalCategory: (0, pg_core_1.text)('vocal_category').$type(),
positionChange: (0, pg_core_1.text)('position_change'), // "oral → doggystyle"
dominantPosition: (0, pg_core_1.text)('dominant_position'),
// Scoring for auto-sequence building
interestScore: (0, pg_core_1.real)('interest_score').default(0),
hasEyeContact: (0, pg_core_1.boolean)('has_eye_contact').default(false),
hasFullSentence: (0, pg_core_1.boolean)('has_full_sentence').default(false),
isClimaxMoment: (0, pg_core_1.boolean)('is_climax_moment').default(false),
// Caption/title generation
captioned: (0, pg_core_1.boolean)('captioned').default(false),
captionText: (0, pg_core_1.text)('caption_text'),
title: (0, pg_core_1.text)('title'),
// Frame reference for thumbnail
keyframePath: (0, pg_core_1.text)('keyframe_path'),
keyframeTimestamp: (0, pg_core_1.real)('keyframe_timestamp'),
// Inclusion in sequence (for Abstract)
isIncluded: (0, pg_core_1.boolean)('is_included').default(true),
sequenceOrder: (0, pg_core_1.integer)('sequence_order'),
isHook: (0, pg_core_1.boolean)('is_hook').default(false),
// Inclusion for clip generation (for Clips tab, independent from Abstract)
includeInClips: (0, pg_core_1.boolean)('include_in_clips').default(true),
createdAt: (0, pg_core_1.timestamp)('created_at', { withTimezone: true }).defaultNow(),
}, (table) => ({
digestIdx: (0, pg_core_1.index)('idx_video_segments_digest').on(table.digestId),
videoIdx: (0, pg_core_1.index)('idx_video_segments_video').on(table.videoId),
typeIdx: (0, pg_core_1.index)('idx_video_segments_type').on(table.segmentType),
timeIdx: (0, pg_core_1.index)('idx_video_segments_time').on(table.videoId, table.startTime),
includedIdx: (0, pg_core_1.index)('idx_video_segments_included').on(table.digestId, table.isIncluded),
sequenceIdx: (0, pg_core_1.index)('idx_video_segments_sequence').on(table.digestId, table.sequenceOrder),
clipsIdx: (0, pg_core_1.index)('idx_video_segments_clips').on(table.digestId, table.includeInClips),
}));
// ============================================
// Video Tag Mapping Tables (Digest → Tag System)
// ============================================
// Links videos to existing tags (from digest or manual)
exports.videoTags = (0, pg_core_1.pgTable)('video_tags', {
id: (0, pg_core_1.serial)('id').primaryKey(),
videoId: (0, pg_core_1.integer)('video_id').notNull(),
tagId: (0, pg_core_1.integer)('tag_id').notNull(),
confidence: (0, pg_core_1.integer)('confidence'), // 0-100, null for manual
source: (0, pg_core_1.text)('source').notNull().default('manual'),
digestId: (0, pg_core_1.integer)('digest_id'),
createdAt: (0, pg_core_1.timestamp)('created_at', { withTimezone: true }).defaultNow(),
}, (table) => ({
videoIdx: (0, pg_core_1.index)('idx_video_tags_video').on(table.videoId),
tagIdx: (0, pg_core_1.index)('idx_video_tags_tag').on(table.tagId),
sourceIdx: (0, pg_core_1.index)('idx_video_tags_source').on(table.source),
uniqueIdx: (0, pg_core_1.index)('idx_video_tags_unique').on(table.videoId, table.tagId),
}));
exports.SUGGESTED_TAG_STATUS = ['pending', 'approved', 'rejected', 'mapped'];
// Unmatched tags from digest that need admin review
exports.digestSuggestedTags = (0, pg_core_1.pgTable)('digest_suggested_tags', {
id: (0, pg_core_1.serial)('id').primaryKey(),
digestId: (0, pg_core_1.integer)('digest_id').notNull(),
videoId: (0, pg_core_1.integer)('video_id').notNull(),
suggestedCategory: (0, pg_core_1.text)('suggested_category').notNull(),
suggestedValue: (0, pg_core_1.text)('suggested_value').notNull(),
confidence: (0, pg_core_1.integer)('confidence'), // 0-100
status: (0, pg_core_1.text)('status').notNull().default('pending'),
mappedTagId: (0, pg_core_1.integer)('mapped_tag_id'),
reviewedBy: (0, pg_core_1.integer)('reviewed_by'),
reviewedAt: (0, pg_core_1.timestamp)('reviewed_at', { withTimezone: true }),
createdAt: (0, pg_core_1.timestamp)('created_at', { withTimezone: true }).defaultNow(),
}, (table) => ({
digestIdx: (0, pg_core_1.index)('idx_digest_suggested_tags_digest').on(table.digestId),
videoIdx: (0, pg_core_1.index)('idx_digest_suggested_tags_video').on(table.videoId),
statusIdx: (0, pg_core_1.index)('idx_digest_suggested_tags_status').on(table.status),
categoryValueIdx: (0, pg_core_1.index)('idx_digest_suggested_tags_cat_val').on(table.suggestedCategory, table.suggestedValue),
}));
// Clip-level tag assignments (links clips to formal tags)
exports.digestClipTags = (0, pg_core_1.pgTable)('digest_clip_tags', {
id: (0, pg_core_1.serial)('id').primaryKey(),
clipId: (0, pg_core_1.integer)('clip_id').notNull(),
tagId: (0, pg_core_1.integer)('tag_id').notNull(),
source: (0, pg_core_1.text)('source').notNull().default('auto'),
createdAt: (0, pg_core_1.timestamp)('created_at', { withTimezone: true }).defaultNow(),
}, (table) => ({
clipIdx: (0, pg_core_1.index)('idx_digest_clip_tags_clip').on(table.clipId),
tagIdx: (0, pg_core_1.index)('idx_digest_clip_tags_tag').on(table.tagId),
uniqueIdx: (0, pg_core_1.index)('idx_digest_clip_tags_unique').on(table.clipId, table.tagId),
}));
// ============================================
// Watch Party Tables
// ============================================
exports.WATCH_PARTY_STATUS = ['waiting', 'active', 'paused', 'ended'];
exports.LOOP_MODE = ['none', 'video', 'queue'];
exports.watchPartySessions = (0, pg_core_1.pgTable)('watch_party_sessions', {
id: (0, pg_core_1.serial)('id').primaryKey(),
hostId: (0, pg_core_1.integer)('host_id').notNull().references(() => exports.mediaUsers.id, { onDelete: 'cascade' }),
mediaId: (0, pg_core_1.integer)('media_id').notNull().references(() => exports.publicMedia.id, { onDelete: 'cascade' }),
inviteCode: (0, pg_core_1.text)('invite_code').notNull().unique(),
status: (0, pg_core_1.text)('status').notNull().default('waiting'),
currentTime: (0, pg_core_1.integer)('current_time').default(0), // playback position in seconds
isPlaying: (0, pg_core_1.boolean)('is_playing').default(false),
maxParticipants: (0, pg_core_1.integer)('max_participants').default(20),
allowChat: (0, pg_core_1.boolean)('allow_chat').default(true),
createdAt: (0, pg_core_1.timestamp)('created_at', { withTimezone: true }).defaultNow(),
startedAt: (0, pg_core_1.timestamp)('started_at', { withTimezone: true }),
endedAt: (0, pg_core_1.timestamp)('ended_at', { withTimezone: true }),
// Post-party summary stats (generated when party ends)
summaryStats: (0, pg_core_1.jsonb)('summary_stats').$type(),
}, (table) => ({
hostIdx: (0, pg_core_1.index)('idx_watch_party_sessions_host').on(table.hostId),
inviteCodeIdx: (0, pg_core_1.index)('idx_watch_party_sessions_invite').on(table.inviteCode),
statusIdx: (0, pg_core_1.index)('idx_watch_party_sessions_status').on(table.status),
createdAtIdx: (0, pg_core_1.index)('idx_watch_party_sessions_created').on(table.createdAt),
}));
exports.watchPartyParticipants = (0, pg_core_1.pgTable)('watch_party_participants', {
id: (0, pg_core_1.serial)('id').primaryKey(),
partyId: (0, pg_core_1.integer)('party_id').notNull().references(() => exports.watchPartySessions.id, { onDelete: 'cascade' }),
userId: (0, pg_core_1.integer)('user_id').notNull().references(() => exports.mediaUsers.id, { onDelete: 'cascade' }),
isConnected: (0, pg_core_1.boolean)('is_connected').default(false),
isKicked: (0, pg_core_1.boolean)('is_kicked').default(false),
joinedAt: (0, pg_core_1.timestamp)('joined_at', { withTimezone: true }).defaultNow(),
leftAt: (0, pg_core_1.timestamp)('left_at', { withTimezone: true }),
}, (table) => ({
partyIdx: (0, pg_core_1.index)('idx_watch_party_participants_party').on(table.partyId),
userIdx: (0, pg_core_1.index)('idx_watch_party_participants_user').on(table.userId),
uniqueIdx: (0, pg_core_1.index)('idx_watch_party_participants_unique').on(table.partyId, table.userId),
}));
exports.watchPartyChatMessages = (0, pg_core_1.pgTable)('watch_party_chat_messages', {
id: (0, pg_core_1.serial)('id').primaryKey(),
partyId: (0, pg_core_1.integer)('party_id').notNull().references(() => exports.watchPartySessions.id, { onDelete: 'cascade' }),
userId: (0, pg_core_1.integer)('user_id').notNull().references(() => exports.mediaUsers.id, { onDelete: 'cascade' }),
content: (0, pg_core_1.text)('content').notNull(),
createdAt: (0, pg_core_1.timestamp)('created_at', { withTimezone: true }).defaultNow(),
}, (table) => ({
partyIdx: (0, pg_core_1.index)('idx_watch_party_chat_party').on(table.partyId),
createdAtIdx: (0, pg_core_1.index)('idx_watch_party_chat_created').on(table.createdAt),
}));
// Watch Party Reactions (persisted for summary stats)
exports.watchPartyReactions = (0, pg_core_1.pgTable)('watch_party_reactions', {
id: (0, pg_core_1.serial)('id').primaryKey(),
partyId: (0, pg_core_1.integer)('party_id').notNull().references(() => exports.watchPartySessions.id, { onDelete: 'cascade' }),
userId: (0, pg_core_1.integer)('user_id').notNull().references(() => exports.mediaUsers.id, { onDelete: 'cascade' }),
reactionType: (0, pg_core_1.text)('reaction_type').notNull(),
videoTimestamp: (0, pg_core_1.integer)('video_timestamp'), // seconds into video when reaction was sent
createdAt: (0, pg_core_1.timestamp)('created_at', { withTimezone: true }).defaultNow(),
}, (table) => ({
partyIdx: (0, pg_core_1.index)('idx_watch_party_reactions_party').on(table.partyId),
userIdx: (0, pg_core_1.index)('idx_watch_party_reactions_user').on(table.userId),
}));
// Watch Party Invites
exports.INVITE_STATUS = ['pending', 'accepted', 'declined', 'expired'];
exports.watchPartyInvites = (0, pg_core_1.pgTable)('watch_party_invites', {
id: (0, pg_core_1.serial)('id').primaryKey(),
partyId: (0, pg_core_1.integer)('party_id').notNull().references(() => exports.watchPartySessions.id, { onDelete: 'cascade' }),
fromUserId: (0, pg_core_1.integer)('from_user_id').notNull().references(() => exports.mediaUsers.id, { onDelete: 'cascade' }),
toUserId: (0, pg_core_1.integer)('to_user_id').notNull().references(() => exports.mediaUsers.id, { onDelete: 'cascade' }),
status: (0, pg_core_1.text)('status').notNull().default('pending').$type(),
createdAt: (0, pg_core_1.timestamp)('created_at', { withTimezone: true }).defaultNow(),
respondedAt: (0, pg_core_1.timestamp)('responded_at', { withTimezone: true }),
}, (table) => ({
partyIdx: (0, pg_core_1.index)('idx_watch_party_invites_party').on(table.partyId),
toUserIdx: (0, pg_core_1.index)('idx_watch_party_invites_to_user').on(table.toUserId),
fromUserIdx: (0, pg_core_1.index)('idx_watch_party_invites_from_user').on(table.fromUserId),
statusIdx: (0, pg_core_1.index)('idx_watch_party_invites_status').on(table.status),
}));
exports.tagGenerationJobs = (0, pg_core_1.pgTable)('tag_generation_jobs', {
id: (0, pg_core_1.serial)('id').primaryKey(),
status: (0, pg_core_1.text)('status').$type().default('pending').notNull(),
contentType: (0, pg_core_1.text)('content_type').$type().notNull(),
contentIds: (0, pg_core_1.jsonb)('content_ids').$type().notNull(),
options: (0, pg_core_1.jsonb)('options').$type(),
results: (0, pg_core_1.jsonb)('results').$type(),
progress: (0, pg_core_1.integer)('progress').default(0),
currentItem: (0, pg_core_1.integer)('current_item').default(0),
totalItems: (0, pg_core_1.integer)('total_items').default(0),
error: (0, pg_core_1.text)('error'),
createdAt: (0, pg_core_1.timestamp)('created_at', { withTimezone: true }).defaultNow(),
completedAt: (0, pg_core_1.timestamp)('completed_at', { withTimezone: true }),
}, (table) => ({
statusIdx: (0, pg_core_1.index)('idx_tag_gen_status').on(table.status),
contentTypeIdx: (0, pg_core_1.index)('idx_tag_gen_content_type').on(table.contentType),
createdAtIdx: (0, pg_core_1.index)('idx_tag_gen_created_at').on(table.createdAt),
}));
// ============================================================================
// Performer Face Recognition
// ============================================================================
/**
* Named performers that can be associated with multiple detected faces.
* Integrates with tag system for public gallery filtering.
*/
exports.creators = (0, pg_core_1.pgTable)('creators', {
id: (0, pg_core_1.serial)('id').primaryKey(),
name: (0, pg_core_1.text)('name').notNull().unique(),
slug: (0, pg_core_1.text)('slug').notNull().unique(), // URL-friendly name for public browsing
gender: (0, pg_core_1.integer)('gender'), // 0=female, 1=male, null=unknown
primaryFaceId: (0, pg_core_1.integer)('primary_face_id'), // Best face image for display thumbnail
faceCount: (0, pg_core_1.integer)('face_count').default(0), // Denormalized count of linked faces
videoCount: (0, pg_core_1.integer)('video_count').default(0), // Videos featuring this performer
tagId: (0, pg_core_1.integer)('tag_id'), // Link to performers tag category for filtering
isPublic: (0, pg_core_1.boolean)('is_public').default(false), // Visible in public Browse Actors
// Centroid embedding for efficient matching (512-D averaged, quality-filtered)
centroidEmbedding: (0, pg_core_1.jsonb)('centroid_embedding').$type(),
centroidUpdatedAt: (0, pg_core_1.timestamp)('centroid_updated_at', { withTimezone: true }),
centroidFaceCount: (0, pg_core_1.integer)('centroid_face_count').default(0), // Faces used in centroid computation
// Quality metrics for adaptive matching
embeddingVariance: (0, pg_core_1.real)('embedding_variance'), // How spread out the embeddings are
avgIntraClusterSimilarity: (0, pg_core_1.real)('avg_intra_cluster_similarity'), // Average similarity within performer's faces
avgAge: (0, pg_core_1.real)('avg_age'), // Average detected age of performer
ageStdDev: (0, pg_core_1.real)('age_std_dev'), // Standard deviation of detected ages
createdAt: (0, pg_core_1.timestamp)('created_at', { withTimezone: true }).defaultNow(),
updatedAt: (0, pg_core_1.timestamp)('updated_at', { withTimezone: true }).defaultNow(),
}, (table) => ({
nameIdx: (0, pg_core_1.index)('idx_creators_name').on(table.name),
slugIdx: (0, pg_core_1.index)('idx_creators_slug').on(table.slug),
genderIdx: (0, pg_core_1.index)('idx_creators_gender').on(table.gender),
tagIdx: (0, pg_core_1.index)('idx_creators_tag').on(table.tagId),
publicIdx: (0, pg_core_1.index)('idx_creators_public').on(table.isPublic),
}));
// Face status for tracking review state
exports.FACE_STATUS = ['pending', 'assigned', 'ignored', 'skipped'];
/**
* Stores face embeddings for performer recognition.
* Each row represents a single detected face that can be linked to a performer.
*/
exports.performerFaces = (0, pg_core_1.pgTable)('performer_faces', {
id: (0, pg_core_1.serial)('id').primaryKey(),
// Performer identification - FK to performers table (null if unidentified)
performerId: (0, pg_core_1.integer)('performer_id'), // Links to creators.id
performerName: (0, pg_core_1.text)('performer_name'), // Legacy field, kept for migration
// 512-dimensional face embedding vector from ArcFace
// Stored as JSON array for compatibility (consider pgvector for production)
faceEmbedding: (0, pg_core_1.jsonb)('face_embedding').$type().notNull(),
// Face attributes from InsightFace
gender: (0, pg_core_1.integer)('gender'), // 0=female, 1=male
age: (0, pg_core_1.integer)('age'),
detectionScore: (0, pg_core_1.real)('detection_score'), // Confidence of face detection
// Bounding box in source frame [x1, y1, x2, y2]
bbox: (0, pg_core_1.jsonb)('bbox').$type(),
// Face crop image path
faceCropPath: (0, pg_core_1.text)('face_crop_path'), // Path to cropped face thumbnail
// Source information
sourceFrame: (0, pg_core_1.text)('source_frame'), // Path to frame this was extracted from
sourceVideo: (0, pg_core_1.text)('source_video'), // Path to source video
sourceDigestId: (0, pg_core_1.integer)('source_digest_id'), // Link to video digest
frameTimestamp: (0, pg_core_1.real)('frame_timestamp'), // Timestamp in video
frameIndex: (0, pg_core_1.integer)('frame_index'), // Index of frame in digest
// Face grouping metadata
facesInFrame: (0, pg_core_1.integer)('faces_in_frame').default(1), // How many faces were in this frame
status: (0, pg_core_1.text)('status').$type().default('pending'), // Review status
// Matching metadata
matchConfidence: (0, pg_core_1.real)('match_confidence'), // Confidence of performer match (if matched)
isVerified: (0, pg_core_1.boolean)('is_verified').default(false), // Human-verified match
createdAt: (0, pg_core_1.timestamp)('created_at', { withTimezone: true }).defaultNow(),
updatedAt: (0, pg_core_1.timestamp)('updated_at', { withTimezone: true }).defaultNow(),
}, (table) => ({
performerIdIdx: (0, pg_core_1.index)('idx_performer_faces_performer').on(table.performerId),
performerNameIdx: (0, pg_core_1.index)('idx_performer_faces_name').on(table.performerName),
sourceVideoIdx: (0, pg_core_1.index)('idx_performer_faces_video').on(table.sourceVideo),
genderIdx: (0, pg_core_1.index)('idx_performer_faces_gender').on(table.gender),
digestIdx: (0, pg_core_1.index)('idx_performer_faces_digest').on(table.sourceDigestId),
cropPathIdx: (0, pg_core_1.index)('idx_performer_faces_crop').on(table.faceCropPath),
statusIdx: (0, pg_core_1.index)('idx_performer_faces_status').on(table.status),
facesInFrameIdx: (0, pg_core_1.index)('idx_performer_faces_faces_in_frame').on(table.facesInFrame),
detectionScoreIdx: (0, pg_core_1.index)('idx_performer_faces_det_score').on(table.detectionScore),
}));
// Discrepancy types for filename vs face detection mismatches
exports.DISCREPANCY_TYPES = [
'no_face_match', // Parsed name has no matching face
'low_confidence', // Face match confidence below threshold
'name_mismatch', // Filename says X but face matches Y
'gender_mismatch', // Gender of face doesn't match expected
'unknown_performer', // Parsed name not in performers database
'extra_face', // Detected face not in parsed performers
];
// Resolution status for discrepancies
exports.DISCREPANCY_STATUS = ['pending', 'confirmed', 'rejected', 'merged', 'ignored'];
// Resolution types
exports.DISCREPANCY_RESOLUTIONS = ['use_filename', 'use_face', 'create_alias', 'ignore', 'assign_face'];
/**
* Tracks mismatches between filename-parsed performers and face detection results.
* Used for admin review and resolution of performer identification issues.
*/
exports.performerDiscrepancies = (0, pg_core_1.pgTable)('performer_discrepancies', {
id: (0, pg_core_1.serial)('id').primaryKey(),
// Source info
videoId: (0, pg_core_1.integer)('video_id').references(() => exports.videos.id, { onDelete: 'cascade' }),
digestId: (0, pg_core_1.integer)('digest_id').references(() => exports.videoDigests.id, { onDelete: 'cascade' }),
// What filename parsing said
parsedPerformerName: (0, pg_core_1.text)('parsed_performer_name').notNull(),
parsedMethod: (0, pg_core_1.text)('parsed_method'), // 'llm', 'regex', 'manual'
parsedConfidence: (0, pg_core_1.real)('parsed_confidence'),
// What face detection found
detectedPerformerId: (0, pg_core_1.integer)('detected_performer_id').references(() => exports.creators.id, { onDelete: 'set null' }),
detectedPerformerName: (0, pg_core_1.text)('detected_performer_name'),
faceMatchConfidence: (0, pg_core_1.real)('face_match_confidence'),
faceSimilarity: (0, pg_core_1.real)('face_similarity'),
// Discrepancy details
discrepancyType: (0, pg_core_1.text)('discrepancy_type').$type().notNull(),
// Resolution
status: (0, pg_core_1.text)('status').$type().default('pending').notNull(),
resolvedBy: (0, pg_core_1.integer)('resolved_by').references(() => exports.mediaUsers.id, { onDelete: 'set null' }),
resolvedAt: (0, pg_core_1.timestamp)('resolved_at', { withTimezone: true }),
resolution: (0, pg_core_1.text)('resolution').$type(),
adminNotes: (0, pg_core_1.text)('admin_notes'),
createdAt: (0, pg_core_1.timestamp)('created_at', { withTimezone: true }).defaultNow().notNull(),
}, (table) => ({
videoIdx: (0, pg_core_1.index)('idx_discrepancies_video').on(table.videoId),
digestIdx: (0, pg_core_1.index)('idx_discrepancies_digest').on(table.digestId),
statusIdx: (0, pg_core_1.index)('idx_discrepancies_status').on(table.status),
typeIdx: (0, pg_core_1.index)('idx_discrepancies_type').on(table.discrepancyType),
detectedPerformerIdx: (0, pg_core_1.index)('idx_discrepancies_detected_performer').on(table.detectedPerformerId),
}));
// ============================================
// Pipeline Management System
// ============================================
// Pipeline status lifecycle
exports.PIPELINE_STATUS = ['pending', 'queued', 'running', 'paused', 'completed', 'failed', 'cancelled'];
// Pipeline step status
exports.PIPELINE_STEP_STATUS = ['pending', 'waiting_dependency', 'waiting_resource', 'running', 'completed', 'failed', 'skipped'];
// Pipeline definition - a multi-step workflow
exports.pipelines = (0, pg_core_1.pgTable)('pipelines', {
id: (0, pg_core_1.serial)('id').primaryKey(),
name: (0, pg_core_1.text)('name').notNull(),
description: (0, pg_core_1.text)('description'),
status: (0, pg_core_1.text)('status').notNull().default('pending').$type(),
priority: (0, pg_core_1.integer)('priority').default(5),
// Context for the entire pipeline (e.g., videoId, digestId)
context: (0, pg_core_1.jsonb)('context').$type(),
// Resource requirements aggregated from steps
totalVramRequired: (0, pg_core_1.integer)('total_vram_required').default(0),
estimatedDurationMs: (0, pg_core_1.integer)('estimated_duration_ms'),
// Progress tracking
progress: (0, pg_core_1.integer)('progress').default(0),
currentStepId: (0, pg_core_1.integer)('current_step_id'),
completedSteps: (0, pg_core_1.integer)('completed_steps').default(0),
totalSteps: (0, pg_core_1.integer)('total_steps').default(0),
// Error handling
error: (0, pg_core_1.text)('error'),
retryCount: (0, pg_core_1.integer)('retry_count').default(0),
maxRetries: (0, pg_core_1.integer)('max_retries').default(3),
// Timestamps
createdAt: (0, pg_core_1.timestamp)('created_at', { withTimezone: true }).defaultNow(),
queuedAt: (0, pg_core_1.timestamp)('queued_at', { withTimezone: true }),
startedAt: (0, pg_core_1.timestamp)('started_at', { withTimezone: true }),
completedAt: (0, pg_core_1.timestamp)('completed_at', { withTimezone: true }),
// Crash recovery
lastHeartbeat: (0, pg_core_1.timestamp)('last_heartbeat', { withTimezone: true }),
runningOnNode: (0, pg_core_1.text)('running_on_node'), // For future multi-node support
}, (table) => ({
statusIdx: (0, pg_core_1.index)('idx_pipelines_status').on(table.status),
priorityIdx: (0, pg_core_1.index)('idx_pipelines_priority').on(table.priority, table.createdAt),
heartbeatIdx: (0, pg_core_1.index)('idx_pipelines_heartbeat').on(table.lastHeartbeat),
}));
// Individual steps within a pipeline
exports.pipelineSteps = (0, pg_core_1.pgTable)('pipeline_steps', {
id: (0, pg_core_1.serial)('id').primaryKey(),
pipelineId: (0, pg_core_1.integer)('pipeline_id').notNull().references(() => exports.pipelines.id, { onDelete: 'cascade' }),
// Step definition
name: (0, pg_core_1.text)('name').notNull(),
stepType: (0, pg_core_1.text)('step_type').notNull().$type(),
sequenceOrder: (0, pg_core_1.integer)('sequence_order').notNull(),
// Dependencies (array of step IDs that must complete first)
dependsOn: (0, pg_core_1.jsonb)('depends_on').$type().default([]),
// Resource requirements
resourceCategory: (0, pg_core_1.text)('resource_category').default('cpu').$type(),
vramRequired: (0, pg_core_1.integer)('vram_required').default(0),
requiredContainers: (0, pg_core_1.jsonb)('required_containers').$type().default([]),
// Step configuration
config: (0, pg_core_1.jsonb)('config').$type(),
// Execution state
status: (0, pg_core_1.text)('status').notNull().default('pending').$type(),
progress: (0, pg_core_1.integer)('progress').default(0),
result: (0, pg_core_1.jsonb)('result').$type(),
error: (0, pg_core_1.text)('error'),
// Timing
estimatedDurationMs: (0, pg_core_1.integer)('estimated_duration_ms'),
startedAt: (0, pg_core_1.timestamp)('started_at', { withTimezone: true }),
completedAt: (0, pg_core_1.timestamp)('completed_at', { withTimezone: true }),
durationMs: (0, pg_core_1.integer)('duration_ms'),
}, (table) => ({
pipelineIdx: (0, pg_core_1.index)('idx_pipeline_steps_pipeline').on(table.pipelineId),
sequenceIdx: (0, pg_core_1.index)('idx_pipeline_steps_sequence').on(table.pipelineId, table.sequenceOrder),
statusIdx: (0, pg_core_1.index)('idx_pipeline_steps_status').on(table.status),
}));
// Step-level progress events for detailed observability
exports.pipelineStepEvents = (0, pg_core_1.pgTable)('pipeline_step_events', {
id: (0, pg_core_1.serial)('id').primaryKey(),
stepId: (0, pg_core_1.integer)('step_id').notNull().references(() => exports.pipelineSteps.id, { onDelete: 'cascade' }),
eventType: (0, pg_core_1.text)('event_type').notNull(), // 'stage_start', 'progress', 'prompt_call', 'stage_complete', 'log', 'error'
eventData: (0, pg_core_1.jsonb)('event_data').$type(),
createdAt: (0, pg_core_1.timestamp)('created_at', { withTimezone: true }).defaultNow(),
}, (table) => ({
stepIdx: (0, pg_core_1.index)('idx_pipeline_step_events_step').on(table.stepId),
createdAtIdx: (0, pg_core_1.index)('idx_pipeline_step_events_created').on(table.createdAt),
}));
// Real-time GPU/resource tracking snapshots
exports.resourceSnapshots = (0, pg_core_1.pgTable)('resource_snapshots', {
id: (0, pg_core_1.serial)('id').primaryKey(),
// GPU metrics (from nvidia-smi)
gpuIndex: (0, pg_core_1.integer)('gpu_index').default(0),
gpuName: (0, pg_core_1.text)('gpu_name'),
vramUsedMb: (0, pg_core_1.integer)('vram_used_mb'),
vramTotalMb: (0, pg_core_1.integer)('vram_total_mb'),
gpuUtilization: (0, pg_core_1.integer)('gpu_utilization'), // 0-100%
gpuTemperature: (0, pg_core_1.integer)('gpu_temperature'), // Celsius
// Active processes using GPU
activeProcesses: (0, pg_core_1.jsonb)('active_processes').$type(),
// Linked to pipeline/step if applicable
pipelineId: (0, pg_core_1.integer)('pipeline_id'),
stepId: (0, pg_core_1.integer)('step_id'),
createdAt: (0, pg_core_1.timestamp)('created_at', { withTimezone: true }).defaultNow(),
}, (table) => ({
createdAtIdx: (0, pg_core_1.index)('idx_resource_snapshots_created').on(table.createdAt),
pipelineIdx: (0, pg_core_1.index)('idx_resource_snapshots_pipeline').on(table.pipelineId),
}));
// Pipeline templates for common workflows
exports.pipelineTemplates = (0, pg_core_1.pgTable)('pipeline_templates', {
id: (0, pg_core_1.serial)('id').primaryKey(),
name: (0, pg_core_1.text)('name').notNull().unique(),
displayName: (0, pg_core_1.text)('display_name').notNull(),
description: (0, pg_core_1.text)('description'),
// Template definition - array of step configurations
steps: (0, pg_core_1.jsonb)('steps').$type().notNull(),
// Default context schema (for validation)
contextSchema: (0, pg_core_1.jsonb)('context_schema').$type(),
createdAt: (0, pg_core_1.timestamp)('created_at', { withTimezone: true }).defaultNow(),
updatedAt: (0, pg_core_1.timestamp)('updated_at', { withTimezone: true }),
}, (table) => ({
nameIdx: (0, pg_core_1.index)('idx_pipeline_templates_name').on(table.name),
}));
// Subscription Plans - Available subscription tiers
exports.subscriptionPlans = (0, pg_core_1.pgTable)('subscription_plans', {
id: (0, pg_core_1.serial)('id').primaryKey(),
name: (0, pg_core_1.text)('name').notNull().unique(), // 'monthly', 'quarterly', 'yearly', 'lifetime'
displayName: (0, pg_core_1.text)('display_name').notNull(), // 'Monthly Subscription'
description: (0, pg_core_1.text)('description'),
priceCAD: (0, pg_core_1.integer)('price_cad').notNull(), // Price in cents (e.g., 1000 = $10.00)
durationDays: (0, pg_core_1.integer)('duration_days'), // null for lifetime
isActive: (0, pg_core_1.boolean)('is_active').default(true),
sortOrder: (0, pg_core_1.integer)('sort_order').default(0),
createdAt: (0, pg_core_1.timestamp)('created_at', { withTimezone: true }).defaultNow(),
updatedAt: (0, pg_core_1.timestamp)('updated_at', { withTimezone: true }),
});
// User Subscriptions - Tracks individual user subscription status
exports.userSubscriptions = (0, pg_core_1.pgTable)('user_subscriptions', {
id: (0, pg_core_1.serial)('id').primaryKey(),
userId: (0, pg_core_1.integer)('user_id').notNull().references(() => exports.mediaUsers.id, { onDelete: 'cascade' }),
planId: (0, pg_core_1.integer)('plan_id').references(() => exports.subscriptionPlans.id),
status: (0, pg_core_1.text)('status').notNull().default('inactive').$type(),
startDate: (0, pg_core_1.timestamp)('start_date', { withTimezone: true }),
endDate: (0, pg_core_1.timestamp)('end_date', { withTimezone: true }), // null for lifetime
gracePeriodEndDate: (0, pg_core_1.timestamp)('grace_period_end_date', { withTimezone: true }),
lastPaymentId: (0, pg_core_1.integer)('last_payment_id'),
autoRenew: (0, pg_core_1.boolean)('auto_renew').default(true),
notes: (0, pg_core_1.text)('notes'),
createdAt: (0, pg_core_1.timestamp)('created_at', { withTimezone: true }).defaultNow(),
updatedAt: (0, pg_core_1.timestamp)('updated_at', { withTimezone: true }),
}, (table) => ({
userIdx: (0, pg_core_1.uniqueIndex)('idx_user_subscriptions_user').on(table.userId),
statusIdx: (0, pg_core_1.index)('idx_user_subscriptions_status').on(table.status),
endDateIdx: (0, pg_core_1.index)('idx_user_subscriptions_end_date').on(table.endDate),
}));
// Invoices - Generated invoices for users
exports.invoices = (0, pg_core_1.pgTable)('invoices', {
id: (0, pg_core_1.serial)('id').primaryKey(),
userId: (0, pg_core_1.integer)('user_id').notNull().references(() => exports.mediaUsers.id, { onDelete: 'cascade' }),
planId: (0, pg_core_1.integer)('plan_id').references(() => exports.subscriptionPlans.id),
invoiceNumber: (0, pg_core_1.text)('invoice_number').notNull().unique(), // INV-2026-00001
amountCAD: (0, pg_core_1.integer)('amount_cad').notNull(), // Amount in cents
status: (0, pg_core_1.text)('status').notNull().default('pending').$type(),
dueDate: (0, pg_core_1.timestamp)('due_date', { withTimezone: true }).notNull(),
paidAt: (0, pg_core_1.timestamp)('paid_at', { withTimezone: true }),
paymentId: (0, pg_core_1.integer)('payment_id'),
description: (0, pg_core_1.text)('description'),
notes: (0, pg_core_1.text)('notes'),
createdAt: (0, pg_core_1.timestamp)('created_at', { withTimezone: true }).defaultNow(),
updatedAt: (0, pg_core_1.timestamp)('updated_at', { withTimezone: true }),
}, (table) => ({
userIdx: (0, pg_core_1.index)('idx_invoices_user').on(table.userId),
statusIdx: (0, pg_core_1.index)('idx_invoices_status').on(table.status),
dueDateIdx: (0, pg_core_1.index)('idx_invoices_due_date').on(table.dueDate),
invoiceNumberIdx: (0, pg_core_1.index)('idx_invoices_number').on(table.invoiceNumber),
}));
// Payments - Records of actual payments received (e-transfers)
exports.payments = (0, pg_core_1.pgTable)('payments', {
id: (0, pg_core_1.serial)('id').primaryKey(),
invoiceId: (0, pg_core_1.integer)('invoice_id').references(() => exports.invoices.id),
userId: (0, pg_core_1.integer)('user_id').references(() => exports.mediaUsers.id, { onDelete: 'set null' }),
// E-transfer details
senderEmail: (0, pg_core_1.text)('sender_email').notNull(),
senderName: (0, pg_core_1.text)('sender_name'),
amountCAD: (0, pg_core_1.integer)('amount_cad').notNull(), // Amount in cents
referenceNumber: (0, pg_core_1.text)('reference_number').unique(), // Interac reference
customMessage: (0, pg_core_1.text)('custom_message'), // Contains username hint
// Matching/processing status
status: (0, pg_core_1.text)('status').notNull().default('unmatched').$type(),
matchConfidence: (0, pg_core_1.real)('match_confidence'), // 0.0-1.0 confidence score
matchMethod: (0, pg_core_1.text)('match_method'), // 'email_exact', 'username_message', 'manual', 'llm'
// Raw email data for audit trail
rawEmailJson: (0, pg_core_1.jsonb)('raw_email_json').$type(),
receivedAt: (0, pg_core_1.timestamp)('received_at', { withTimezone: true }).notNull(),
processedAt: (0, pg_core_1.timestamp)('processed_at', { withTimezone: true }),
processedBy: (0, pg_core_1.integer)('processed_by'), // Admin who manually matched
createdAt: (0, pg_core_1.timestamp)('created_at', { withTimezone: true }).defaultNow(),
updatedAt: (0, pg_core_1.timestamp)('updated_at', { withTimezone: true }),
}, (table) => ({
userIdx: (0, pg_core_1.index)('idx_payments_user').on(table.userId),
invoiceIdx: (0, pg_core_1.index)('idx_payments_invoice').on(table.invoiceId),
statusIdx: (0, pg_core_1.index)('idx_payments_status').on(table.status),
senderEmailIdx: (0, pg_core_1.index)('idx_payments_sender_email').on(table.senderEmail),
referenceIdx: (0, pg_core_1.index)('idx_payments_reference').on(table.referenceNumber),
receivedAtIdx: (0, pg_core_1.index)('idx_payments_received_at').on(table.receivedAt),
}));
// Payment Audit Log - Audit trail for all payment operations
exports.paymentAuditLog = (0, pg_core_1.pgTable)('payment_audit_log', {
id: (0, pg_core_1.serial)('id').primaryKey(),
paymentId: (0, pg_core_1.integer)('payment_id').references(() => exports.payments.id),
invoiceId: (0, pg_core_1.integer)('invoice_id').references(() => exports.invoices.id),
userId: (0, pg_core_1.integer)('user_id').references(() => exports.mediaUsers.id),
action: (0, pg_core_1.text)('action').notNull(), // 'created', 'matched', 'applied', 'refunded', 'manual_override', etc.
previousState: (0, pg_core_1.jsonb)('previous_state').$type(),
newState: (0, pg_core_1.jsonb)('new_state').$type(),
performedBy: (0, pg_core_1.integer)('performed_by'), // Admin user ID
notes: (0, pg_core_1.text)('notes'),
ipAddress: (0, pg_core_1.text)('ip_address'),
createdAt: (0, pg_core_1.timestamp)('created_at', { withTimezone: true }).defaultNow(),
}, (table) => ({
paymentIdx: (0, pg_core_1.index)('idx_payment_audit_payment').on(table.paymentId),
invoiceIdx: (0, pg_core_1.index)('idx_payment_audit_invoice').on(table.invoiceId),
userIdx: (0, pg_core_1.index)('idx_payment_audit_user').on(table.userId),
actionIdx: (0, pg_core_1.index)('idx_payment_audit_action').on(table.action),
createdAtIdx: (0, pg_core_1.index)('idx_payment_audit_created').on(table.createdAt),
}));
// ============================================
// Notification System Tables
// ============================================
// Notification types enum
exports.NOTIFICATION_TYPES = [
'friend_request',
'friend_request_accepted',
'poke',
'video_recommendation',
'watch_party_invite',
'achievement_unlocked',
'comment_reply',
'digest_completed',
'digest_failed',
'upload_approved',
'upload_rejected',
'system',
];
// Main notifications table
exports.notifications = (0, pg_core_1.pgTable)('notifications', {
id: (0, pg_core_1.serial)('id').primaryKey(),
userId: (0, pg_core_1.integer)('user_id').notNull().references(() => exports.mediaUsers.id, { onDelete: 'cascade' }),
type: (0, pg_core_1.text)('type').notNull().$type(),
title: (0, pg_core_1.text)('title').notNull(),
message: (0, pg_core_1.text)('message'),
referenceType: (0, pg_core_1.text)('reference_type'), // 'user', 'media', 'watch_party', 'digest', 'upload', etc.
referenceId: (0, pg_core_1.integer)('reference_id'),
actorId: (0, pg_core_1.integer)('actor_id').references(() => exports.mediaUsers.id, { onDelete: 'set null' }),
metadata: (0, pg_core_1.jsonb)('metadata').$type(),
isRead: (0, pg_core_1.boolean)('is_read').default(false),
actionUrl: (0, pg_core_1.text)('action_url'),
groupKey: (0, pg_core_1.text)('group_key'), // For aggregation (e.g., "poke_123" to group multiple pokes from same user)
createdAt: (0, pg_core_1.timestamp)('created_at', { withTimezone: true }).defaultNow(),
readAt: (0, pg_core_1.timestamp)('read_at', { withTimezone: true }),
}, (table) => ({
userUnreadIdx: (0, pg_core_1.index)('idx_notifications_user_unread').on(table.userId, table.isRead),
userCreatedIdx: (0, pg_core_1.index)('idx_notifications_user_created').on(table.userId, table.createdAt),
typeIdx: (0, pg_core_1.index)('idx_notifications_type').on(table.type),
createdAtIdx: (0, pg_core_1.index)('idx_notifications_created').on(table.createdAt),
actorIdx: (0, pg_core_1.index)('idx_notifications_actor').on(table.actorId),
}));
// User notification preferences table
exports.notificationPreferences = (0, pg_core_1.pgTable)('notification_preferences', {
id: (0, pg_core_1.serial)('id').primaryKey(),
userId: (0, pg_core_1.integer)('user_id').notNull().unique().references(() => exports.mediaUsers.id, { onDelete: 'cascade' }),
// Email toggles per notification type
emailFriendRequest: (0, pg_core_1.boolean)('email_friend_request').default(true),
emailFriendRequestAccepted: (0, pg_core_1.boolean)('email_friend_request_accepted').default(true),
emailPoke: (0, pg_core_1.boolean)('email_poke').default(true),
emailVideoRecommendation: (0, pg_core_1.boolean)('email_video_recommendation').default(true),
emailWatchPartyInvite: (0, pg_core_1.boolean)('email_watch_party_invite').default(true),
emailAchievement: (0, pg_core_1.boolean)('email_achievement').default(false),
emailDigestComplete: (0, pg_core_1.boolean)('email_digest_complete').default(true),
emailDigestFailed: (0, pg_core_1.boolean)('email_digest_failed').default(true),
emailUploadApproved: (0, pg_core_1.boolean)('email_upload_approved').default(true),
emailUploadRejected: (0, pg_core_1.boolean)('email_upload_rejected').default(true),
// In-app toggles per notification type (default all on)
inAppFriendRequest: (0, pg_core_1.boolean)('inapp_friend_request').default(true),
inAppFriendRequestAccepted: (0, pg_core_1.boolean)('inapp_friend_request_accepted').default(true),
inAppPoke: (0, pg_core_1.boolean)('inapp_poke').default(true),
inAppVideoRecommendation: (0, pg_core_1.boolean)('inapp_video_recommendation').default(true),
inAppWatchPartyInvite: (0, pg_core_1.boolean)('inapp_watch_party_invite').default(true),
inAppAchievement: (0, pg_core_1.boolean)('inapp_achievement').default(true),
inAppDigestComplete: (0, pg_core_1.boolean)('inapp_digest_complete').default(true),
inAppDigestFailed: (0, pg_core_1.boolean)('inapp_digest_failed').default(true),
inAppUploadApproved: (0, pg_core_1.boolean)('inapp_upload_approved').default(true),
inAppUploadRejected: (0, pg_core_1.boolean)('inapp_upload_rejected').default(true),
inAppCommentReply: (0, pg_core_1.boolean)('inapp_comment_reply').default(true),
inAppSystem: (0, pg_core_1.boolean)('inapp_system').default(true),
// Quiet hours settings
quietHoursEnabled: (0, pg_core_1.boolean)('quiet_hours_enabled').default(false),
quietHoursStart: (0, pg_core_1.text)('quiet_hours_start'), // "22:00" (24-hour format)
quietHoursEnd: (0, pg_core_1.text)('quiet_hours_end'), // "08:00"
updatedAt: (0, pg_core_1.timestamp)('updated_at', { withTimezone: true }).defaultNow(),
}, (table) => ({
userIdx: (0, pg_core_1.index)('idx_notification_preferences_user').on(table.userId),
}));
// ============================================
// Geo-Blocking Tables
// ============================================
exports.GEO_BLOCKING_MODES = ['disabled', 'blocklist', 'allowlist'];
exports.geoBlockingRules = (0, pg_core_1.pgTable)('geo_blocking_rules', {
id: (0, pg_core_1.serial)('id').primaryKey(),
country: (0, pg_core_1.text)('country').notNull(), // ISO Alpha-2 code (required)
countryName: (0, pg_core_1.text)('country_name'), // Display name
region: (0, pg_core_1.text)('region'), // Optional - state/province ISO code
city: (0, pg_core_1.text)('city'), // Optional - city name
createdAt: (0, pg_core_1.timestamp)('created_at', { withTimezone: true }).defaultNow(),
createdBy: (0, pg_core_1.integer)('created_by').references(() => exports.mediaUsers.id),
note: (0, pg_core_1.text)('note'), // Admin note explaining why blocked
}, (table) => ({
countryIdx: (0, pg_core_1.index)('idx_geo_blocking_country').on(table.country),
uniqueRule: (0, pg_core_1.uniqueIndex)('idx_geo_blocking_unique').on(table.country, table.region, table.city),
}));
// ============================================
// Inbox Publishing Tracking
// ============================================
exports.INBOX_FILE_TYPES = ['clip', 'scene', 'video'];
exports.publishedInboxFiles = (0, pg_core_1.pgTable)('published_inbox_files', {
id: (0, pg_core_1.serial)('id').primaryKey(),
sourceInboxPath: (0, pg_core_1.text)('source_inbox_path').notNull(), // Original path in inbox
filename: (0, pg_core_1.text)('filename').notNull(),
publishedAt: (0, pg_core_1.timestamp)('published_at', { withTimezone: true }).defaultNow(),
publishedToPath: (0, pg_core_1.text)('published_to_path'), // Destination path in public directory
publicMediaId: (0, pg_core_1.integer)('public_media_id').references(() => exports.publicMedia.id, { onDelete: 'set null' }),
fileType: (0, pg_core_1.text)('file_type').$type().notNull(),
}, (table) => ({
sourcePathIdx: (0, pg_core_1.index)('idx_published_inbox_source').on(table.sourceInboxPath),
fileTypeIdx: (0, pg_core_1.index)('idx_published_inbox_file_type').on(table.fileType),
}));
// ============================================
// Video OCR Results Table
// ============================================
exports.OCR_TEXT_TYPES = ['credits', 'title_card', 'overlay', 'unknown'];
exports.videoOcrResults = (0, pg_core_1.pgTable)('video_ocr_results', {
id: (0, pg_core_1.serial)('id').primaryKey(),
digestId: (0, pg_core_1.integer)('digest_id').notNull(),
videoId: (0, pg_core_1.integer)('video_id').notNull(),
frameIndex: (0, pg_core_1.integer)('frame_index').notNull(),
timestamp: (0, pg_core_1.real)('timestamp').notNull(),
extractedText: (0, pg_core_1.text)('extracted_text').notNull(),
confidence: (0, pg_core_1.real)('confidence'),
textType: (0, pg_core_1.text)('text_type').$type(),
sourceFrame: (0, pg_core_1.text)('source_frame').notNull(),
createdAt: (0, pg_core_1.timestamp)('created_at', { withTimezone: true }).defaultNow(),
}, (table) => ({
digestIdx: (0, pg_core_1.index)('idx_video_ocr_results_digest').on(table.digestId),
videoIdx: (0, pg_core_1.index)('idx_video_ocr_results_video').on(table.videoId),
}));
//# sourceMappingURL=schema.js.map