🏗ïļ Complete Platform Hub Backend Implementation Plan

Comprehensive Backend Implementation for SolidProfessor Platform Hub:

📚 Table of Contents

🗄ïļ Complete Database Schema

Core Tables

-- Users (extend existing)
ALTER TABLE users ADD COLUMN avatar_url VARCHAR(500) NULL;
ALTER TABLE users ADD COLUMN bio TEXT NULL;
ALTER TABLE users ADD COLUMN website VARCHAR(255) NULL;
ALTER TABLE users ADD COLUMN location VARCHAR(100) NULL;
ALTER TABLE users ADD COLUMN is_verified BOOLEAN DEFAULT FALSE;
ALTER TABLE users ADD COLUMN follower_count INT DEFAULT 0;
ALTER TABLE users ADD COLUMN following_count INT DEFAULT 0;
ALTER TABLE users ADD COLUMN models_count INT DEFAULT 0;

-- Models Table
CREATE TABLE models (
    id BIGINT PRIMARY KEY AUTO_INCREMENT,
    uuid VARCHAR(36) UNIQUE NOT NULL,
    title VARCHAR(255) NOT NULL,
    description TEXT,
    slug VARCHAR(255) UNIQUE,
    author_id BIGINT NOT NULL,
    category_id BIGINT NULL,
    
    -- File Information
    file_path VARCHAR(500) NOT NULL,
    thumbnail_path VARCHAR(500) NULL,
    preview_images JSON NULL, -- Array of preview image URLs
    file_size BIGINT NOT NULL, -- bytes
    file_format VARCHAR(20) NOT NULL, -- STL, OBJ, PLY, etc.
    original_filename VARCHAR(255) NOT NULL,
    
    -- Model Metadata
    polygon_count INT NULL,
    vertex_count INT NULL,
    bounding_box JSON NULL, -- {x, y, z dimensions}
    print_settings JSON NULL, -- Print recommendations
    software_created VARCHAR(100) NULL,
    software_version VARCHAR(50) NULL,
    
    -- Status & Visibility
    status ENUM('draft', 'pending_review', 'published', 'rejected', 'archived') DEFAULT 'draft',
    visibility ENUM('public', 'unlisted', 'private') DEFAULT 'public',
    is_featured BOOLEAN DEFAULT FALSE,
    is_downloadable BOOLEAN DEFAULT TRUE,
    is_remixable BOOLEAN DEFAULT TRUE,
    
    -- Licensing
    license VARCHAR(100) DEFAULT 'Creative Commons - Attribution',
    commercial_use BOOLEAN DEFAULT TRUE,
    attribution_required BOOLEAN DEFAULT TRUE,
    
    -- SEO & Discovery
    search_keywords TEXT NULL,
    view_count INT DEFAULT 0,
    
    -- Moderation
    moderation_notes TEXT NULL,
    moderated_by BIGINT NULL,
    moderated_at TIMESTAMP NULL,
    
    -- Timestamps
    published_at TIMESTAMP NULL,
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
    deleted_at TIMESTAMP NULL,
    
    -- Indexes
    INDEX idx_models_author (author_id, status, published_at DESC),
    INDEX idx_models_category (category_id, status, published_at DESC),
    INDEX idx_models_featured (is_featured, status, published_at DESC),
    INDEX idx_models_status (status, visibility, published_at DESC),
    INDEX idx_models_search (title, description, search_keywords),
    FULLTEXT idx_fulltext_search (title, description, search_keywords),
    
    FOREIGN KEY (author_id) REFERENCES users(id) ON DELETE CASCADE,
    FOREIGN KEY (category_id) REFERENCES model_categories(id) ON DELETE SET NULL,
    FOREIGN KEY (moderated_by) REFERENCES users(id) ON DELETE SET NULL
);

-- Model Categories
CREATE TABLE model_categories (
    id BIGINT PRIMARY KEY AUTO_INCREMENT,
    name VARCHAR(100) NOT NULL UNIQUE,
    slug VARCHAR(100) NOT NULL UNIQUE,
    description TEXT,
    parent_id BIGINT NULL,
    icon VARCHAR(50) NULL,
    color VARCHAR(7) NULL, -- Hex color
    sort_order INT DEFAULT 0,
    is_active BOOLEAN DEFAULT TRUE,
    models_count INT DEFAULT 0,
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    
    INDEX idx_categories_parent (parent_id, sort_order),
    INDEX idx_categories_active (is_active, sort_order),
    INDEX idx_categories_models_count (models_count DESC),
    
    FOREIGN KEY (parent_id) REFERENCES model_categories(id) ON DELETE CASCADE
);

-- Model Tags (Many-to-Many)
CREATE TABLE model_tags (
    id BIGINT PRIMARY KEY AUTO_INCREMENT,
    name VARCHAR(50) NOT NULL UNIQUE,
    slug VARCHAR(50) NOT NULL UNIQUE,
    description TEXT NULL,
    usage_count INT DEFAULT 0,
    is_trending BOOLEAN DEFAULT FALSE,
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    
    INDEX idx_tags_usage (usage_count DESC),
    INDEX idx_tags_trending (is_trending, usage_count DESC),
    INDEX idx_tags_name (name)
);

CREATE TABLE model_tag_assignments (
    model_id BIGINT NOT NULL,
    tag_id BIGINT NOT NULL,
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    
    PRIMARY KEY (model_id, tag_id),
    INDEX idx_tag_assignments_tag (tag_id),
    
    FOREIGN KEY (model_id) REFERENCES models(id) ON DELETE CASCADE,
    FOREIGN KEY (tag_id) REFERENCES model_tags(id) ON DELETE CASCADE
);

-- Model Analytics & Stats
CREATE TABLE model_stats (
    model_id BIGINT PRIMARY KEY,
    views_count INT DEFAULT 0,
    unique_views_count INT DEFAULT 0,
    downloads_count INT DEFAULT 0,
    likes_count INT DEFAULT 0,
    comments_count INT DEFAULT 0,
    saves_count INT DEFAULT 0,
    shares_count INT DEFAULT 0,
    remixes_count INT DEFAULT 0,
    
    -- Time-based analytics for trending
    views_today INT DEFAULT 0,
    views_week INT DEFAULT 0,
    views_month INT DEFAULT 0,
    downloads_week INT DEFAULT 0,
    downloads_month INT DEFAULT 0,
    
    -- Engagement metrics
    avg_rating DECIMAL(3,2) DEFAULT 0.00,
    rating_count INT DEFAULT 0,
    bounce_rate DECIMAL(5,2) DEFAULT 0.00,
    
    last_viewed_at TIMESTAMP NULL,
    updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
    
    INDEX idx_stats_trending (views_week DESC, likes_count DESC),
    INDEX idx_stats_popular (downloads_count DESC, likes_count DESC),
    INDEX idx_stats_rating (avg_rating DESC, rating_count DESC),
    
    FOREIGN KEY (model_id) REFERENCES models(id) ON DELETE CASCADE
);

-- User Collections
CREATE TABLE user_collections (
    id BIGINT PRIMARY KEY AUTO_INCREMENT,
    user_id BIGINT NOT NULL,
    name VARCHAR(100) NOT NULL,
    description TEXT,
    is_public BOOLEAN DEFAULT FALSE,
    is_featured BOOLEAN DEFAULT FALSE,
    models_count INT DEFAULT 0,
    cover_image_url VARCHAR(500) NULL,
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
    
    INDEX idx_collections_user (user_id, is_public),
    INDEX idx_collections_featured (is_featured, models_count DESC),
    
    FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE
);

CREATE TABLE collection_models (
    collection_id BIGINT NOT NULL,
    model_id BIGINT NOT NULL,
    added_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    sort_order INT DEFAULT 0,
    
    PRIMARY KEY (collection_id, model_id),
    INDEX idx_collection_models_model (model_id),
    INDEX idx_collection_models_sort (collection_id, sort_order),
    
    FOREIGN KEY (collection_id) REFERENCES user_collections(id) ON DELETE CASCADE,
    FOREIGN KEY (model_id) REFERENCES models(id) ON DELETE CASCADE
);

Social Features Tables

-- Model Likes
CREATE TABLE model_likes (
    id BIGINT PRIMARY KEY AUTO_INCREMENT,
    model_id BIGINT NOT NULL,
    user_id BIGINT NOT NULL,
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    
    UNIQUE KEY unique_model_user_like (model_id, user_id),
    INDEX idx_model_likes_model (model_id),
    INDEX idx_model_likes_user (user_id, created_at DESC),
    
    FOREIGN KEY (model_id) REFERENCES models(id) ON DELETE CASCADE,
    FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE
);

-- Model Comments
CREATE TABLE model_comments (
    id BIGINT PRIMARY KEY AUTO_INCREMENT,
    model_id BIGINT NOT NULL,
    user_id BIGINT NOT NULL,
    parent_comment_id BIGINT NULL,
    content TEXT NOT NULL,
    is_edited BOOLEAN DEFAULT FALSE,
    likes_count INT DEFAULT 0,
    replies_count INT DEFAULT 0,
    status ENUM('published', 'pending', 'hidden', 'spam') DEFAULT 'published',
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
    deleted_at TIMESTAMP NULL,
    
    INDEX idx_comments_model (model_id, status, created_at DESC),
    INDEX idx_comments_parent (parent_comment_id),
    INDEX idx_comments_user (user_id, created_at DESC),
    INDEX idx_comments_status (status),
    
    FOREIGN KEY (model_id) REFERENCES models(id) ON DELETE CASCADE,
    FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE,
    FOREIGN KEY (parent_comment_id) REFERENCES model_comments(id) ON DELETE CASCADE
);

-- Comment Likes
CREATE TABLE comment_likes (
    id BIGINT PRIMARY KEY AUTO_INCREMENT,
    comment_id BIGINT NOT NULL,
    user_id BIGINT NOT NULL,
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    
    UNIQUE KEY unique_comment_user_like (comment_id, user_id),
    INDEX idx_comment_likes_comment (comment_id),
    INDEX idx_comment_likes_user (user_id),
    
    FOREIGN KEY (comment_id) REFERENCES model_comments(id) ON DELETE CASCADE,
    FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE
);

-- User Saves/Bookmarks
CREATE TABLE model_saves (
    id BIGINT PRIMARY KEY AUTO_INCREMENT,
    model_id BIGINT NOT NULL,
    user_id BIGINT NOT NULL,
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    
    UNIQUE KEY unique_model_user_save (model_id, user_id),
    INDEX idx_model_saves_user (user_id, created_at DESC),
    INDEX idx_model_saves_model (model_id),
    
    FOREIGN KEY (model_id) REFERENCES models(id) ON DELETE CASCADE,
    FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE
);

-- User Follows
CREATE TABLE user_follows (
    id BIGINT PRIMARY KEY AUTO_INCREMENT,
    follower_id BIGINT NOT NULL,
    following_id BIGINT NOT NULL,
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    
    UNIQUE KEY unique_follow_relationship (follower_id, following_id),
    INDEX idx_follows_follower (follower_id),
    INDEX idx_follows_following (following_id),
    
    FOREIGN KEY (follower_id) REFERENCES users(id) ON DELETE CASCADE,
    FOREIGN KEY (following_id) REFERENCES users(id) ON DELETE CASCADE
);

-- Model Ratings
CREATE TABLE model_ratings (
    id BIGINT PRIMARY KEY AUTO_INCREMENT,
    model_id BIGINT NOT NULL,
    user_id BIGINT NOT NULL,
    rating TINYINT NOT NULL CHECK (rating >= 1 AND rating <= 5),
    review TEXT NULL,
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
    
    UNIQUE KEY unique_model_user_rating (model_id, user_id),
    INDEX idx_model_ratings_model (model_id, rating),
    INDEX idx_model_ratings_user (user_id),
    
    FOREIGN KEY (model_id) REFERENCES models(id) ON DELETE CASCADE,
    FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE
);

File Upload & Processing Tables

-- Upload Sessions (for chunked uploads)
CREATE TABLE upload_sessions (
    id VARCHAR(36) PRIMARY KEY, -- UUID
    user_id BIGINT NOT NULL,
    filename VARCHAR(255) NOT NULL,
    file_size BIGINT NOT NULL,
    mime_type VARCHAR(100) NOT NULL,
    chunks_total INT NOT NULL,
    chunks_uploaded INT DEFAULT 0,
    chunk_size INT NOT NULL,
    status ENUM('pending', 'uploading', 'processing', 'completed', 'failed', 'expired') DEFAULT 'pending',
    temp_path VARCHAR(500) NULL,
    final_path VARCHAR(500) NULL,
    metadata JSON NULL,
    error_message TEXT NULL,
    expires_at TIMESTAMP NOT NULL,
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
    
    INDEX idx_upload_sessions_user (user_id, status),
    INDEX idx_upload_sessions_expires (expires_at),
    INDEX idx_upload_sessions_status (status, created_at),
    
    FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE
);

-- File Processing Jobs
CREATE TABLE file_processing_jobs (
    id BIGINT PRIMARY KEY AUTO_INCREMENT,
    upload_session_id VARCHAR(36) NULL,
    model_id BIGINT NULL,
    job_type ENUM('validation', 'thumbnail', 'preview', 'optimization', 'virus_scan', 'metadata_extraction') NOT NULL,
    status ENUM('pending', 'processing', 'completed', 'failed') DEFAULT 'pending',
    progress INT DEFAULT 0, -- 0-100
    result JSON NULL,
    error_message TEXT NULL,
    started_at TIMESTAMP NULL,
    completed_at TIMESTAMP NULL,
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    
    INDEX idx_processing_jobs_session (upload_session_id),
    INDEX idx_processing_jobs_model (model_id),
    INDEX idx_processing_jobs_status (status, created_at),
    INDEX idx_processing_jobs_type (job_type, status),
    
    FOREIGN KEY (upload_session_id) REFERENCES upload_sessions(id) ON DELETE CASCADE,
    FOREIGN KEY (model_id) REFERENCES models(id) ON DELETE CASCADE
);

-- Download Tracking
CREATE TABLE model_downloads (
    id BIGINT PRIMARY KEY AUTO_INCREMENT,
    model_id BIGINT NOT NULL,
    user_id BIGINT NULL,
    ip_address VARCHAR(45) NOT NULL,
    user_agent TEXT NULL,
    referrer VARCHAR(500) NULL,
    country_code VARCHAR(2) NULL,
    download_type ENUM('original', 'stl', 'obj', 'preview') DEFAULT 'original',
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    
    INDEX idx_downloads_model (model_id, created_at),
    INDEX idx_downloads_user (user_id, created_at),
    INDEX idx_downloads_ip (ip_address, created_at),
    INDEX idx_downloads_country (country_code, created_at),
    
    FOREIGN KEY (model_id) REFERENCES models(id) ON DELETE CASCADE,
    FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE SET NULL
);

📁 File Storage Architecture

🗂ïļ Directory Structure

storage/
├── uploads/
│   ├── temp/           # Temporary upload chunks
│   │   ├── {session-id}/
│   │   │   ├── chunk_1
│   │   │   ├── chunk_2
│   │   │   └── metadata.json
│   ├── models/         # Final model files
│   │   ├── {year}/
│   │   │   ├── {month}/
│   │   │   │   ├── {uuid}/
│   │   │   │   │   ├── original.sldprt
│   │   │   │   │   ├── optimized.stl
│   │   │   │   │   ├── web_preview.obj
│   │   │   │   │   └── metadata.json
│   ├── thumbnails/     # Generated thumbnails
│   │   ├── {year}/
│   │   │   ├── {month}/
│   │   │   │   ├── {uuid}/
│   │   │   │   │   ├── thumb_200.webp
│   │   │   │   │   ├── thumb_400.webp
│   │   │   │   │   └── thumb_800.webp
│   └── previews/       # Preview images & videos
│       ├── {year}/
│       │   ├── {month}/
│       │   │   ├── {uuid}/
│       │   │   │   ├── preview_1.webp
│       │   │   │   ├── preview_2.webp
│       │   │   │   ├── preview_3.webp
│       │   │   │   └── rotation_video.mp4

☁ïļ CDN Integration

// File URL Generation Service
class FileUrlService {
    public function getModelUrl(Model $model, string $type = 'original'): string {
        $year = $model->created_at->format('Y');
        $month = $model->created_at->format('m');
        $basePath = "models/{$year}/{$month}/{$model->uuid}";
        
        return match($type) {
            'download' => $this->generateSignedUrl($basePath . '/original.' . $model->file_format),
            'preview' => $this->getCdnUrl($basePath . '/web_preview.obj'),
            'thumbnail' => $this->getCdnUrl("thumbnails/{$year}/{$month}/{$model->uuid}/thumb_400.webp"),
            'thumbnail_large' => $this->getCdnUrl("thumbnails/{$year}/{$month}/{$model->uuid}/thumb_800.webp"),
            default => $this->getCdnUrl($basePath . '/original.' . $model->file_format)
        };
    }
    
    public function getPreviewImages(Model $model): array {
        $year = $model->created_at->format('Y');
        $month = $model->created_at->format('m');
        $basePath = "previews/{$year}/{$month}/{$model->uuid}";
        
        $images = [];
        for ($i = 1; $i <= 5; $i++) {
            $url = $this->getCdnUrl($basePath . "/preview_{$i}.webp");
            if ($this->urlExists($url)) {
                $images[] = $url;
            }
        }
        
        return $images;
    }
    
    private function getCdnUrl(string $path): string {
        return env('CDN_URL') . '/' . $path;
    }
    
    private function generateSignedUrl(string $path): string {
        return Storage::disk('s3')->temporaryUrl($path, now()->addMinutes(30));
    }
}

📋 Models List API

1. Get Models List with Advanced Filtering

GET /api/v1/models?page=1&limit=24&sort=newest&category=mechanical&search=gear&tags=automotive,3d-printing&featured=true&license=cc&format=stl&min_rating=4

Query Parameters:

Response Structure:

{
  "data": [
    {
      "id": "550e8400-e29b-41d4-a716-446655440000",
      "title": "Mechanical Gear Assembly",
      "description": "High-precision mechanical gear system for industrial applications...",
      "slug": "mechanical-gear-assembly",
      "thumbnailUrl": "https://cdn.solidprofessor.com/thumbnails/2025/01/550e8400/thumb_400.webp",
      "previewImages": [
        "https://cdn.solidprofessor.com/previews/2025/01/550e8400/preview_1.webp",
        "https://cdn.solidprofessor.com/previews/2025/01/550e8400/preview_2.webp",
        "https://cdn.solidprofessor.com/previews/2025/01/550e8400/preview_3.webp"
      ],
      "author": {
        "id": 123,
        "name": "Alex Rodriguez",
        "username": "alex_cad_pro",
        "avatarUrl": "https://cdn.solidprofessor.com/avatars/123.webp",
        "isVerified": true,
        "badge": "Pro Designer",
        "stats": {
          "totalModels": 47,
          "totalLikes": 2341,
          "followers": 1250
        }
      },
      "category": {
        "id": 5,
        "name": "Mechanical",
        "slug": "mechanical",
        "icon": "cog",
        "color": "#348303"
      },
      "tags": ["gear", "mechanical", "automotive", "3d-printing", "industrial"],
      "stats": {
        "views": 1847,
        "uniqueViews": 1203,
        "downloads": 142,
        "likes": 89,
        "comments": 18,
        "saves": 65,
        "rating": 4.7,
        "ratingCount": 23
      },
      "fileInfo": {
        "format": "SOLIDWORKS",
        "originalFormat": "SLDPRT",
        "size": "2.4 MB",
        "sizeBytes": 2516582,
        "polygonCount": 15420,
        "alternativeFormats": ["STL", "OBJ", "STEP"]
      },
      "interactions": {
        "isLiked": false,
        "isSaved": true,
        "hasDownloaded": false,
        "canDownload": true,
        "userRating": null
      },
      "licensing": {
        "type": "Creative Commons - Attribution",
        "commercialUse": true,
        "attributionRequired": true,
        "remixAllowed": true
      },
      "features": {
        "isFeatured": false,
        "isDownloadable": true,
        "isRemixable": true,
        "hasPrintSettings": true,
        "hasPreviewVideo": true
      },
      "publishedAt": "2025-01-20T10:00:00Z",
      "createdAt": "2025-01-20T10:00:00Z",
      "trending": {
        "rank": 5,
        "category": "weekly"
      }
    }
  ],
  "meta": {
    "currentPage": 1,
    "lastPage": 15,
    "perPage": 24,
    "total": 350,
    "from": 1,
    "to": 24,
    "hasMore": true
  },
  "filters": {
    "categories": [
      {
        "id": 1,
        "name": "Mechanical",
        "slug": "mechanical",
        "count": 150,
        "icon": "cog",
        "color": "#348303"
      },
      {
        "id": 2,
        "name": "Architectural",
        "slug": "architectural", 
        "count": 75,
        "icon": "building",
        "color": "#2c5aa0"
      }
    ],
    "popularTags": [
      {"name": "3d-printing", "count": 200},
      {"name": "mechanical", "count": 150},
      {"name": "gear", "count": 45},
      {"name": "automotive", "count": 67}
    ],
    "formats": [
      {"name": "STL", "count": 280},
      {"name": "OBJ", "count": 50},
      {"name": "SOLIDWORKS", "count": 120},
      {"name": "STEP", "count": 85}
    ],
    "licenses": [
      {"type": "CC Attribution", "count": 200},
      {"type": "CC Share-Alike", "count": 80},
      {"type": "MIT", "count": 45},
      {"type": "Proprietary", "count": 25}
    ],
    "ratings": {
      "5": 45,
      "4": 89,
      "3": 67,
      "2": 23,
      "1": 8
    }
  },
  "suggestions": {
    "relatedSearches": ["mechanical gears", "gear train", "automotive parts"],
    "trendingNow": ["electric vehicle parts", "drone components", "miniature mechanisms"]
  }
}

2. Get Trending Models

GET /api/v1/models/trending?period=week&category=mechanical&limit=12

3. Get Featured Models

GET /api/v1/models/featured?limit=8&rotation=daily

4. Get Models by Author

GET /api/v1/users/{userId}/models?sort=newest&status=published

5. Search Autocomplete

GET /api/v1/models/search/suggestions?q=mech&limit=10

🔍 Model Detail API

1. Get Model Details

GET /api/v1/models/{uuid}?include=comments,related,author_stats

Response Structure:

{
  "data": {
    "id": "550e8400-e29b-41d4-a716-446655440000",
    "title": "Mechanical Gear Assembly",
    "description": "A comprehensive mechanical gear assembly designed for industrial applications. This model features precision-engineered teeth, optimal load distribution, and compatibility with standard mounting systems.\n\nDesigned with SolidWorks 2024, this assembly includes detailed drawings and manufacturing specifications. Perfect for educational purposes or as a starting point for custom gear systems.",
    "slug": "mechanical-gear-assembly",
    "author": {
      "id": 123,
      "name": "Alex Rodriguez",
      "username": "alex_cad_pro",
      "avatarUrl": "https://cdn.solidprofessor.com/avatars/123.webp",
      "isVerified": true,
      "badge": "Pro Designer",
      "bio": "Mechanical engineer with 10+ years of experience in CAD design and manufacturing. Specialized in precision mechanical components.",
      "website": "https://alexrodriguez.design",
      "location": "San Francisco, CA",
      "joinedDate": "2023-05-15T10:00:00Z",
      "stats": {
        "totalModels": 47,
        "totalLikes": 2341,
        "totalDownloads": 15420,
        "followers": 1250,
        "following": 89,
        "avgRating": 4.6
      },
      "socialLinks": {
        "linkedin": "https://linkedin.com/in/alexrodriguez",
        "github": "https://github.com/alexcadpro"
      },
      "isFollowing": false
    },
    "files": {
      "primary": {
        "downloadUrl": "/api/v1/models/550e8400/download",
        "filename": "gear_assembly_v2.sldprt",
        "size": 2516582,
        "sizeFormatted": "2.4 MB",
        "format": "SOLIDWORKS",
        "version": "2024"
      },
      "alternativeFormats": [
        {
          "downloadUrl": "/api/v1/models/550e8400/download?format=stl",
          "filename": "gear_assembly_v2.stl",
          "size": 1024000,
          "sizeFormatted": "1.0 MB",
          "format": "STL"
        },
        {
          "downloadUrl": "/api/v1/models/550e8400/download?format=obj",
          "filename": "gear_assembly_v2.obj",
          "size": 856000,
          "sizeFormatted": "856 KB",
          "format": "OBJ"
        },
        {
          "downloadUrl": "/api/v1/models/550e8400/download?format=step",
          "filename": "gear_assembly_v2.step",
          "size": 1536000,
          "sizeFormatted": "1.5 MB",
          "format": "STEP"
        }
      ],
      "supportingFiles": [
        {
          "type": "drawing",
          "filename": "technical_drawing.pdf",
          "size": 245760,
          "downloadUrl": "/api/v1/models/550e8400/files/drawing"
        },
        {
          "type": "assembly_instructions",
          "filename": "assembly_guide.pdf",
          "size": 512000,
          "downloadUrl": "/api/v1/models/550e8400/files/instructions"
        }
      ]
    },
    "media": {
      "thumbnailUrl": "https://cdn.solidprofessor.com/thumbnails/2025/01/550e8400/thumb_800.webp",
      "previewImages": [
        {
          "url": "https://cdn.solidprofessor.com/previews/2025/01/550e8400/preview_1.webp",
          "caption": "Isometric view showing overall assembly",
          "type": "isometric"
        },
        {
          "url": "https://cdn.solidprofessor.com/previews/2025/01/550e8400/preview_2.webp", 
          "caption": "Section view revealing internal structure",
          "type": "section"
        },
        {
          "url": "https://cdn.solidprofessor.com/previews/2025/01/550e8400/preview_3.webp",
          "caption": "Exploded view showing individual components",
          "type": "exploded"
        },
        {
          "url": "https://cdn.solidprofessor.com/previews/2025/01/550e8400/preview_4.webp",
          "caption": "Detail view of gear teeth profile",
          "type": "detail"
        }
      ],
      "previewVideo": {
        "url": "https://cdn.solidprofessor.com/previews/2025/01/550e8400/rotation.mp4",
        "thumbnail": "https://cdn.solidprofessor.com/previews/2025/01/550e8400/video_thumb.webp",
        "duration": 15
      },
      "interactive3D": {
        "viewerUrl": "https://viewer.solidprofessor.com/embed/550e8400",
        "modelUrl": "https://cdn.solidprofessor.com/models/2025/01/550e8400/web_preview.obj"
      }
    },
    "metadata": {
      "category": {
        "id": 5,
        "name": "Mechanical",
        "slug": "mechanical",
        "icon": "cog",
        "color": "#348303",
        "breadcrumb": ["Engineering", "Mechanical", "Gears"]
      },
      "tags": ["gear", "mechanical", "industrial", "precision", "assembly", "solidworks"],
      "specifications": {
        "polygonCount": 15420,
        "vertexCount": 7710,
        "fileSize": 2516582,
        "fileSizeFormatted": "2.4 MB",
        "dimensions": {
          "x": 100.5,
          "y": 80.0,
          "z": 25.0,
          "units": "mm"
        },
        "boundingBox": {
          "min": {"x": -50.25, "y": -40.0, "z": 0},
          "max": {"x": 50.25, "y": 40.0, "z": 25.0}
        },
        "scale": "1:1",
        "accuracy": "Âą0.01mm"
      },
      "technical": {
        "software": {
          "created": "SolidWorks 2024",
          "version": "2024 SP1",
          "compatible": ["SolidWorks 2020+", "Fusion 360", "Inventor 2022+"]
        },
        "materials": {
          "recommended": ["Steel AISI 1045", "Aluminum 6061-T6"],
          "properties": {
            "hardness": "HRC 45-50",
            "tensileStrength": "600-700 MPa"
          }
        },
        "manufacturing": {
          "processes": ["CNC Machining", "Gear Hobbing", "Heat Treatment"],
          "tolerance": "IT7",
          "surfaceFinish": "Ra 1.6Ξm"
        }
      }
    },
    "licensing": {
      "type": "Creative Commons - Attribution",
      "code": "CC-BY-4.0",
      "description": "You are free to use, modify, and distribute with attribution",
      "url": "https://creativecommons.org/licenses/by/4.0/",
      "commercialUse": true,
      "attributionRequired": true,
      "shareAlike": false,
      "derivatives": true,
      "attribution": "Alex Rodriguez - SolidProfessor Hub"
    },
    "stats": {
      "views": 1847,
      "uniqueViews": 1203,
      "downloads": 142,
      "likes": 89,
      "comments": 18,
      "saves": 65,
      "shares": 12,
      "remixes": 3,
      "rating": {
        "average": 4.7,
        "count": 23,
        "breakdown": {
          "5": 15,
          "4": 6,
          "3": 2,
          "2": 0,
          "1": 0
        }
      }
    },
    "interactions": {
      "isLiked": false,
      "isSaved": true,
      "hasDownloaded": false,
      "canDownload": true,
      "canComment": true,
      "canRate": true,
      "userRating": null,
      "canRemix": true,
      "canReport": true
    },
    "printSettings": {
      "recommended": {
        "layerHeight": "0.2mm",
        "infill": "20%",
        "supports": true,
        "supportType": "Tree supports",
        "bedAdhesion": "Brim",
        "nozzleTemperature": "210°C",
        "bedTemperature": "60°C",
        "printSpeed": "50mm/s"
      },
      "materials": ["PLA", "PETG", "ABS"],
      "estimatedPrintTime": "4h 30m",
      "estimatedFilament": "45g",
      "difficulty": "Intermediate",
      "postProcessing": ["Support removal", "Light sanding"]
    },
    "history": {
      "publishedAt": "2025-01-20T10:00:00Z",
      "createdAt": "2025-01-20T09:30:00Z",
      "updatedAt": "2025-01-22T15:30:00Z",
      "versions": [
        {
          "version": "2.0",
          "date": "2025-01-22T15:30:00Z",
          "changes": "Improved gear tooth profile, added assembly instructions"
        },
        {
          "version": "1.0", 
          "date": "2025-01-20T10:00:00Z",
          "changes": "Initial release"
        }
      ]
    },
    "relatedModels": [
      {
        "id": "another-uuid-here",
        "title": "Bearing Housing Assembly",
        "thumbnailUrl": "https://cdn.solidprofessor.com/thumbnails/another/thumb_400.webp",
        "author": {
          "name": "Alex Rodriguez",
          "username": "alex_cad_pro"
        },
        "stats": {
          "likes": 67,
          "downloads": 45,
          "rating": 4.5
        },
        "similarity": 0.85
      }
    ],
    "collections": [
      {
        "id": 15,
        "name": "Mechanical Assemblies",
        "owner": {
          "name": "Engineering Library",
          "username": "eng_library"
        },
        "isPublic": true,
        "modelsCount": 24
      }
    ]
  }
}

2. Track Model View

POST /api/v1/models/{uuid}/view

3. Download Model

POST /api/v1/models/{uuid}/download?format=stl

4. Get Model Comments

GET /api/v1/models/{uuid}/comments?sort=newest&limit=20&include_replies=true

5. Rate Model

POST /api/v1/models/{uuid}/rate
{
  "rating": 5,
  "review": "Excellent model with great attention to detail!"
}

ðŸ“Ī Upload System API

Complete Upload Flow

1. Initialize Upload Session

POST /api/v1/uploads/init

Request Body:

{
  "filename": "mechanical_gear_assembly.sldprt",
  "fileSize": 2516582,
  "mimeType": "application/sldprt",
  "chunkSize": 1048576,
  "fileHash": "sha256:a665a45920422f9d417e4867efdc4fb8a04a1f3fff1fa07e998e86f7f7a27ae3"
}

Response:

{
  "data": {
    "sessionId": "550e8400-e29b-41d4-a716-446655440000",
    "chunksTotal": 3,
    "chunkSize": 1048576,
    "uploadUrls": [
      "https://s3.amazonaws.com/bucket/upload/session-id/chunk-1?signature=...",
      "https://s3.amazonaws.com/bucket/upload/session-id/chunk-2?signature=...",
      "https://s3.amazonaws.com/bucket/upload/session-id/chunk-3?signature=..."
    ],
    "expiresAt": "2025-01-24T12:00:00Z"
  }
}

2. Upload Chunks (Direct to S3)

PUT {uploadUrl} (Direct S3 upload)

3. Confirm Chunk Upload

POST /api/v1/uploads/{sessionId}/chunks/{chunkNumber}/confirm

4. Complete Upload

POST /api/v1/uploads/{sessionId}/complete

5. Create Model

POST /api/v1/models

Request Body:

{
  "uploadSessionId": "550e8400-e29b-41d4-a716-446655440000",
  "title": "Mechanical Gear Assembly",
  "description": "A high-precision gear assembly designed for industrial applications...",
  "categoryId": 5,
  "tags": ["gear", "mechanical", "industrial", "precision"],
  "license": "CC-BY-4.0",
  "visibility": "public",
  "isDownloadable": true,
  "isRemixable": true,
  "softwareInfo": {
    "name": "SolidWorks",
    "version": "2024 SP1"
  },
  "printSettings": {
    "layerHeight": "0.2mm",
    "infill": "20%",
    "supports": true,
    "material": "PLA",
    "estimatedTime": "4h 30m"
  },
  "technicalSpecs": {
    "dimensions": {
      "x": 100.5,
      "y": 80.0,
      "z": 25.0,
      "units": "mm"
    },
    "materials": ["Steel AISI 1045", "Aluminum 6061-T6"],
    "tolerance": "Âą0.01mm"
  },
  "publishImmediately": true,
  "notifyFollowers": true
}

Response:

{
  "data": {
    "id": "550e8400-e29b-41d4-a716-446655440000",
    "title": "Mechanical Gear Assembly",
    "status": "processing",
    "author": {
      "id": 123,
      "name": "Current User"
    },
    "processingJobs": [
      {
        "type": "virus_scan",
        "status": "pending",
        "estimatedDuration": "30s"
      },
      {
        "type": "validation",
        "status": "pending", 
        "estimatedDuration": "1m"
      },
      {
        "type": "thumbnail",
        "status": "pending",
        "estimatedDuration": "2m"
      },
      {
        "type": "preview",
        "status": "pending",
        "estimatedDuration": "3m"
      },
      {
        "type": "metadata_extraction",
        "status": "pending",
        "estimatedDuration": "1m"
      }
    ],
    "estimatedProcessingTime": "5-7 minutes",
    "statusUrl": "/api/v1/models/550e8400/processing-status",
    "editUrl": "/models/550e8400/edit"
  }
}

6. Check Processing Status

GET /api/v1/models/{uuid}/processing-status

7. Upload Preview Images

POST /api/v1/models/{uuid}/preview-images

💎 Social Features API

Likes & Saves

POST /api/v1/models/{uuid}/like
DELETE /api/v1/models/{uuid}/like
POST /api/v1/models/{uuid}/save
DELETE /api/v1/models/{uuid}/save

Following & Collections

POST /api/v1/users/{userId}/follow
DELETE /api/v1/users/{userId}/follow
GET /api/v1/collections
POST /api/v1/collections/{id}/models/{uuid}

Comments System

Get Comments

GET /api/v1/models/{uuid}/comments?sort=newest&limit=20&include_replies=true

Post Comment

POST /api/v1/models/{uuid}/comments
{
  "content": "Amazing work! The detail on the gear teeth is incredible.",
  "parentCommentId": null,
  "mentions": ["@alex_cad_pro"]
}

Like Comment

POST /api/v1/comments/{commentId}/like

Report Content

POST /api/v1/models/{uuid}/report
{
  "reason": "inappropriate_content",
  "description": "Contains copyrighted material without permission",
  "category": "copyright"
}

🏗ïļ Laravel Implementation

Core Models & Relationships

// app/Models/Model.php
class Model extends Eloquent implements Searchable
{
    use HasFactory, SoftDeletes, Searchable, HasUuids;
    
    protected $fillable = [
        'title', 'description', 'author_id', 'category_id', 'status',
        'visibility', 'file_path', 'file_size', 'file_format',
        'polygon_count', 'license', 'is_featured', 'is_downloadable'
    ];
    
    protected $casts = [
        'preview_images' => 'array',
        'bounding_box' => 'array',
        'print_settings' => 'array',
        'technical_specs' => 'array',
        'software_info' => 'array',
        'published_at' => 'datetime',
        'is_featured' => 'boolean',
        'is_downloadable' => 'boolean',
        'is_remixable' => 'boolean'
    ];
    
    protected $hidden = ['file_path'];
    
    // Relationships
    public function author() {
        return $this->belongsTo(User::class, 'author_id');
    }
    
    public function category() {
        return $this->belongsTo(ModelCategory::class);
    }
    
    public function tags() {
        return $this->belongsToMany(ModelTag::class, 'model_tag_assignments');
    }
    
    public function comments() {
        return $this->hasMany(ModelComment::class)
            ->whereNull('parent_comment_id')
            ->where('status', 'published');
    }
    
    public function allComments() {
        return $this->hasMany(ModelComment::class);
    }
    
    public function likes() {
        return $this->hasMany(ModelLike::class);
    }
    
    public function saves() {
        return $this->hasMany(ModelSave::class);
    }
    
    public function ratings() {
        return $this->hasMany(ModelRating::class);
    }
    
    public function downloads() {
        return $this->hasMany(ModelDownload::class);
    }
    
    public function stats() {
        return $this->hasOne(ModelStats::class);
    }
    
    public function collections() {
        return $this->belongsToMany(UserCollection::class, 'collection_models');
    }
    
    // Scopes
    public function scopePublished($query) {
        return $query->where('status', 'published')
                    ->where('visibility', 'public')
                    ->whereNotNull('published_at');
    }
    
    public function scopeFeatured($query) {
        return $query->where('is_featured', true);
    }
    
    public function scopeByCategory($query, $categoryId) {
        return $query->where('category_id', $categoryId);
    }
    
    public function scopeByAuthor($query, $authorId) {
        return $query->where('author_id', $authorId);
    }
    
    public function scopeWithTag($query, $tagName) {
        return $query->whereHas('tags', function ($q) use ($tagName) {
            $q->where('slug', $tagName);
        });
    }
    
    public function scopeDownloadable($query) {
        return $query->where('is_downloadable', true);
    }
    
    public function scopeMinRating($query, $rating) {
        return $query->whereHas('stats', function ($q) use ($rating) {
            $q->where('avg_rating', '>=', $rating);
        });
    }
    
    // Helper Methods
    public function isLikedBy(?User $user): bool {
        if (!$user) return false;
        return $this->likes()->where('user_id', $user->id)->exists();
    }
    
    public function isSavedBy(?User $user): bool {
        if (!$user) return false;
        return $this->saves()->where('user_id', $user->id)->exists();
    }
    
    public function getRatingBy(?User $user): ?int {
        if (!$user) return null;
        return $this->ratings()->where('user_id', $user->id)->value('rating');
    }
    
    public function hasBeenDownloadedBy(?User $user): bool {
        if (!$user) return false;
        return $this->downloads()->where('user_id', $user->id)->exists();
    }
    
    public function canBeDownloadedBy(?User $user): bool {
        if (!$this->is_downloadable) return false;
        if ($this->visibility === 'private' && $this->author_id !== $user?->id) return false;
        return true;
    }
    
    // URL Generation
    public function getDownloadUrlAttribute(): string {
        return route('api.models.download', $this->uuid);
    }
    
    public function getThumbnailUrlAttribute(): string {
        return app(FileUrlService::class)->getModelUrl($this, 'thumbnail');
    }
    
    public function getPreviewImagesAttribute(): array {
        return app(FileUrlService::class)->getPreviewImages($this);
    }
    
    // Statistics
    public function incrementStat(string $type): void {
        $this->stats()->increment($type . '_count');
        
        // Update time-based stats
        if (in_array($type, ['views', 'downloads'])) {
            $this->stats()->increment($type . '_today');
            $this->stats()->increment($type . '_week'); 
            $this->stats()->increment($type . '_month');
        }
    }
    
    public function updateRatingStats(): void {
        $avgRating = $this->ratings()->avg('rating');
        $ratingCount = $this->ratings()->count();
        
        $this->stats()->update([
            'avg_rating' => round($avgRating, 2),
            'rating_count' => $ratingCount
        ]);
    }
    
    // Search Configuration
    public function toSearchableArray(): array {
        return [
            'title' => $this->title,
            'description' => $this->description,
            'author_name' => $this->author->name,
            'category_name' => $this->category->name ?? '',
            'tags' => $this->tags->pluck('name')->join(' '),
            'file_format' => $this->file_format,
            'license' => $this->license,
            'published_at' => $this->published_at?->timestamp,
            'stats' => [
                'downloads' => $this->stats?->downloads_count ?? 0,
                'likes' => $this->stats?->likes_count ?? 0,
                'rating' => $this->stats?->avg_rating ?? 0,
            ]
        ];
    }
}

Advanced Controllers

// app/Http/Controllers/Api/ModelController.php
class ModelController extends Controller
{
    public function __construct(
        private ModelService $modelService,
        private FileUrlService $fileUrlService,
        private ModelCacheService $cacheService
    ) {}
    
    public function index(ModelListRequest $request)
    {
        $filters = $request->getFilters();
        $sort = $request->getSortOption();
        $perPage = $request->getPerPage();
        
        // Use cache for popular/trending requests
        if ($this->shouldUseCache($filters, $sort)) {
            $models = $this->cacheService->getModels($filters, $sort, $perPage);
        } else {
            $models = $this->modelService->getFilteredModels($filters, $sort, $perPage);
        }
        
        return ModelListResource::collection($models)
            ->additional([
                'filters' => $this->modelService->getAvailableFilters($request),
                'suggestions' => $this->modelService->getSuggestions($request->search)
            ]);
    }
    
    public function show(string $uuid, ModelDetailRequest $request)
    {
        $includes = $request->getIncludes();
        
        $model = Model::query()
            ->with($this->getBaseIncludes())
            ->when(in_array('comments', $includes), function ($q) {
                $q->with(['comments.author', 'comments.replies.author']);
            })
            ->when(in_array('related', $includes), function ($q) {
                // Load related models will be handled separately
            })
            ->where('uuid', $uuid)
            ->published()
            ->firstOrFail();
        
        // Check permissions
        $this->authorize('view', $model);
        
        // Track view (async)
        TrackModelView::dispatch($model, request()->ip(), auth()->user());
        
        // Get related models if requested
        $relatedModels = in_array('related', $includes) 
            ? $this->modelService->getRelatedModels($model)
            : collect();
        
        return new ModelDetailResource($model, $relatedModels);
    }
    
    public function store(CreateModelRequest $request)
    {
        $uploadSession = UploadSession::where('id', $request->uploadSessionId)
            ->where('user_id', auth()->id())
            ->where('status', 'completed')
            ->firstOrFail();
        
        DB::transaction(function () use ($request, $uploadSession) {
            // Create model
            $model = $this->modelService->createFromUpload($uploadSession, $request->validated());
            
            // Queue processing jobs
            ProcessUploadedModel::dispatch($model);
            
            // Update user stats
            auth()->user()->increment('models_count');
            
            // Notify followers if enabled
            if ($request->notifyFollowers) {
                NotifyFollowersOfNewModel::dispatch($model);
            }
        });
        
        return new ModelResource($model);
    }
    
    public function download(string $uuid, DownloadModelRequest $request)
    {
        $model = Model::where('uuid', $uuid)->published()->firstOrFail();
        
        $this->authorize('download', $model);
        
        $format = $request->get('format', 'original');
        
        // Record download
        ModelDownload::create([
            'model_id' => $model->id,
            'user_id' => auth()->id(),
            'ip_address' => $request->ip(),
            'user_agent' => $request->userAgent(),
            'download_type' => $format,
            'referrer' => $request->header('referer')
        ]);
        
        // Update stats
        $model->incrementStat('downloads');
        
        // Generate signed download URL
        $filePath = $this->fileUrlService->getDownloadPath($model, $format);
        $downloadUrl = Storage::disk('s3')->temporaryUrl($filePath, now()->addMinutes(30));
        
        return response()->json([
            'downloadUrl' => $downloadUrl,
            'filename' => $this->fileUrlService->getDownloadFilename($model, $format),
            'expiresAt' => now()->addMinutes(30)->toISOString()
        ]);
    }
    
    public function like(string $uuid)
    {
        $model = Model::where('uuid', $uuid)->published()->firstOrFail();
        
        $like = ModelLike::firstOrCreate([
            'model_id' => $model->id,
            'user_id' => auth()->id()
        ]);
        
        if ($like->wasRecentlyCreated) {
            $model->incrementStat('likes');
            
            // Notify author
            if ($model->author_id !== auth()->id()) {
                NotifyModelLiked::dispatch($model, auth()->user());
            }
        }
        
        return response()->json([
            'liked' => true,
            'likesCount' => $model->stats->likes_count
        ]);
    }
    
    public function unlike(string $uuid)
    {
        $model = Model::where('uuid', $uuid)->published()->firstOrFail();
        
        $deleted = ModelLike::where([
            'model_id' => $model->id,
            'user_id' => auth()->id()
        ])->delete();
        
        if ($deleted) {
            $model->stats()->decrement('likes_count');
        }
        
        return response()->json([
            'liked' => false,
            'likesCount' => $model->fresh()->stats->likes_count
        ]);
    }
    
    public function rate(string $uuid, RateModelRequest $request)
    {
        $model = Model::where('uuid', $uuid)->published()->firstOrFail();
        
        ModelRating::updateOrCreate(
            [
                'model_id' => $model->id,
                'user_id' => auth()->id()
            ],
            [
                'rating' => $request->rating,
                'review' => $request->review
            ]
        );
        
        // Update model rating stats
        $model->updateRatingStats();
        
        return response()->json(['success' => true]);
    }
    
    private function getBaseIncludes(): array
    {
        return [
            'author:id,name,username,avatar_url,is_verified',
            'category:id,name,slug,icon,color',
            'tags:id,name,slug',
            'stats'
        ];
    }
    
    private function shouldUseCache(array $filters, string $sort): bool
    {
        // Use cache for popular queries without user-specific filters
        return empty($filters['author']) && 
               empty($filters['search']) && 
               in_array($sort, ['popular', 'trending', 'featured']);
    }
}

🔄 File Processing Pipeline

Upload Processing Jobs

// app/Jobs/ProcessUploadedModel.php
class ProcessUploadedModel implements ShouldQueue
{
    use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
    
    public int $timeout = 600; // 10 minutes
    public int $tries = 3;
    
    public function __construct(private Model $model) {}
    
    public function handle(
        FileProcessor $fileProcessor,
        VirusScanner $virusScanner,
        ThumbnailGenerator $thumbnailGenerator,
        MetadataExtractor $metadataExtractor
    ): void {
        try {
            // Update status
            $this->model->update(['status' => 'processing']);
            
            // 1. Virus scan
            $this->createProcessingJob('virus_scan', 'processing');
            
            if (!$virusScanner->scan($this->model->file_path)) {
                $this->failModel('Security scan failed - potential threat detected');
                return;
            }
            
            $this->completeProcessingJob('virus_scan');
            
            // 2. File validation
            $this->createProcessingJob('validation', 'processing');
            
            $validation = $fileProcessor->validate($this->model->file_path, $this->model->file_format);
            if (!$validation['valid']) {
                $this->failModel('File validation failed: ' . $validation['error']);
                return;
            }
            
            $this->completeProcessingJob('validation', $validation);
            
            // 3. Extract metadata
            $this->createProcessingJob('metadata_extraction', 'processing');
            
            $metadata = $metadataExtractor->extract($this->model->file_path);
            $this->model->update([
                'polygon_count' => $metadata['polygonCount'] ?? null,
                'vertex_count' => $metadata['vertexCount'] ?? null,
                'bounding_box' => $metadata['boundingBox'] ?? null,
                'file_size' => Storage::size($this->model->file_path)
            ]);
            
            $this->completeProcessingJob('metadata_extraction', $metadata);
            
            // 4. Generate thumbnails
            $this->createProcessingJob('thumbnail', 'processing');
            
            $thumbnailPaths = $thumbnailGenerator->generate($this->model);
            $this->model->update(['thumbnail_path' => $thumbnailPaths['primary']]);
            
            $this->completeProcessingJob('thumbnail', ['paths' => $thumbnailPaths]);
            
            // 5. Generate preview images
            $this->createProcessingJob('preview', 'processing');
            
            $previewPaths = $thumbnailGenerator->generatePreviews($this->model);
            $this->model->update(['preview_images' => $previewPaths]);
            
            $this->completeProcessingJob('preview', ['paths' => $previewPaths]);
            
            // 6. Generate alternative formats (STL, OBJ if not already)
            if (!in_array($this->model->file_format, ['STL', 'OBJ'])) {
                GenerateAlternativeFormats::dispatch($this->model);
            }
            
            // 7. Create stats record
            ModelStats::firstOrCreate(['model_id' => $this->model->id]);
            
            // 8. Update status to published
            $this->model->update([
                'status' => 'published',
                'published_at' => now()
            ]);
            
            // 9. Index in search
            $this->model->searchable();
            
            // 10. Clear related caches
            $this->clearCaches();
            
            // 11. Notify author
            ModelProcessingCompleted::dispatch($this->model);
            
        } catch (Exception $e) {
            $this->failModel('Processing failed: ' . $e->getMessage());
            throw $e;
        }
    }
    
    private function createProcessingJob(string $type, string $status): void
    {
        FileProcessingJob::create([
            'model_id' => $this->model->id,
            'job_type' => $type,
            'status' => $status,
            'started_at' => now()
        ]);
    }
    
    private function completeProcessingJob(string $type, array $result = null): void
    {
        FileProcessingJob::where('model_id', $this->model->id)
            ->where('job_type', $type)
            ->update([
                'status' => 'completed',
                'progress' => 100,
                'result' => $result,
                'completed_at' => now()
            ]);
    }
    
    private function failModel(string $reason): void
    {
        $this->model->update([
            'status' => 'rejected',
            'moderation_notes' => $reason
        ]);
        
        // Update all pending jobs to failed
        FileProcessingJob::where('model_id', $this->model->id)
            ->where('status', '!=', 'completed')
            ->update([
                'status' => 'failed',
                'error_message' => $reason,
                'completed_at' => now()
            ]);
        
        ModelProcessingFailed::dispatch($this->model, $reason);
    }
    
    private function clearCaches(): void
    {
        $tags = ['models', 'trending', 'popular', 'featured'];
        
        if ($this->model->category_id) {
            $tags[] = "category_{$this->model->category_id}";
        }
        
        Cache::tags($tags)->flush();
    }
}

// app/Jobs/GenerateAlternativeFormats.php
class GenerateAlternativeFormats implements ShouldQueue
{
    use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
    
    public function handle(Model $model, FileConverter $converter): void
    {
        $basePath = pathinfo($model->file_path, PATHINFO_DIRNAME);
        $baseFilename = pathinfo($model->file_path, PATHINFO_FILENAME);
        
        $formats = ['stl', 'obj'];
        
        foreach ($formats as $format) {
            if (strtolower($model->file_format) === $format) {
                continue; // Skip if already in this format
            }
            
            try {
                $outputPath = $basePath . '/' . $baseFilename . '.' . $format;
                
                $success = $converter->convert(
                    $model->file_path,
                    $outputPath,
                    $format
                );
                
                if ($success) {
                    // Store format info in model metadata
                    $alternativeFormats = $model->alternative_formats ?? [];
                    $alternativeFormats[$format] = [
                        'path' => $outputPath,
                        'size' => Storage::size($outputPath),
                        'generated_at' => now()->toISOString()
                    ];
                    
                    $model->update(['alternative_formats' => $alternativeFormats]);
                }
                
            } catch (Exception $e) {
                Log::error('Failed to generate alternative format', [
                    'model_id' => $model->id,
                    'format' => $format,
                    'error' => $e->getMessage()
                ]);
            }
        }
    }
}

🔍 Search & Performance Optimization

Full-Text Search with Scout

// app/Services/ModelSearchService.php
class ModelSearchService
{
    public function __construct(
        private ModelCacheService $cacheService
    ) {}
    
    public function search(string $query, array $filters = [], string $sort = 'relevance'): LengthAwarePaginator
    {
        // Use cache for popular searches
        $cacheKey = $this->generateCacheKey($query, $filters, $sort);
        
        if ($this->shouldUseCache($query, $filters)) {
            return $this->cacheService->remember($cacheKey, 900, function () use ($query, $filters, $sort) {
                return $this->performSearch($query, $filters, $sort);
            });
        }
        
        return $this->performSearch($query, $filters, $sort);
    }
    
    private function performSearch(string $query, array $filters, string $sort): LengthAwarePaginator
    {
        $searchQuery = Model::search($query)
            ->where('status', 'published')
            ->where('visibility', 'public');
        
        // Apply filters
        $this->applyFilters($searchQuery, $filters);
        
        // Apply sorting
        $this->applySorting($searchQuery, $sort);
        
        return $searchQuery->paginate(24);
    }
    
    private function applyFilters($searchQuery, array $filters): void
    {
        if (!empty($filters['category'])) {
            $searchQuery->where('category_id', $filters['category']);
        }
        
        if (!empty($filters['tags'])) {
            $searchQuery->whereIn('tags', $filters['tags']);
        }
        
        if (!empty($filters['format'])) {
            $searchQuery->where('file_format', strtoupper($filters['format']));
        }
        
        if (!empty($filters['license'])) {
            $searchQuery->where('license', $filters['license']);
        }
        
        if (!empty($filters['author'])) {
            $searchQuery->where('author_id', $filters['author']);
        }
        
        if (!empty($filters['min_rating'])) {
            $searchQuery->where('stats.avg_rating', '>=', $filters['min_rating']);
        }
        
        if (!empty($filters['created_after'])) {
            $searchQuery->where('published_at', '>=', $filters['created_after']);
        }
        
        if (!empty($filters['created_before'])) {
            $searchQuery->where('published_at', '<=', $filters['created_before']);
        }
        
        if (!empty($filters['downloadable'])) {
            $searchQuery->where('is_downloadable', true);
        }
    }
    
    private function applySorting($searchQuery, string $sort): void
    {
        match($sort) {
            'popular' => $searchQuery->orderBy('stats.downloads_count', 'desc'),
            'trending' => $searchQuery->orderBy('stats.views_week', 'desc'),
            'newest' => $searchQuery->orderBy('published_at', 'desc'),
            'oldest' => $searchQuery->orderBy('published_at', 'asc'),
            'rating' => $searchQuery->orderBy('stats.avg_rating', 'desc'),
            'downloads' => $searchQuery->orderBy('stats.downloads_count', 'desc'),
            'likes' => $searchQuery->orderBy('stats.likes_count', 'desc'),
            default => null // Use relevance scoring
        };
    }
    
    public function getSuggestions(string $query, int $limit = 10): array
    {
        if (strlen($query) < 2) {
            return [];
        }
        
        return Cache::remember("search_suggestions_{$query}_{$limit}", 3600, function () use ($query, $limit) {
            // Get model title suggestions
            $modelSuggestions = Model::search($query)
                ->where('status', 'published')
                ->take($limit)
                ->get(['title', 'slug'])
                ->map(fn($model) => [
                    'type' => 'model',
                    'text' => $model->title,
                    'url' => "/models/{$model->slug}"
                ]);
            
            // Get tag suggestions
            $tagSuggestions = ModelTag::where('name', 'like', "%{$query}%")
                ->orderBy('usage_count', 'desc')
                ->take(5)
                ->get(['name', 'slug'])
                ->map(fn($tag) => [
                    'type' => 'tag',
                    'text' => $tag->name,
                    'url' => "/models?tags={$tag->slug}"
                ]);
            
            // Get author suggestions
            $authorSuggestions = User::where('name', 'like', "%{$query}%")
                ->where('models_count', '>', 0)
                ->orderBy('models_count', 'desc')
                ->take(3)
                ->get(['name', 'username'])
                ->map(fn($user) => [
                    'type' => 'author',
                    'text' => $user->name,
                    'url' => "/users/{$user->username}"
                ]);
            
            return $modelSuggestions
                ->concat($tagSuggestions)
                ->concat($authorSuggestions)
                ->take($limit)
                ->values()
                ->toArray();
        });
    }
    
    private function shouldUseCache(string $query, array $filters): bool
    {
        // Cache simple queries without user-specific filters
        return strlen($query) <= 20 && 
               empty($filters['author']) && 
               count($filters) <= 3;
    }
    
    private function generateCacheKey(string $query, array $filters, string $sort): string
    {
        return 'search_' . md5($query . serialize($filters) . $sort);
    }
}

// app/Services/ModelCacheService.php
class ModelCacheService
{
    private const CACHE_TTL = [
        'popular' => 3600,      // 1 hour
        'trending' => 1800,     // 30 minutes
        'featured' => 7200,     // 2 hours
        'categories' => 86400,  // 24 hours
        'tags' => 3600,         // 1 hour
    ];
    
    public function getPopularModels(int $page = 1, int $limit = 24): LengthAwarePaginator
    {
        return $this->remember("popular_models_p{$page}_l{$limit}", self::CACHE_TTL['popular'], function () use ($limit) {
            return Model::query()
                ->with(['author', 'category', 'stats'])
                ->published()
                ->join('model_stats', 'models.id', '=', 'model_stats.model_id')
                ->orderByRaw('(downloads_count * 2 + likes_count * 3 + views_count * 0.1) DESC')
                ->paginate($limit);
        });
    }
    
    public function getTrendingModels(string $period = 'week', int $limit = 12): Collection
    {
        return $this->remember("trending_models_{$period}_{$limit}", self::CACHE_TTL['trending'], function () use ($period, $limit) {
            $column = match($period) {
                'today' => 'views_today',
                'week' => 'views_week',
                'month' => 'views_month',
                default => 'views_week'
            };
            
            return Model::query()
                ->with(['author', 'category', 'stats'])
                ->published()
                ->join('model_stats', 'models.id', '=', 'model_stats.model_id')
                ->where($column, '>', 0)
                ->orderByRaw("({$column} * 2 + likes_count) DESC")
                ->limit($limit)
                ->get();
        });
    }
    
    public function getFeaturedModels(int $limit = 8): Collection
    {
        return $this->remember("featured_models_{$limit}", self::CACHE_TTL['featured'], function () use ($limit) {
            return Model::query()
                ->with(['author', 'category', 'stats'])
                ->featured()
                ->published()
                ->orderBy('published_at', 'desc')
                ->limit($limit)
                ->get();
        });
    }
    
    public function getCategories(): Collection
    {
        return $this->remember('model_categories', self::CACHE_TTL['categories'], function () {
            return ModelCategory::query()
                ->where('is_active', true)
                ->with('parent')
                ->orderBy('sort_order')
                ->orderBy('name')
                ->get();
        });
    }
    
    public function getPopularTags(int $limit = 20): Collection
    {
        return $this->remember("popular_tags_{$limit}", self::CACHE_TTL['tags'], function () use ($limit) {
            return ModelTag::query()
                ->where('usage_count', '>', 0)
                ->orderBy('usage_count', 'desc')
                ->limit($limit)
                ->get(['name', 'slug', 'usage_count']);
        });
    }
    
    public function clearModelCache(Model $model): void
    {
        $tags = [
            'models',
            'popular_models',
            'trending_models',
            'featured_models',
            "category_{$model->category_id}",
            "author_{$model->author_id}"
        ];
        
        Cache::tags($tags)->flush();
        
        // Clear specific model cache
        Cache::forget("model_detail_{$model->id}");
    }
    
    public function remember(string $key, int $ttl, Closure $callback)
    {
        return Cache::remember($key, $ttl, $callback);
    }
}

🔒 Security & Authorization

Authorization Policies

// app/Policies/ModelPolicy.php
class ModelPolicy
{
    public function viewAny(?User $user): bool
    {
        return true; // Anyone can browse public models
    }
    
    public function view(?User $user, Model $model): bool
    {
        // Public models can be viewed by anyone
        if ($model->visibility === 'public' && $model->status === 'published') {
            return true;
        }
        
        // Private models only by author or admins
        if ($model->visibility === 'private') {
            return $user && ($user->id === $model->author_id || $user->hasRole(['admin', 'moderator']));
        }
        
        // Draft models only by author or admins
        if ($model->status === 'draft') {
            return $user && ($user->id === $model->author_id || $user->hasRole(['admin', 'moderator']));
        }
        
        // Unlisted models can be viewed by anyone with the link
        if ($model->visibility === 'unlisted') {
            return $model->status === 'published';
        }
        
        return false;
    }
    
    public function create(User $user): bool
    {
        // Check if user has reached upload limits
        if ($user->hasRole('free') && $user->models()->count() >= 10) {
            return false;
        }
        
        if ($user->hasRole('pro') && $user->models()->count() >= 100) {
            return false;
        }
        
        // Check if user is banned
        return !$user->is_banned;
    }
    
    public function update(User $user, Model $model): bool
    {
        return $user->id === $model->author_id || 
               $user->hasRole(['admin', 'moderator']);
    }
    
    public function delete(User $user, Model $model): bool
    {
        return $user->id === $model->author_id || 
               $user->hasRole(['admin']);
    }
    
    public function download(User $user, Model $model): bool
    {
        // Must be able to view the model first
        if (!$this->view($user, $model)) {
            return false;
        }
        
        // Model must be downloadable
        if (!$model->is_downloadable) {
            return false;
        }
        
        // Check download limits for free users
        if ($user->hasRole('free')) {
            $todayDownloads = ModelDownload::where('user_id', $user->id)
                ->whereDate('created_at', today())
                ->count();
                
            if ($todayDownloads >= 5) {
                return false;
            }
        }
        
        return true;
    }
    
    public function comment(User $user, Model $model): bool
    {
        return $this->view($user, $model) && 
               !$user->is_banned &&
               !$user->is_muted;
    }
    
    public function like(User $user, Model $model): bool
    {
        return $this->view($user, $model) && 
               !$user->is_banned;
    }
    
    public function rate(User $user, Model $model): bool
    {
        return $this->view($user, $model) && 
               !$user->is_banned &&
               $user->id !== $model->author_id; // Can't rate own models
    }
    
    public function report(User $user, Model $model): bool
    {
        return $this->view($user, $model) && 
               !$user->is_banned &&
               $user->id !== $model->author_id; // Can't report own models
    }
}

File Upload Security

// app/Services/FileSecurityService.php
class FileSecurityService
{
    private const ALLOWED_MIME_TYPES = [
        'stl' => ['application/octet-stream', 'model/stl'],
        'obj' => ['application/obj', 'text/plain'],
        'ply' => ['application/ply', 'text/plain'],
        'sldprt' => ['application/sldprt', 'application/octet-stream'],
        'step' => ['application/step', 'model/step'],
        'iges' => ['application/iges', 'model/iges'],
        '3mf' => ['application/3mf', 'model/3mf']
    ];
    
    private const MAX_FILE_SIZE = 100 * 1024 * 1024; // 100MB
    private const VIRUS_SCAN_TIMEOUT = 30; // seconds
    
    public function validateUpload(UploadedFile $file): array
    {
        $errors = [];
        
        // Check file size
        if ($file->getSize() > self::MAX_FILE_SIZE) {
            $errors[] = 'File size exceeds maximum allowed size of 100MB';
        }
        
        // Check file extension
        $extension = strtolower($file->getClientOriginalExtension());
        if (!array_key_exists($extension, self::ALLOWED_MIME_TYPES)) {
            $errors[] = 'File type not supported';
        }
        
        // Check MIME type
        $mimeType = $file->getMimeType();
        if (isset(self::ALLOWED_MIME_TYPES[$extension]) && 
            !in_array($mimeType, self::ALLOWED_MIME_TYPES[$extension])) {
            $errors[] = 'File MIME type does not match extension';
        }
        
        // Check for malicious content
        if ($this->containsMaliciousContent($file->getPathname())) {
            $errors[] = 'File contains potentially malicious content';
        }
        
        return [
            'valid' => empty($errors),
            'errors' => $errors
        ];
    }
    
    public function scanForVirus(string $filePath): bool
    {
        try {
            $scanner = new ClamAVScanner();
            $result = $scanner->scan($filePath, self::VIRUS_SCAN_TIMEOUT);
            
            Log::info('Virus scan completed', [
                'file' => basename($filePath),
                'result' => $result ? 'clean' : 'infected'
            ]);
            
            return $result;
            
        } catch (Exception $e) {
            Log::error('Virus scan failed', [
                'file' => basename($filePath),
                'error' => $e->getMessage()
            ]);
            
            // Fail safe - reject if scan fails
            return false;
        }
    }
    
    public function sanitizeFilename(string $filename): string
    {
        // Remove dangerous characters
        $filename = preg_replace('/[^\w\-_\.]/', '_', $filename);
        
        // Prevent double extensions
        $filename = preg_replace('/\.{2,}/', '.', $filename);
        
        // Ensure reasonable length
        if (strlen($filename) > 255) {
            $extension = pathinfo($filename, PATHINFO_EXTENSION);
            $basename = substr(pathinfo($filename, PATHINFO_FILENAME), 0, 250);
            $filename = $basename . '.' . $extension;
        }
        
        return $filename;
    }
    
    private function containsMaliciousContent(string $filePath): bool
    {
        // Read first 2KB to check for malicious patterns
        $content = file_get_contents($filePath, false, null, 0, 2048);
        
        $maliciousPatterns = [
            // Script injections
            '/<\?php/i',
            '/<script/i',
            '/javascript:/i',
            '/data:text\/html/i',
            '/eval\s*\(/i',
            '/exec\s*\(/i',
            '/system\s*\(/i',
            '/shell_exec\s*\(/i',
            
            // File inclusions
            '/include\s*\(/i',
            '/require\s*\(/i',
            '/file_get_contents\s*\(/i',
            
            // SQL injections (in text files)
            '/union\s+select/i',
            '/drop\s+table/i',
            '/insert\s+into/i',
            
            // Command injections
            '/\|.*?(cat|ls|pwd|whoami)/i',
            '/&&.*?(rm|cp|mv)/i',
        ];
        
        foreach ($maliciousPatterns as $pattern) {
            if (preg_match($pattern, $content)) {
                Log::warning('Malicious pattern detected in upload', [
                    'file' => basename($filePath),
                    'pattern' => $pattern
                ]);
                return true;
            }
        }
        
        return false;
    }
}

// app/Http/Middleware/RateLimitUploads.php
class RateLimitUploads
{
    public function handle(Request $request, Closure $next): Response
    {
        $user = $request->user();
        
        if (!$user) {
            return response()->json(['error' => 'Authentication required'], 401);
        }
        
        // Rate limit based on user role
        $limits = match($user->role) {
            'free' => ['count' => 3, 'window' => 3600],      // 3 per hour
            'pro' => ['count' => 10, 'window' => 3600],      // 10 per hour
            'premium' => ['count' => 25, 'window' => 3600],  // 25 per hour
            default => ['count' => 1, 'window' => 3600]      // 1 per hour
        };
        
        $key = 'upload_rate_limit:' . $user->id;
        $attempts = Cache::get($key, 0);
        
        if ($attempts >= $limits['count']) {
            return response()->json([
                'error' => 'Upload rate limit exceeded',
                'retry_after' => $limits['window']
            ], 429);
        }
        
        // Increment counter
        Cache::put($key, $attempts + 1, $limits['window']);
        
        return $next($request);
    }
}

📊 Testing Strategy

Feature Tests

// tests/Feature/ModelUploadTest.php
class ModelUploadTest extends TestCase
{
    use RefreshDatabase;
    
    protected function setUp(): void
    {
        parent::setUp();
        
        Storage::fake('private');
        Storage::fake('s3');
        Queue::fake();
    }
    
    public function test_user_can_initialize_upload_session(): void
    {
        $user = User::factory()->create();
        
        $response = $this->actingAs($user)
            ->postJson('/api/v1/uploads/init', [
                'filename' => 'test_model.stl',
                'fileSize' => 1048576,
                'mimeType' => 'application/octet-stream',
                'chunkSize' => 1048576,
                'fileHash' => 'sha256:test-hash'
            ]);
            
        $response->assertStatus(201)
            ->assertJsonStructure([
                'data' => [
                    'sessionId',
                    'chunksTotal',
                    'chunkSize',
                    'uploadUrls',
                    'expiresAt'
                ]
            ]);
            
        $this->assertDatabaseHas('upload_sessions', [
            'user_id' => $user->id,
            'filename' => 'test_model.stl',
            'file_size' => 1048576
        ]);
    }
    
    public function test_user_can_complete_upload_and_create_model(): void
    {
        $user = User::factory()->create();
        $category = ModelCategory::factory()->create();
        
        // Create completed upload session
        $session = UploadSession::factory()->create([
            'user_id' => $user->id,
            'status' => 'completed',
            'file_path' => 'uploads/models/test_file.stl'
        ]);
        
        $response = $this->actingAs($user)
            ->postJson('/api/v1/models', [
                'uploadSessionId' => $session->id,
                'title' => 'Test Model',
                'description' => 'A test model description',
                'categoryId' => $category->id,
                'tags' => ['test', 'sample'],
                'license' => 'CC-BY-4.0',
                'visibility' => 'public',
                'isDownloadable' => true,
                'publishImmediately' => true
            ]);
            
        $response->assertStatus(201)
            ->assertJsonStructure([
                'data' => [
                    'id',
                    'title',
                    'status',
                    'processingJobs'
                ]
            ]);
            
        $this->assertDatabaseHas('models', [
            'title' => 'Test Model',
            'author_id' => $user->id,
            'category_id' => $category->id,
            'status' => 'processing'
        ]);
        
        // Verify processing job was queued
        Queue::assertPushed(ProcessUploadedModel::class);
    }
    
    public function test_user_cannot_upload_oversized_file(): void
    {
        $user = User::factory()->create();
        
        $response = $this->actingAs($user)
            ->postJson('/api/v1/uploads/init', [
                'filename' => 'huge_model.stl',
                'fileSize' => 200 * 1024 * 1024, // 200MB
                'mimeType' => 'application/octet-stream',
                'chunkSize' => 1048576
            ]);
            
        $response->assertStatus(422)
            ->assertJsonValidationErrors(['fileSize']);
    }
    
    public function test_user_cannot_upload_invalid_file_type(): void
    {
        $user = User::factory()->create();
        
        $response = $this->actingAs($user)
            ->postJson('/api/v1/uploads/init', [
                'filename' => 'malicious.exe',
                'fileSize' => 1048576,
                'mimeType' => 'application/x-executable',
                'chunkSize' => 1048576
            ]);
            
        $response->assertStatus(422)
            ->assertJsonValidationErrors(['mimeType']);
    }
    
    public function test_upload_session_expires(): void
    {
        $user = User::factory()->create();
        
        $session = UploadSession::factory()->create([
            'user_id' => $user->id,
            'expires_at' => now()->subHour(),
            'status' => 'pending'
        ]);
        
        $response = $this->actingAs($user)
            ->postJson("/api/v1/uploads/{$session->id}/complete");
            
        $response->assertStatus(410); // Gone
    }
}

// tests/Feature/ModelInteractionTest.php
class ModelInteractionTest extends TestCase
{
    use RefreshDatabase;
    
    public function test_user_can_like_model(): void
    {
        $user = User::factory()->create();
        $model = Model::factory()->published()->create();
        
        $response = $this->actingAs($user)
            ->postJson("/api/v1/models/{$model->uuid}/like");
            
        $response->assertStatus(200)
            ->assertJson([
                'liked' => true,
                'likesCount' => 1
            ]);
            
        $this->assertDatabaseHas('model_likes', [
            'model_id' => $model->id,
            'user_id' => $user->id
        ]);
    }
    
    public function test_user_can_download_model(): void
    {
        $user = User::factory()->create();
        $model = Model::factory()->published()->downloadable()->create();
        
        Storage::disk('s3')->put($model->file_path, 'fake file content');
        
        $response = $this->actingAs($user)
            ->postJson("/api/v1/models/{$model->uuid}/download");
            
        $response->assertStatus(200)
            ->assertJsonStructure([
                'downloadUrl',
                'filename',
                'expiresAt'
            ]);
            
        $this->assertDatabaseHas('model_downloads', [
            'model_id' => $model->id,
            'user_id' => $user->id
        ]);
    }
    
    public function test_user_can_rate_model(): void
    {
        $user = User::factory()->create();
        $model = Model::factory()->published()->create();
        
        $response = $this->actingAs($user)
            ->postJson("/api/v1/models/{$model->uuid}/rate", [
                'rating' => 5,
                'review' => 'Excellent model!'
            ]);
            
        $response->assertStatus(200);
        
        $this->assertDatabaseHas('model_ratings', [
            'model_id' => $model->id,
            'user_id' => $user->id,
            'rating' => 5
        ]);
    }
    
    public function test_user_cannot_rate_own_model(): void
    {
        $user = User::factory()->create();
        $model = Model::factory()->published()->create(['author_id' => $user->id]);
        
        $response = $this->actingAs($user)
            ->postJson("/api/v1/models/{$model->uuid}/rate", [
                'rating' => 5
            ]);
            
        $response->assertStatus(403);
    }
}

// tests/Feature/ModelSearchTest.php
class ModelSearchTest extends TestCase
{
    use RefreshDatabase;
    
    public function test_can_search_models_by_title(): void
    {
        Model::factory()->published()->create(['title' => 'Mechanical Gear']);
        Model::factory()->published()->create(['title' => 'Electronic Circuit']);
        
        $response = $this->getJson('/api/v1/models?search=mechanical');
        
        $response->assertStatus(200)
            ->assertJsonCount(1, 'data')
            ->assertJsonPath('data.0.title', 'Mechanical Gear');
    }
    
    public function test_can_filter_models_by_category(): void
    {
        $category = ModelCategory::factory()->create(['slug' => 'mechanical']);
        
        Model::factory()->published()->create(['category_id' => $category->id]);
        Model::factory()->published()->create(['category_id' => null]);
        
        $response = $this->getJson('/api/v1/models?category=mechanical');
        
        $response->assertStatus(200)
            ->assertJsonCount(1, 'data');
    }
    
    public function test_can_sort_models_by_popularity(): void
    {
        $popular = Model::factory()->published()->create();
        $unpopular = Model::factory()->published()->create();
        
        // Create stats to make first model more popular
        ModelStats::factory()->create([
            'model_id' => $popular->id,
            'downloads_count' => 100,
            'likes_count' => 50
        ]);
        
        ModelStats::factory()->create([
            'model_id' => $unpopular->id,
            'downloads_count' => 1,
            'likes_count' => 0
        ]);
        
        $response = $this->getJson('/api/v1/models?sort=popular');
        
        $response->assertStatus(200)
            ->assertJsonPath('data.0.id', $popular->uuid);
    }
}

🚀 Deployment & Scaling

ðŸŽŊ Production Infrastructure

Environment Configuration

# .env.production
APP_NAME="SolidProfessor Platform Hub"
APP_ENV=production
APP_DEBUG=false
APP_URL=https://hub.solidprofessor.com

# Database Configuration
DB_CONNECTION=mysql
DB_HOST=prod-db-cluster.us-east-1.rds.amazonaws.com
DB_PORT=3306
DB_DATABASE=solidprofessor_hub
DB_USERNAME=hub_user
DB_PASSWORD=${DB_PASSWORD}

# Read Replica for Analytics
DB_READ_HOST=prod-db-replica.us-east-1.rds.amazonaws.com

# Redis Configuration
REDIS_HOST=prod-redis-cluster.abc123.cache.amazonaws.com
REDIS_PASSWORD=${REDIS_PASSWORD}
REDIS_PORT=6379

# File Storage
FILESYSTEM_DISK=s3
AWS_ACCESS_KEY_ID=${AWS_ACCESS_KEY_ID}
AWS_SECRET_ACCESS_KEY=${AWS_SECRET_ACCESS_KEY}
AWS_DEFAULT_REGION=us-east-1
AWS_BUCKET=solidprofessor-hub-models
AWS_URL=https://solidprofessor-hub-models.s3.amazonaws.com
CDN_URL=https://cdn.solidprofessor.com

# Search
SCOUT_DRIVER=elasticsearch
ELASTICSEARCH_HOST=https://search-solidprofessor-abc123.us-east-1.es.amazonaws.com
ELASTICSEARCH_INDEX=models

# Queue Configuration
QUEUE_CONNECTION=redis
QUEUE_PREFIX=hub_prod_
QUEUE_FAILED_DRIVER=database

# Cache Configuration
CACHE_DRIVER=redis
SESSION_DRIVER=redis
SESSION_LIFETIME=120

# File Processing
MAX_UPLOAD_SIZE=104857600  # 100MB
CHUNK_SIZE_LIMIT=10485760  # 10MB
UPLOAD_SESSION_TTL=86400   # 24 hours
VIRUS_SCAN_ENABLED=true

# Rate Limiting
RATE_LIMIT_UPLOADS_FREE=3
RATE_LIMIT_UPLOADS_PRO=10
RATE_LIMIT_DOWNLOADS_FREE=10
RATE_LIMIT_DOWNLOADS_PRO=50

# Email
MAIL_MAILER=ses
MAIL_FROM_ADDRESS=noreply@solidprofessor.com
MAIL_FROM_NAME="SolidProfessor Hub"

# Monitoring
SENTRY_LARAVEL_DSN=${SENTRY_DSN}
LOG_CHANNEL=stack
LOG_LEVEL=info

# Feature Flags
ENABLE_MODEL_REMIXING=true
ENABLE_SOCIAL_FEATURES=true
ENABLE_COLLECTIONS=true
ENABLE_RATINGS=true

Deployment Pipeline

# .github/workflows/deploy.yml
name: Deploy to Production

on:
  push:
    branches: [main]

jobs:
  test:
    runs-on: ubuntu-latest
    services:
      mysql:
        image: mysql:8.0
        env:
          MYSQL_ROOT_PASSWORD: password
          MYSQL_DATABASE: testing
        options: --health-cmd="mysqladmin ping" --health-interval=10s
      redis:
        image: redis:7
        options: --health-cmd "redis-cli ping" --health-interval=10s
        
    steps:
      - uses: actions/checkout@v3
      
      - name: Setup PHP
        uses: shivammathur/setup-php@v2
        with:
          php-version: 8.2
          extensions: bcmath, ctype, fileinfo, json, mbstring, openssl, pdo, tokenizer, xml
          
      - name: Install dependencies
        run: composer install --prefer-dist --no-progress
        
      - name: Copy environment file
        run: cp .env.testing .env
        
      - name: Generate application key
        run: php artisan key:generate
        
      - name: Run migrations
        run: php artisan migrate --force
        
      - name: Run tests
        run: php artisan test --parallel
        
      - name: Run static analysis
        run: ./vendor/bin/phpstan analyse
        
  deploy:
    needs: test
    runs-on: ubuntu-latest
    if: github.ref == 'refs/heads/main'
    
    steps:
      - uses: actions/checkout@v3
      
      - name: Deploy to production
        uses: appleboy/ssh-action@v0.1.5
        with:
          host: ${{ secrets.PROD_HOST }}
          username: ${{ secrets.PROD_USER }}
          key: ${{ secrets.PROD_SSH_KEY }}
          script: |
            cd /var/www/platform-hub
            git pull origin main
            composer install --optimize-autoloader --no-dev
            php artisan migrate --force
            php artisan config:cache
            php artisan route:cache
            php artisan view:cache
            php artisan queue:restart
            sudo supervisorctl restart php-fpm
            sudo nginx -s reload

Performance Monitoring

📊 Key Metrics

  • API Response Time: < 200ms average
  • File Upload Success Rate: > 99%
  • Search Response Time: < 100ms
  • Database Query Time: < 50ms average
  • CDN Cache Hit Rate: > 95%
  • Queue Processing Time: < 5 minutes
  • Error Rate: < 0.1%
  • Uptime: > 99.9%

⚡ Scaling Strategy

  • Horizontal Scaling: Auto-scaling groups for web servers
  • Database Scaling: Read replicas for analytics queries
  • Queue Scaling: Auto-scaling queue workers based on load
  • CDN Scaling: Global edge locations for file delivery
  • Cache Scaling: Redis cluster with automatic failover
  • Search Scaling: Multi-node Elasticsearch cluster

Backup & Disaster Recovery

# Backup Strategy
- Database: Automated daily backups with 30-day retention
- Files: S3 versioning with cross-region replication
- Configuration: Git-based infrastructure as code
- Recovery Time Objective (RTO): < 4 hours
- Recovery Point Objective (RPO): < 1 hour

# Monitoring Setup
- Application: New Relic APM
- Infrastructure: AWS CloudWatch + DataDog
- Logs: ELK Stack (Elasticsearch, Logstash, Kibana)
- Alerts: PagerDuty integration for critical issues
- Health Checks: Automated endpoint monitoring

🎉 This comprehensive backend implementation provides a production-ready, scalable platform for 3D model sharing with advanced features, robust security, and enterprise-grade performance optimization.