Introduces a migration script to import all videos from Bunny.net into the PostgreSQL database, populating the `videos` table with relevant metadata and handling both new and existing video entries. Updates schema to include Bunny.net specific fields. Replit-Commit-Author: Agent Replit-Commit-Session-Id: d7424866-83d1-4486-a212-ac12b4c7becf Replit-Commit-Checkpoint-Type: full_checkpoint Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/8cc42625-c1f5-4e43-99bd-77f2c4dedee2/d7424866-83d1-4486-a212-ac12b4c7becf/t16ASiD
165 lines
6.8 KiB
TypeScript
165 lines
6.8 KiB
TypeScript
import { sql } from "drizzle-orm";
|
|
import { pgTable, text, varchar, integer, timestamp, boolean, real } from "drizzle-orm/pg-core";
|
|
import { createInsertSchema } from "drizzle-zod";
|
|
import { z } from "zod";
|
|
|
|
export const videos = pgTable("videos", {
|
|
id: varchar("id").primaryKey(),
|
|
title: text("title").notNull(),
|
|
description: text("description").default("").notNull(),
|
|
thumbnailUrl: text("thumbnail_url").notNull(),
|
|
customThumbnailUrl: text("custom_thumbnail_url"),
|
|
videoUrl: text("video_url").notNull(),
|
|
videoUrlMp4: text("video_url_mp4"),
|
|
videoUrlIframe: text("video_url_iframe"),
|
|
duration: integer("duration").notNull(), // in seconds
|
|
views: integer("views").notNull().default(0),
|
|
category: text("category").default("").notNull(),
|
|
tags: text("tags").array().default([]).notNull(),
|
|
isPublic: boolean("is_public").default(true).notNull(),
|
|
uploadStatus: text("upload_status").default("completed").notNull(), // pending, processing, completed, failed
|
|
originalFileName: text("original_file_name"),
|
|
fileSize: integer("file_size"), // in bytes
|
|
bitrate: integer("bitrate"), // in kbps
|
|
resolution: text("resolution"), // e.g., "1920x1080"
|
|
format: text("format"), // e.g., "mp4", "avi", "mov"
|
|
encoding: text("encoding"), // e.g., "h264", "h265"
|
|
bunnyId: varchar("bunny_id"), // Original Bunny.net video ID
|
|
bunnyLibraryId: varchar("bunny_library_id"), // Bunny.net library ID
|
|
source: text("source").default("bunny").notNull(), // bunny, upload, manual
|
|
createdAt: timestamp("created_at").notNull().default(sql`CURRENT_TIMESTAMP`),
|
|
updatedAt: timestamp("updated_at").notNull().default(sql`CURRENT_TIMESTAMP`),
|
|
});
|
|
|
|
// User table for authentication and video ownership
|
|
export const users = pgTable("users", {
|
|
id: varchar("id").primaryKey().default(sql`gen_random_uuid()`),
|
|
username: varchar("username", { length: 50 }).notNull().unique(),
|
|
email: varchar("email", { length: 255 }).notNull().unique(),
|
|
password: varchar("password", { length: 255 }).notNull(),
|
|
firstName: varchar("first_name", { length: 100 }),
|
|
lastName: varchar("last_name", { length: 100 }),
|
|
profileImageUrl: text("profile_image_url"),
|
|
isAdmin: boolean("is_admin").default(false).notNull(),
|
|
createdAt: timestamp("created_at").notNull().default(sql`CURRENT_TIMESTAMP`),
|
|
updatedAt: timestamp("updated_at").notNull().default(sql`CURRENT_TIMESTAMP`),
|
|
});
|
|
|
|
// Video uploads table for tracking upload progress
|
|
export const videoUploads = pgTable("video_uploads", {
|
|
id: varchar("id").primaryKey().default(sql`gen_random_uuid()`),
|
|
userId: varchar("user_id").notNull().references(() => users.id),
|
|
videoId: varchar("video_id").references(() => videos.id),
|
|
originalFileName: text("original_file_name").notNull(),
|
|
fileSize: integer("file_size").notNull(),
|
|
mimeType: text("mime_type").notNull(),
|
|
uploadStatus: text("upload_status").default("uploading").notNull(), // uploading, processing, completed, failed
|
|
uploadProgress: real("upload_progress").default(0).notNull(), // 0-1
|
|
errorMessage: text("error_message"),
|
|
uploadUrl: text("upload_url"), // presigned URL for upload
|
|
createdAt: timestamp("created_at").notNull().default(sql`CURRENT_TIMESTAMP`),
|
|
updatedAt: timestamp("updated_at").notNull().default(sql`CURRENT_TIMESTAMP`),
|
|
});
|
|
|
|
// Categories table for better organization
|
|
export const categories = pgTable("categories", {
|
|
id: varchar("id").primaryKey().default(sql`gen_random_uuid()`),
|
|
name: text("name").notNull().unique(),
|
|
description: text("description"),
|
|
color: varchar("color", { length: 7 }).default("#000000").notNull(), // hex color
|
|
createdAt: timestamp("created_at").notNull().default(sql`CURRENT_TIMESTAMP`),
|
|
});
|
|
|
|
// Tags table for better organization
|
|
export const tags = pgTable("tags", {
|
|
id: varchar("id").primaryKey().default(sql`gen_random_uuid()`),
|
|
name: text("name").notNull().unique(),
|
|
useCount: integer("use_count").default(0).notNull(),
|
|
createdAt: timestamp("created_at").notNull().default(sql`CURRENT_TIMESTAMP`),
|
|
});
|
|
|
|
// Video tags junction table
|
|
export const videoTags = pgTable("video_tags", {
|
|
videoId: varchar("video_id").notNull().references(() => videos.id, { onDelete: "cascade" }),
|
|
tagId: varchar("tag_id").notNull().references(() => tags.id, { onDelete: "cascade" }),
|
|
});
|
|
|
|
// Video ads/spots table for storing advertisement metadata
|
|
export const videoAds = pgTable("video_ads", {
|
|
id: varchar("id").primaryKey().default(sql`gen_random_uuid()`),
|
|
videoId: varchar("video_id").notNull().references(() => videos.id, { onDelete: "cascade" }),
|
|
adType: text("ad_type").notNull(), // preroll, midroll, postroll
|
|
adUrl: text("ad_url").notNull(), // VAST URL or direct video URL
|
|
adTitle: text("ad_title"),
|
|
adDuration: integer("ad_duration"), // in seconds
|
|
position: integer("position").default(0), // for midroll ads - position in video (seconds)
|
|
skipAfter: integer("skip_after"), // seconds after which ad can be skipped
|
|
vastTag: text("vast_tag"), // VAST XML tag URL
|
|
adNetwork: text("ad_network"), // network name (e.g., Google AdX, Publift)
|
|
isActive: boolean("is_active").default(true).notNull(),
|
|
priority: integer("priority").default(1), // ad priority (1 = highest)
|
|
createdAt: timestamp("created_at").notNull().default(sql`CURRENT_TIMESTAMP`),
|
|
updatedAt: timestamp("updated_at").notNull().default(sql`CURRENT_TIMESTAMP`),
|
|
});
|
|
|
|
// Schemas for form validation and API
|
|
export const insertVideoSchema = createInsertSchema(videos).omit({
|
|
id: true,
|
|
createdAt: true,
|
|
updatedAt: true,
|
|
});
|
|
|
|
export const updateVideoSchema = createInsertSchema(videos).omit({
|
|
id: true,
|
|
createdAt: true,
|
|
updatedAt: true,
|
|
}).partial();
|
|
|
|
export const insertUserSchema = createInsertSchema(users).omit({
|
|
id: true,
|
|
createdAt: true,
|
|
updatedAt: true,
|
|
});
|
|
|
|
export const insertVideoUploadSchema = createInsertSchema(videoUploads).omit({
|
|
id: true,
|
|
createdAt: true,
|
|
updatedAt: true,
|
|
});
|
|
|
|
export const insertCategorySchema = createInsertSchema(categories).omit({
|
|
id: true,
|
|
createdAt: true,
|
|
});
|
|
|
|
export const insertTagSchema = createInsertSchema(tags).omit({
|
|
id: true,
|
|
createdAt: true,
|
|
});
|
|
|
|
export const insertVideoAdSchema = createInsertSchema(videoAds).omit({
|
|
id: true,
|
|
createdAt: true,
|
|
updatedAt: true,
|
|
});
|
|
|
|
// Type exports
|
|
export type Video = typeof videos.$inferSelect;
|
|
export type InsertVideo = z.infer<typeof insertVideoSchema>;
|
|
export type UpdateVideo = z.infer<typeof updateVideoSchema>;
|
|
|
|
export type VideoAd = typeof videoAds.$inferSelect;
|
|
export type InsertVideoAd = z.infer<typeof insertVideoAdSchema>;
|
|
|
|
export type User = typeof users.$inferSelect;
|
|
export type InsertUser = z.infer<typeof insertUserSchema>;
|
|
|
|
export type VideoUpload = typeof videoUploads.$inferSelect;
|
|
export type InsertVideoUpload = z.infer<typeof insertVideoUploadSchema>;
|
|
|
|
export type Category = typeof categories.$inferSelect;
|
|
export type InsertCategory = z.infer<typeof insertCategorySchema>;
|
|
|
|
export type Tag = typeof tags.$inferSelect;
|
|
export type InsertTag = z.infer<typeof insertTagSchema>;
|