videofolxtv/shared/schema.ts
sebastjanartic c71720454f Add admin dashboard and Replit authentication integration
Integrates Replit authentication using OpenID Connect, adds an admin dashboard route with video management and thumbnail upload capabilities, and updates dependencies.

Replit-Commit-Author: Agent
Replit-Commit-Session-Id: 890577b1-c154-40a4-a177-a0c6d55320c3
Replit-Commit-Checkpoint-Type: full_checkpoint
Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/8cc42625-c1f5-4e43-99bd-77f2c4dedee2/890577b1-c154-40a4-a177-a0c6d55320c3/1jMBtLj
2025-09-02 12:01:00 +00:00

181 lines
7.7 KiB
TypeScript

import { sql } from "drizzle-orm";
import { pgTable, text, varchar, integer, timestamp, boolean, real, pgEnum, jsonb, index } from "drizzle-orm/pg-core";
import { createInsertSchema } from "drizzle-zod";
import { z } from "zod";
// Enums for better type safety
export const contentTypeEnum = pgEnum('content_type', ['video', 'oddaja', 'music_video', 'documentary', 'live']);
export const genreEnum = pgEnum('genre', ['volksmusik', 'schlager', 'pop', 'rock', 'country', 'instrumental', 'dance', 'other']);
export const videos = pgTable("videos", {
id: varchar("id").primaryKey().default(sql`gen_random_uuid()`),
title: text("title").notNull(),
description: text("description").default("").notNull(),
thumbnailUrl: text("thumbnail_url").notNull(),
customThumbnailUrl: text("custom_thumbnail_url"),
faceCenterPosition: text("face_center_position"), // CSS object-position for face centering
facesDetected: integer("faces_detected").default(0), // Number of faces detected
faceConfidence: real("face_confidence").default(0), // Confidence score of primary face
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(),
contentType: contentTypeEnum("content_type").default('video').notNull(),
genre: genreEnum("genre").default('other').notNull(),
tags: text("tags").array().default([]).notNull(),
isPublic: boolean("is_public").default(true).notNull(),
uploadStatus: text("upload_status").default("pending").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"
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(),
isSuperAdmin: boolean("is_super_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" }),
});
// Session storage table for Replit Auth
export const sessions = pgTable("sessions", {
sid: varchar("sid").primaryKey(),
sess: jsonb("sess").notNull(),
expire: timestamp("expire").notNull(),
}, (table) => [
index("IDX_session_expire").on(table.expire)
]);
// 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>;