Add new video attributes and improve storage management

Implement a hybrid storage solution combining Bunny.net for video operations and PostgreSQL for user data. Update video schema with face detection and content type fields, and add a `isSuperAdmin` flag for user roles.

Replit-Commit-Author: Agent
Replit-Commit-Session-Id: 22c71427-e484-49cc-bab4-5c9a7f7d98bc
Replit-Commit-Checkpoint-Type: full_checkpoint
Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/8cc42625-c1f5-4e43-99bd-77f2c4dedee2/22c71427-e484-49cc-bab4-5c9a7f7d98bc/JP4XKDb
This commit is contained in:
sebastjanartic 2025-09-02 12:05:41 +00:00
parent c71720454f
commit e394e3f671
2 changed files with 147 additions and 6 deletions

View File

@ -40,4 +40,3 @@ args = "npm run dev"
waitForPort = 5000
[agent]
integrations = ["javascript_database==1.0.0", "javascript_log_in_with_replit==1.0.0", "javascript_object_storage==1.0.0"]

View File

@ -391,11 +391,16 @@ export class MemStorage implements IStorage {
description: video.description || "",
category: video.category || "",
customThumbnailUrl: null,
faceCenterPosition: null,
facesDetected: 0,
faceConfidence: 0,
videoUrlMp4: null,
videoUrlIframe: null,
tags: [],
isPublic: true,
views: video.views || 0,
contentType: video.contentType || 'video',
genre: video.genre || 'other',
createdAt: new Date(),
updatedAt: new Date(),
uploadStatus: video.uploadStatus || "completed",
@ -442,11 +447,16 @@ export class MemStorage implements IStorage {
description: video.description || "",
category: video.category || "",
customThumbnailUrl: null,
faceCenterPosition: null,
facesDetected: 0,
faceConfidence: 0,
videoUrlMp4: null,
videoUrlIframe: null,
tags: video.tags || [],
isPublic: video.isPublic ?? true,
views: video.views || 0,
contentType: video.contentType || 'video',
genre: video.genre || 'other',
uploadStatus: video.uploadStatus || "completed",
originalFileName: video.originalFileName || null,
fileSize: video.fileSize || null,
@ -526,6 +536,7 @@ export class MemStorage implements IStorage {
lastName: user.lastName || null,
profileImageUrl: user.profileImageUrl || null,
isAdmin: user.isAdmin ?? false,
isSuperAdmin: user.isSuperAdmin ?? false,
createdAt: new Date(),
updatedAt: new Date()
};
@ -770,8 +781,8 @@ class BunnyStorage implements IStorage {
}
// Update views cache
if (views !== undefined) {
this.viewsCache.set(id, (this.viewsCache.get(id) || 0) + (views - (this.viewsCache.get(id) || 0)));
if (views !== undefined && typeof views === 'number') {
this.viewsCache.set(id, views);
}
// Return updated video
@ -899,14 +910,145 @@ class BunnyStorage implements IStorage {
}
}
// Storage selection logic - choose DatabaseStorage if PostgreSQL is available
// Hybrid storage implementation that uses Bunny.net for videos and Database for users
export class HybridStorage implements IStorage {
private bunnyStorage: BunnyStorage;
private databaseStorage: DatabaseStorage;
constructor() {
this.bunnyStorage = new BunnyStorage();
this.databaseStorage = new DatabaseStorage();
}
// Video operations - use Bunny.net
async getVideos(limit?: number, offset?: number, search?: string): Promise<Video[]> {
return this.bunnyStorage.getVideos(limit, offset, search);
}
async getVideo(id: string): Promise<Video | undefined> {
return this.bunnyStorage.getVideo(id);
}
async createVideo(video: InsertVideo): Promise<Video> {
return this.bunnyStorage.createVideo(video);
}
async updateVideo(id: string, video: UpdateVideo): Promise<Video | undefined> {
return this.bunnyStorage.updateVideo(id, video);
}
async updateVideoViews(id: string): Promise<void> {
return this.bunnyStorage.updateVideoViews(id);
}
async getVideoCount(search?: string): Promise<number> {
return this.bunnyStorage.getVideoCount(search);
}
async deleteVideo(id: string): Promise<boolean> {
return this.bunnyStorage.deleteVideo(id);
}
// User operations - use Database
async getUser(id: string): Promise<User | undefined> {
return this.databaseStorage.getUser(id);
}
async getUserByEmail(email: string): Promise<User | undefined> {
return this.databaseStorage.getUserByEmail(email);
}
async getUserByUsername(username: string): Promise<User | undefined> {
return this.databaseStorage.getUserByUsername(username);
}
async createUser(user: InsertUser): Promise<User> {
return this.databaseStorage.createUser(user);
}
async updateUser(id: string, user: Partial<InsertUser>): Promise<User | undefined> {
return this.databaseStorage.updateUser(id, user);
}
async upsertUser(user: any): Promise<User> {
return this.databaseStorage.upsertUser(user);
}
async validateUserPassword(email: string, password: string): Promise<User | null> {
return this.databaseStorage.validateUserPassword(email, password);
}
// Upload operations - use Database
async createVideoUpload(upload: InsertVideoUpload): Promise<VideoUpload> {
return this.databaseStorage.createVideoUpload(upload);
}
async getVideoUpload(id: string): Promise<VideoUpload | undefined> {
return this.databaseStorage.getVideoUpload(id);
}
async updateVideoUpload(id: string, upload: Partial<InsertVideoUpload>): Promise<VideoUpload | undefined> {
return this.databaseStorage.updateVideoUpload(id, upload);
}
async getUserVideoUploads(userId: string): Promise<VideoUpload[]> {
return this.databaseStorage.getUserVideoUploads(userId);
}
// Category operations - use Database
async getCategories(): Promise<Category[]> {
return this.databaseStorage.getCategories();
}
async createCategory(category: InsertCategory): Promise<Category> {
return this.databaseStorage.createCategory(category);
}
async updateCategory(id: string, category: Partial<InsertCategory>): Promise<Category | undefined> {
return this.databaseStorage.updateCategory(id, category);
}
async deleteCategory(id: string): Promise<boolean> {
return this.databaseStorage.deleteCategory(id);
}
// Tag operations - use Database
async getTags(): Promise<Tag[]> {
return this.databaseStorage.getTags();
}
async getPopularTags(limit?: number): Promise<Tag[]> {
return this.databaseStorage.getPopularTags(limit);
}
async createTag(tag: InsertTag): Promise<Tag> {
return this.databaseStorage.createTag(tag);
}
async incrementTagUse(name: string): Promise<void> {
return this.databaseStorage.incrementTagUse(name);
}
}
// Storage selection logic - use hybrid approach when both Bunny.net and Database are available
let storage: IStorage;
const hasDatabase = process.env.DATABASE_URL;
const hasBunnyConfig = process.env.BUNNY_API_KEY && process.env.BUNNY_LIBRARY_ID && process.env.BUNNY_HOSTNAME;
// Prioritize Bunny.net storage for user's video content
if (hasBunnyConfig) {
// Use hybrid storage when both Bunny.net and Database are available
if (hasBunnyConfig && hasDatabase) {
try {
storage = new HybridStorage();
console.log('✅ Using Hybrid storage (Bunny.net for videos + Database for users)');
} catch (error) {
console.error('❌ Failed to initialize Hybrid storage:', error);
console.log('📁 Falling back to memory storage');
storage = new MemStorage();
}
}
// Prioritize Bunny.net storage for video content only
else if (hasBunnyConfig) {
try {
storage = new BunnyStorage();
console.log('✅ Using Bunny.net storage with library ID:', process.env.BUNNY_LIBRARY_ID);