Browse Source

chore: remove failed model auto-import feature due to site protections

unknown 2 months ago
parent
commit
f1619c1bbb

+ 1 - 2
backend/main.py

@@ -21,7 +21,7 @@ from services.chat_manager import manager
 import locales
 import config
 import db
-from routers import auth, orders, catalog, portfolio, files, chat, blog, admin, contact, warehouse, importer
+from routers import auth, orders, catalog, portfolio, files, chat, blog, admin, contact, warehouse
 
 app = FastAPI(title="Radionica 3D API")
 
@@ -85,7 +85,6 @@ app.include_router(blog.router)
 app.include_router(admin.router)
 app.include_router(contact.router)
 app.include_router(warehouse.router)
-app.include_router(importer.router)
 
 # WebSocket Global Handler (Centralized to handle various proxy prefixes)
 @app.websocket("/global")

+ 0 - 30
backend/routers/importer.py

@@ -1,30 +0,0 @@
-from fastapi import APIRouter, HTTPException, Depends, BackgroundTasks
-from pydantic import BaseModel
-from services.link_importer import LinkImporter
-from dependencies import get_current_user
-import os
-
-router = APIRouter(prefix="/importer", tags=["importer"])
-
-class ImportRequest(BaseModel):
-    url: str
-
-@router.post("/url")
-async def import_from_url(request: ImportRequest, background_tasks: BackgroundTasks, user: dict = Depends(get_current_user)):
-    # Note: we allow guests to upload, but here we require login for safety 
-    # to prevent anonymous server-side request forgery (SSRF).
-    if not user:
-        raise HTTPException(status_code=401, detail="Please login to use link import feature")
-
-    try:
-        upload_dir = "uploads" # Shared with other routers
-        if not os.path.exists(upload_dir):
-            os.makedirs(upload_dir, exist_ok=True)
-            
-        files = LinkImporter.download_and_extract(request.url, upload_dir, background_tasks)
-        return {"status": "success", "files": files}
-    except ValueError as ve:
-        raise HTTPException(status_code=400, detail=str(ve))
-    except Exception as e:
-        print(f"IMPORT ERROR: {e}")
-        raise HTTPException(status_code=500, detail=f"Failed to import files: {str(e)}")

+ 0 - 110
backend/services/link_importer.py

@@ -1,110 +0,0 @@
-import re
-import requests
-import os
-import zipfile
-import io
-import uuid
-from typing import List, Dict
-
-# Standard headers to avoid bot detection
-HEADERS = {
-    "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36",
-    "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8",
-}
-
-class LinkImporter:
-    @staticmethod
-    def extract_id(url: str):
-        # Thingiverse: thingiverse.com/thing:12345 or thingiverse.com/thing:12345/files
-        thing_match = re.search(r"thingiverse\.com/thing:(\d+)", url)
-        if thing_match:
-            return "thingiverse", thing_match.group(1)
-        
-        # Printables: printables.com/model/12345-name or printables.com/en/model/12345
-        printables_match = re.search(r"printables\.com/(?:[a-z]{2}/)?model/(\d+)", url)
-        if printables_match:
-            return "printables", printables_match.group(1)
-            
-        return None, None
-
-    @staticmethod
-    def download_and_extract(url: str, upload_dir: str, background_tasks=None) -> List[Dict]:
-        service, item_id = LinkImporter.extract_id(url)
-        if not service:
-            raise ValueError("Unsupported URL format. Please use Thingiverse or Printables link.")
-
-        download_url = ""
-        if service == "thingiverse":
-            download_url = f"https://www.thingiverse.com/thing:{item_id}/zip"
-        elif service == "printables":
-            download_url = f"https://www.printables.com/model/{item_id}/files/zip"
-
-        print(f"DEBUG: Importing from {service} ID {item_id}. URL: {download_url}")
-        
-        try:
-            response = requests.get(download_url, headers=HEADERS, stream=True, timeout=60)
-            response.raise_for_status()
-        except Exception as e:
-            raise Exception(f"Failed to download from {service}: {e}")
-
-        import db
-        import hashlib
-        from services import order_processing
-
-        # Process the ZIP in memory
-        imported_files = []
-        with zipfile.ZipFile(io.BytesIO(response.content)) as z:
-            for file_info in z.infolist():
-                filename = os.path.basename(file_info.filename)
-                if not filename or filename.startswith('.') or filename.startswith('__MACOSX'):
-                    continue
-                
-                ext = os.path.splitext(filename)[1].lower()
-                if ext in ['.stl', '.obj', '.step', '.stp', '.3mf']:
-                    unique_name = f"{uuid.uuid4().hex}{ext}"
-                    save_path = os.path.join(upload_dir, unique_name)
-                    db_file_path = f"uploads/{unique_name}"
-                    
-                    # Read content to memory to hash it
-                    content = z.read(file_info.filename)
-                    with open(save_path, "wb") as target:
-                        target.write(content)
-                    
-                    file_hash = hashlib.sha256(content).hexdigest()
-                    file_size = len(content)
-                    
-                    # Check cache for filament/time/dimensions
-                    filament_g = None
-                    print_time = None
-                    dimensions = None
-                    cached = db.execute_query(
-                        "SELECT filament_g, print_time, dimensions FROM order_files WHERE file_hash = %s AND print_time IS NOT NULL LIMIT 1",
-                        (file_hash,)
-                    )
-                    if cached:
-                        filament_g = cached[0]['filament_g']
-                        print_time = cached[0]['print_time']
-                        dimensions = cached[0]['dimensions']
-                    
-                    # Insert into DB
-                    query = "INSERT INTO order_files (order_id, filename, file_path, file_size, quantity, file_hash, print_time, filament_g, preview_path, dimensions) VALUES (NULL, %s, %s, %s, 1, %s, %s, %s, NULL, %s)"
-                    f_id = db.execute_commit(query, (filename, db_file_path, file_size, file_hash, print_time, filament_g, dimensions))
-                    
-                    # Trigger background processing (previews, etc.)
-                    if background_tasks:
-                        background_tasks.add_task(order_processing.process_single_file_async, f_id)
-                    
-                    imported_files.append({
-                        "id": f_id,
-                        "filename": filename,
-                        "size": file_size,
-                        "print_time": print_time,
-                        "filament_g": filament_g,
-                        "dimensions": dimensions,
-                        "status": "success"
-                    })
-        
-        if not imported_files:
-            raise Exception("No valid 3D models found in the downloaded archive.")
-            
-        return imported_files

+ 2 - 88
src/components/ModelUploadSection.vue

@@ -158,39 +158,8 @@
             </label>
             <div class="relative group">
               <input v-model="modelLink" type="url" :placeholder="t('upload.modelLinkPlaceholder')"
-                @paste="onLinkPaste"
-                class="w-full bg-secondary/50 border border-black/[0.03] rounded-2xl px-4 py-2.5 pr-12 focus:outline-none focus:ring-4 focus:ring-primary/10 focus:border-primary transition-all text-sm font-medium" />
-              <button 
-                v-if="modelLink.includes('thingiverse.com') || modelLink.includes('printables.com')"
-                type="button"
-                @click="handleImportUrl"
-                :disabled="isImporting"
-                class="absolute right-2 top-1/2 -translate-y-1/2 p-2 rounded-xl bg-primary text-white hover:bg-primary/90 transition-all shadow-sm disabled:opacity-50 disabled:cursor-not-allowed"
-                :title="t('common.import')"
-              >
-                <Loader2 v-if="isImporting" class="w-4 h-4 animate-spin" />
-                <LinkIcon v-else class="w-4 h-4" />
-              </button>
+                class="w-full bg-secondary/50 border border-black/[0.03] rounded-2xl px-4 py-2.5 focus:outline-none focus:ring-4 focus:ring-primary/10 focus:border-primary transition-all text-sm font-medium" />
             </div>
-            
-            <!-- Animated Import Prompt -->
-            <Transition enter-active-class="transition duration-300 ease-out" enter-from-class="transform -translate-y-2 opacity-0" enter-to-class="transform translate-y-0 opacity-100">
-              <div v-if="(modelLink.includes('thingiverse.com') || modelLink.includes('printables.com')) && !files.length && !isImporting" 
-                class="mt-3 p-4 bg-primary/10 border border-primary/20 rounded-2xl flex items-center justify-between gap-4 animate-pulse-subtle">
-                <div class="flex items-center gap-3">
-                  <div class="w-10 h-10 rounded-full bg-primary/20 flex items-center justify-center text-primary">
-                    <FileBox class="w-5 h-5" />
-                  </div>
-                  <div>
-                    <p class="text-xs font-bold text-primary uppercase tracking-tight">{{ t("common.import") }}?</p>
-                    <p class="text-[10px] text-primary/60 font-medium">{{ t("upload.importHint") || "We can automatically download files from this link!" }}</p>
-                  </div>
-                </div>
-                <Button @click="handleImportUrl" variant="hero" size="sm" class="h-9 px-4 rounded-xl shadow-lg">
-                  {{ t("common.import") }}
-                </Button>
-              </div>
-            </Transition>
           </div>
         </div>
 
@@ -370,7 +339,7 @@ import { Upload, FileBox, X, Check, Link as LinkIcon, MapPin, User, Phone, Mail,
 import Button from "./ui/button.vue";
 import { defineAsyncComponent } from "vue";
 const StlViewer = defineAsyncComponent(() => import("@/components/StlViewer.vue"));
-import { submitOrder, getCurrentUser, getMaterials, getPriceEstimate, uploadFilesToServer, importFromUrl } from "@/lib/api";
+import { submitOrder, getCurrentUser, getMaterials, getPriceEstimate, uploadFilesToServer } from "@/lib/api";
 
 interface UploadedFile { id: string; dbId?: number; name: string; size: number; type: string; file?: File; quantity: number; basePrice?: number; isUploading?: boolean; printTime?: string; filamentG?: number; dimensions?: string; }
 
@@ -382,7 +351,6 @@ const { t, locale } = useI18n();
 const files = ref<UploadedFile[]>([]);
 const isDragging = ref(false);
 const isSubmitting = ref(false);
-const isImporting = ref(false);
 const modelLink = ref("");
 const address = ref("");
 const firstName = ref("");
@@ -512,60 +480,6 @@ async function addFiles(rawFiles: File[]) {
   }
 }
 function handleDrop(e: DragEvent) { isDragging.value = false; addFiles(Array.from(e.dataTransfer?.files ?? [])); }
-async function handleImportUrl() {
-  if (!modelLink.value || isImporting.value) return;
-  
-  const token = localStorage.getItem("token");
-  if (!token) {
-    toast.error(t("auth.loginRequired"));
-    return;
-  }
-
-  isImporting.value = true;
-  const loadingToast = toast.loading(t("common.loading") + "...");
-  
-  try {
-    const res = await importFromUrl(modelLink.value);
-    if (res.status === "success" && res.files) {
-      for (const f of res.files) {
-        // Prevent duplicates
-        if (files.value.some(existing => existing.dbId === f.id)) continue;
-        
-        files.value.push({
-          id: Math.random().toString(36).substr(2, 9),
-          dbId: f.id,
-          name: f.filename,
-          size: f.size,
-          type: "model/stl",
-          quantity: 1,
-          printTime: f.print_time,
-          filamentG: f.filament_g,
-          dimensions: f.dimensions,
-          isUploading: false
-        });
-      }
-      toast.success(t("common.success"), { id: loadingToast });
-    }
-  } catch (e: any) {
-    console.error("Import failed:", e);
-    let msg = e.message || "Failed to import models";
-    if (modelLink.value.includes("printables.com")) {
-      msg = "Printables link failed. This site is often protected. Please download ZIP manually and upload here.";
-    }
-    toast.error(msg, { id: loadingToast });
-  } finally {
-    isImporting.value = false;
-  }
-}
-
-function onLinkPaste() {
-  // Wait for next tick to let v-model update
-  setTimeout(() => {
-    if ((modelLink.value.includes('thingiverse.com') || modelLink.value.includes('printables.com')) && !files.value.length) {
-      handleImportUrl();
-    }
-  }, 100);
-}
 
 async function handleFileSelect(e: Event) { addFiles(Array.from((e.target as HTMLInputElement).files ?? [])); }
 function removeFile(id: string) { files.value = files.value.filter(f => f.id !== id); }

+ 0 - 13
src/lib/api.ts

@@ -55,19 +55,6 @@ export const uploadFilesToServer = async (data: FormData) => {
   return response.json();
 };
 
-export const importFromUrl = async (url: string) => {
-  const token = localStorage.getItem("token");
-  const response = await fetch(`${API_BASE_URL}/importer/url`, {
-    method: "POST",
-    headers: { 
-      "Content-Type": "application/json",
-      "Authorization": `Bearer ${token}`
-    },
-    body: JSON.stringify({ url }),
-  });
-  if (!response.ok) throw new Error(await getErrorMessage(response, "Failed to import from URL"));
-  return response.json();
-};
 
 export const submitOrder = async (orderData: FormData) => {
   const token = localStorage.getItem("token");