import mongoose from "mongoose"; import { FileType } from "@/types"; const ProjectSchema = new mongoose.Schema({ space_id: { type: String, required: true, index: true }, user_id: { type: String, required: true, index: true }, prompts: { type: [String], default: [], }, // Legacy field for backward compatibility html: { type: String, default: "" }, title: { type: String, default: "Untitled Project", maxlength: 200 }, // Enhanced file structure support fileStructure: { rootFolderId: { type: mongoose.Schema.Types.ObjectId, ref: 'Folder', default: null }, totalFiles: { type: Number, default: 0, min: 0 }, totalSize: { type: Number, default: 0, min: 0 }, lastFileModified: { type: Date, default: Date.now } }, // Project settings settings: { defaultFileType: { type: String, enum: [ 'html', 'css', 'js', 'ts', 'jsx', 'tsx', 'vue', 'json', 'md', 'txt', 'py', 'php', 'xml', 'svg', 'yaml', 'yml', 'png', 'jpg', 'jpeg', 'gif', 'ico', 'webp', 'pdf' ] as FileType[], default: 'html' }, autoSave: { type: Boolean, default: true }, aiAssistance: { type: Boolean, default: true }, theme: { type: String, enum: ['dark', 'light', 'auto'], default: 'dark' }, language: { type: String, default: 'en' } }, // Project metadata metadata: { description: { type: String, maxlength: 1000, default: "" }, tags: [{ type: String, maxlength: 50 }], category: { type: String, enum: ['website', 'webapp', 'api', 'component', 'template', 'other'], default: 'website' }, isPublic: { type: Boolean, default: false }, framework: { type: String, enum: ['vanilla', 'react', 'vue', 'angular', 'svelte', 'next', 'nuxt', 'other'], default: 'vanilla' }, version: { type: String, default: "1.0.0" } }, // Performance and analytics analytics: { totalViews: { type: Number, default: 0 }, totalEdits: { type: Number, default: 0 }, aiGenerations: { type: Number, default: 0 }, lastAccessed: { type: Date, default: Date.now }, buildTime: { type: Number, // milliseconds default: 0 }, deploymentStatus: { type: String, enum: ['pending', 'building', 'deployed', 'failed', 'none'], default: 'none' } } }, { timestamps: true, // Custom timestamp field names for backward compatibility createdAt: '_createdAt', updatedAt: '_updatedAt' }); // Indexes for better performance ProjectSchema.index({ user_id: 1, space_id: 1 }, { unique: true }); ProjectSchema.index({ user_id: 1, '_createdAt': -1 }); ProjectSchema.index({ 'metadata.isPublic': 1, '_createdAt': -1 }); ProjectSchema.index({ 'metadata.category': 1 }); ProjectSchema.index({ 'analytics.lastAccessed': -1 }); // Pre-save middleware ProjectSchema.pre('save', function(this: unknown, next) { // Narrow type const proj = this as unknown as { title?: string; generateTitle: () => string; analytics?: { lastAccessed?: Date } }; // Update analytics if (proj.analytics) { proj.analytics.lastAccessed = new Date(); } // Auto-generate title if not set if (!proj.title || proj.title === "Untitled Project") { proj.title = proj.generateTitle(); } next(); }); // Instance methods ProjectSchema.methods.generateTitle = function(): string { if (this.space_id) { const parts = this.space_id.split('/'); return parts[parts.length - 1] || "Untitled Project"; } return "Untitled Project"; }; ProjectSchema.methods.updateFileStructureStats = async function() { const File = mongoose.model('File'); // Count total files and calculate total size const stats = await File.aggregate([ { $match: { projectId: this._id } }, { $group: { _id: null, totalFiles: { $sum: 1 }, totalSize: { $sum: '$size' }, lastModified: { $max: '$updatedAt' } } } ]); if (stats.length > 0) { this.fileStructure.totalFiles = stats[0].totalFiles; this.fileStructure.totalSize = stats[0].totalSize; this.fileStructure.lastFileModified = stats[0].lastModified; } else { this.fileStructure.totalFiles = 0; this.fileStructure.totalSize = 0; } await this.save(); }; ProjectSchema.methods.initializeFileStructure = async function() { if (this.fileStructure.rootFolderId) { return; // Already initialized } const Folder = mongoose.model('Folder') as unknown as { createRootFolder: (projectId: string) => Promise<{ _id: string | { toString: () => string } }> }; const rootFolder = await Folder.createRootFolder((this._id as unknown as { toString: () => string }).toString()); this.fileStructure.rootFolderId = rootFolder._id as unknown as mongoose.Types.ObjectId; await this.save(); return rootFolder; }; // Static methods ProjectSchema.statics.findByUser = function(userId: string, limit: number = 100) { return this.find({ user_id: userId }) .sort({ '_createdAt': -1 }) .limit(limit) .lean(); }; ProjectSchema.statics.findPublicProjects = function(limit: number = 50) { return this.find({ 'metadata.isPublic': true }) .sort({ 'analytics.totalViews': -1, '_createdAt': -1 }) .limit(limit) .lean(); }; ProjectSchema.statics.searchProjects = function(userId: string, query: string) { return this.find({ user_id: userId, $or: [ { title: { $regex: query, $options: 'i' } }, { 'metadata.description': { $regex: query, $options: 'i' } }, { 'metadata.tags': { $in: [new RegExp(query, 'i')] } }, { space_id: { $regex: query, $options: 'i' } } ] }).sort({ 'analytics.lastAccessed': -1 }); }; export default mongoose.models.Project || mongoose.model("Project", ProjectSchema);