"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