File Upload System
Run ID: 69b6ee7595d85cd597625a8d2026-03-29Development
PantheraHive BOS
BOS Dashboard

Workflow Execution: File Upload System

Category: Development

Description: File Upload System

User Inputs:


1. Workflow Summary & Objective

This workflow outlines the implementation of a robust, scalable, and secure file upload system leveraging AWS S3 as the primary storage solution, with a strict 10MB maximum file size limit. The architecture emphasizes best practices for performance, cost-efficiency, and security, utilizing pre-signed URLs for direct client-to-S3 uploads.

The objective is to provide a detailed guide for developing a system that allows users to upload files through a web application, securely stores them in an S3 bucket, and ensures adherence to the specified size constraint.

2. System Architecture Overview

The recommended architecture for this file upload system involves three core components:

  1. Frontend (Client-side): The user interface where users select files, initiate uploads, and view progress. It performs initial client-side validation.
  2. Backend API (Server-side): A secure application layer responsible for authenticating users, authorizing upload requests, generating secure S3 pre-signed URLs, and recording file metadata in a database.
  3. AWS S3 (Object Storage): The highly durable, scalable, and secure cloud storage service where the actual files are stored.

High-Level Flow:

  1. User selects a file in the frontend.
  2. Frontend performs initial size validation (10MB) and sends metadata (filename, type, size) to the Backend API.
  3. Backend API authenticates the user, validates the request, and generates a time-limited, secure S3 pre-signed URL that specifically allows a PUT operation for a file of the specified size and type.
  4. Backend API returns the pre-signed URL to the frontend.
  5. Frontend directly uploads the file to S3 using the pre-signed URL.
  6. S3 processes the upload, enforcing the size limit and other policies embedded in the pre-signed URL.
  7. Frontend receives upload status from S3 and can optionally notify the Backend API for post-upload processing or status updates.

3. Core Components & Implementation Steps

3.1. AWS S3 Configuration

The foundation of the system is a properly configured S3 bucket.

  1. Create S3 Bucket:

* Name: Choose a globally unique, descriptive name (e.g., your-app-name-uploads-prod).

* Region: Select a region geographically close to your users or backend services for optimal latency and compliance.

* Block Public Access: Enable "Block all public access" to ensure files are private by default.

  1. Enable Versioning (Recommended): Provides protection against accidental deletions and overwrites, allowing recovery of previous file versions.
  2. Default Encryption (Recommended): Enable Server-Side Encryption (SSE-S3) by default for all new objects to ensure data at rest is encrypted.
  3. CORS Configuration:

* Purpose: Essential for direct browser uploads using pre-signed URLs. It allows your frontend domain to make cross-origin requests to your S3 bucket.

* Configuration Example:

text • 2,161 chars
    *   **Note:** If using pre-signed URLs for direct upload, the backend only needs `s3:PutObject` permissions to *generate* the URL, not to directly upload the file itself.

### 3.2. Backend API Development

The backend serves as the secure gatekeeper for upload requests.

1.  **Endpoint for Upload Request:**
    *   Create an API endpoint (e.g., `POST /api/upload/initiate`) that the frontend calls to request an upload.
    *   **Request Body:** Should include file metadata from the client (e.g., `fileName`, `fileType`, `fileSize`).
    *   **Authentication & Authorization:** Crucially, implement robust authentication (e.g., JWT, OAuth) and authorization checks to ensure only legitimate and authorized users can request upload URLs.
    *   **Server-Side Validation:**
        *   **File Size:** Validate `fileSize <= 10 * 1024 * 1024` bytes. **This is critical**, as client-side validation can be bypassed.
        *   **File Type (Optional but Recommended):** Validate `fileType` against a whitelist of allowed MIME types (e.g., `image/jpeg`, `application/pdf`).
        *   **File Name:** Sanitize `fileName` to prevent directory traversal or other path manipulation attacks.
2.  **Generate S3 Pre-signed URL:**
    *   Use the AWS SDK (e.g., `boto3` for Python, `aws-sdk-js` for Node.js) to generate a pre-signed URL for a `PUT` operation.
    *   **Key Parameters for Pre-signed URL Generation:**
        *   `Bucket`: Your S3 bucket name.
        *   `Key`: The desired S3 object key (path and filename). **Recommendation:** Use a unique identifier (e.g., UUID) for the object key and store the original filename in your database. Example: `uploads/user-id/{UUID}.{extension}`.
        *   `Expires`: Set a short expiry time (e.g., 5-15 minutes).
        *   `ContentType`: Set this to the `fileType` received from the client. S3 will enforce this during the upload.
        *   `ContentLengthRange`: Specify the allowed `min` and `max` file sizes. This is the **most robust way to enforce the 10MB limit directly at S3** for `PUT` operations. The `max` should be 10MB (10,485,760 bytes).
    *   **Example (Node.js using `aws-sdk`):**
        
Sandboxed live preview
  • Note: createPresignedPost is generally more flexible for enforcing conditions like Content-Length-Range directly in the S3 policy. If using getSignedUrl for PUT operations, S3 will primarily check the Content-Length header sent by the client against the specified Content-Length-Range in the policy.
  1. Database Integration:

* Store metadata about the pending or completed upload in your database (e.g., id, s3_key, original_filename, uploader_user_id, status (e.g., 'pending', 'uploaded'), upload_timestamp, size, content_type).

* Initially, mark the status as 'pending' or 'initiated'.

3.3. Frontend Development

The frontend provides the user experience for file selection and upload.

  1. File Input:

* Use an HTML <input type="file" id="fileInput"> element.

Add an accept attribute (e.g., accept="image/,application/pdf") for client-side hint on allowed file types.

  1. Client-Side Validation:

Max Size: Immediately check file.size when a file is selected. If file.size > 10 1024 * 1024, display an error to the user and prevent further action. This provides instant feedback and avoids unnecessary network requests.

* File Type: Check file.type against allowed MIME types.

  1. Initiate Upload Request:

* When the user confirms the upload, send a POST request to your Backend API endpoint (/api/upload/initiate) with the file's name, type, and size.

  1. Direct Upload to S3:

* Upon receiving the pre-signed URL (and fields if using createPresignedPost) from the Backend API:

* Create a FormData object if using createPresignedPost (to include the fields).

* Append the actual file to the FormData or send it directly as the request body for PUT operations.

* Use fetch or XMLHttpRequest to send a PUT request directly to the S3 pre-signed URL.

* Crucial: Set the Content-Type header of this request to the actual MIME type of the file.

* Progress Tracking: Implement event listeners for progress events (XMLHttpRequest.upload.onprogress or fetch stream readers) to display an upload progress bar to the user.

  1. Handle S3 Response:

* Upon successful upload to S3 (HTTP 200 OK), notify the user and potentially send a finalization request to your Backend API (e.g., PUT /api/upload/complete/{uploadId}) to update the file's status in the database to 'uploaded'.

* Handle S3 error responses (e.g., 403 Forbidden if permissions are wrong, 400 Bad Request if size/type mismatch).

  1. User Feedback: Provide clear messages for success, ongoing progress, and any errors encountered during validation or upload.

3.4. Security Considerations

  • Authentication & Authorization: Essential on the backend for all API calls.
  • Input Validation: Perform thorough validation on both client and server sides for file size, type, and name.
  • Least Privilege: Grant only the necessary S3 permissions to your backend IAM role.
  • Short-Lived Pre-signed URLs: Minimize the window of opportunity for misuse.
  • Content Type Enforcement: Enforce Content-Type through the pre-signed URL policy and verify it on the backend.
  • Object Key Randomization: Use UUIDs or other random strings in S3 object keys to prevent predictable URLs and potential enumeration attacks.
  • Virus Scanning (Post-upload): For production systems, consider integrating a virus scanner (e.g., ClamAV via AWS Lambda triggered by S3 events) immediately after upload, before files are made available for download or processing.
  • Server-Side Encryption: Ensure S3 bucket has default encryption enabled.

3.5. Error Handling & Monitoring

  • Client-Side: Handle network errors, file selection errors, and API call failures gracefully.
  • Backend: Implement robust error logging (e.g., AWS CloudWatch Logs) for API failures, S3 SDK errors, and validation issues.
  • S3 Specific Errors: Be prepared for S3 to reject uploads if Content-Length or Content-Type conditions are not met.
  • Retries: For transient network issues, implement exponential backoff and retry logic for S3 uploads from the frontend.
  • Monitoring: Set up AWS CloudWatch alarms for S3 bucket metrics (e.g., upload failures, storage usage) and backend API/Lambda errors.

4. Specific Recommendations

  • Direct-to-S3 Uploads with Pre-signed URLs: This is the most recommended approach for efficiency and scalability. It offloads the heavy lifting of file transfer from your backend, reducing server load and bandwidth costs.
  • Enforce Max Size at Multiple Layers:

1. Client-side: Immediate user feedback.

2. Backend API: Critical for security (prevents malicious clients from requesting URLs for oversized files).

3. S3 Pre-signed URL Policy: The ultimate enforcement directly by S3 during the PUT operation.

  • File Type Validation: Always whitelist allowed MIME types on the backend. Never rely solely on file extensions.
  • Progress Indicators: Essential for a good user experience, especially with larger files (even 10MB can take time on slow connections).
  • Serverless Backend: Consider using AWS Lambda and API Gateway for your backend API. This provides automatic scaling, high availability, and a pay-per-execution cost model, which is highly efficient for event-driven tasks like generating pre-signed URLs.
  • Object Naming Strategy: Store original filenames in your database but use unique, non-guessable identifiers (like UUIDs) as S3 object keys to prevent collisions and enhance security.

* Example S3 Key: uploads/user-123/documents/a1b2c3d4-e5f6-7890-1234-567890abcdef.pdf

  • S3 Lifecycle Policies: Configure S3 Lifecycle rules for cost optimization. For example, transition older files to S3 Glacier after a certain period if they are rarely accessed, or delete temporary/stale uploads after a set duration.

5. Structured Data: Key Configuration Parameters

| Parameter | Value/Recommendation | Notes |

| :----------------------- | :--------------------------------------------------- | :--------------------------------------------------------------------------------------------------------------------------------- |

| Storage Provider | AWS S3 | Highly reliable, scalable, and secure object storage. |

| Maximum File Size | 10 MB (10,485,760 bytes) | Enforced on client-side, server-side (API), and via S3 pre-signed URL Content-Length-Range condition. |

| S3 Bucket Name | your-app-name-uploads-prod | Must be globally unique. Follow naming conventions. |

| S3 Region | us-east-1, eu-west-1, etc. | Choose based on latency to users/backend and data residency requirements. |

| IAM Policy (Backend) | s3:PutObject, s3:GetObject (for downloads), s3:DeleteObject (for cleanup) | Adhere to the principle of least privilege. |

| Pre-signed URL Expiry| 300 seconds (5 minutes) | Short expiry limits potential misuse. Adjust based on expected upload times. |

| CORS Configuration | AllowedOrigin: https://your-frontend-domain.com | Crucial for browser-based direct uploads. |

| | AllowedMethod: PUT | |

| | AllowedHeader: * (or specific headers like Content-Type) | |

| Object Key Prefix | uploads/{user_id}/ or temp/ | Organize files logically within the bucket for easier management and access control. |

| Default Encryption | SSE-S3 (Server-Side Encryption with S3-managed keys) | Recommended for all data at rest. |

| Versioning | Enabled | Provides recovery from accidental deletion or overwrites. |

| Content-Type Validation| Whitelist common MIME types (e.g., image/jpeg, application/pdf, text/plain) | Crucial for security and correct serving of files. Enforced by S3 via pre-signed URL Conditions. |

6. Next Steps & Further Considerations

  1. Post-Upload Processing:

* S3 Event Notifications: Configure S3 to trigger AWS Lambda functions on s3:ObjectCreated events.

* Use Cases: Image resizing (thumbnails), virus scanning, metadata extraction, indexing, or notifying other services.

  1. Public vs. Private Access:

* By default, files uploaded via this system will be private.

* If files need to be publicly accessible, consider serving them through Amazon CloudFront (a CDN) with an Origin Access Control (OAC) or Origin Access Identity (OAI) for S3 to ensure secure content delivery while keeping the S3 bucket private.

* For controlled, temporary access to private files, generate S3 pre-signed URLs for GET operations.

  1. Data Retention & Lifecycle:

* Implement S3 Lifecycle policies to automatically manage object storage classes (e.g., move to S3 Intelligent-Tiering or Glacier for cost savings) or delete objects after a specified period.

  1. Scalability Testing: Perform load testing on your backend API and simulate concurrent S3 uploads to ensure the system performs under anticipated load.
  2. Cost Optimization: Regularly review S3 storage, data transfer, and request costs. Leverage S3 Intelligent-Tiering if access patterns are unknown or changing.

This comprehensive guide should enable you to build a robust and efficient file upload system using S3, adhering to your specified constraints and incorporating professional best practices.

file_upload_system.txt
Download source file
Copy all content
Full output as text
Download ZIP
IDE-ready project ZIP
Copy share link
Permanent URL for this run
Get Embed Code
Embed this result on any website
Print / Save PDF
Use browser print dialog
\n\n\n"); var hasSrcMain=Object.keys(extracted).some(function(k){return k.indexOf("src/main")>=0;}); if(!hasSrcMain) zip.file(folder+"src/main."+ext,"import React from 'react'\nimport ReactDOM from 'react-dom/client'\nimport App from './App'\nimport './index.css'\n\nReactDOM.createRoot(document.getElementById('root')!).render(\n \n \n \n)\n"); var hasSrcApp=Object.keys(extracted).some(function(k){return k==="src/App."+ext||k==="App."+ext;}); if(!hasSrcApp) zip.file(folder+"src/App."+ext,"import React from 'react'\nimport './App.css'\n\nfunction App(){\n return(\n
\n
\n

"+slugTitle(pn)+"

\n

Built with PantheraHive BOS

\n
\n
\n )\n}\nexport default App\n"); zip.file(folder+"src/index.css","*{margin:0;padding:0;box-sizing:border-box}\nbody{font-family:system-ui,-apple-system,sans-serif;background:#f0f2f5;color:#1a1a2e}\n.app{min-height:100vh;display:flex;flex-direction:column}\n.app-header{flex:1;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:12px;padding:40px}\nh1{font-size:2.5rem;font-weight:700}\n"); zip.file(folder+"src/App.css",""); zip.file(folder+"src/components/.gitkeep",""); zip.file(folder+"src/pages/.gitkeep",""); zip.file(folder+"src/hooks/.gitkeep",""); Object.keys(extracted).forEach(function(p){ var fp=p.startsWith("src/")?p:"src/"+p; zip.file(folder+fp,extracted[p]); }); zip.file(folder+"README.md","# "+slugTitle(pn)+"\n\nGenerated by PantheraHive BOS.\n\n## Setup\n\`\`\`bash\nnpm install\nnpm run dev\n\`\`\`\n\n## Build\n\`\`\`bash\nnpm run build\n\`\`\`\n\n## Open in IDE\nOpen the project folder in VS Code or WebStorm.\n"); zip.file(folder+".gitignore","node_modules/\ndist/\n.env\n.DS_Store\n*.local\n"); } /* --- Vue (Vite + Composition API + TypeScript) --- */ function buildVue(zip,folder,app,code,panelTxt){ var pn=pkgName(app); var C=cc(pn); var extracted=extractCode(panelTxt); zip.file(folder+"package.json",'{\n "name": "'+pn+'",\n "version": "0.0.0",\n "type": "module",\n "scripts": {\n "dev": "vite",\n "build": "vue-tsc -b && vite build",\n "preview": "vite preview"\n },\n "dependencies": {\n "vue": "^3.5.13",\n "vue-router": "^4.4.5",\n "pinia": "^2.3.0",\n "axios": "^1.7.9"\n },\n "devDependencies": {\n "@vitejs/plugin-vue": "^5.2.1",\n "typescript": "~5.7.3",\n "vite": "^6.0.5",\n "vue-tsc": "^2.2.0"\n }\n}\n'); zip.file(folder+"vite.config.ts","import { defineConfig } from 'vite'\nimport vue from '@vitejs/plugin-vue'\nimport { resolve } from 'path'\n\nexport default defineConfig({\n plugins: [vue()],\n resolve: { alias: { '@': resolve(__dirname,'src') } }\n})\n"); zip.file(folder+"tsconfig.json",'{"files":[],"references":[{"path":"./tsconfig.app.json"},{"path":"./tsconfig.node.json"}]}\n'); zip.file(folder+"tsconfig.app.json",'{\n "compilerOptions":{\n "target":"ES2020","useDefineForClassFields":true,"module":"ESNext","lib":["ES2020","DOM","DOM.Iterable"],\n "skipLibCheck":true,"moduleResolution":"bundler","allowImportingTsExtensions":true,\n "isolatedModules":true,"moduleDetection":"force","noEmit":true,"jsxImportSource":"vue",\n "strict":true,"paths":{"@/*":["./src/*"]}\n },\n "include":["src/**/*.ts","src/**/*.d.ts","src/**/*.tsx","src/**/*.vue"]\n}\n'); zip.file(folder+"env.d.ts","/// \n"); zip.file(folder+"index.html","\n\n\n \n \n "+slugTitle(pn)+"\n\n\n
\n \n\n\n"); var hasMain=Object.keys(extracted).some(function(k){return k==="src/main.ts"||k==="main.ts";}); if(!hasMain) zip.file(folder+"src/main.ts","import { createApp } from 'vue'\nimport { createPinia } from 'pinia'\nimport App from './App.vue'\nimport './assets/main.css'\n\nconst app = createApp(App)\napp.use(createPinia())\napp.mount('#app')\n"); var hasApp=Object.keys(extracted).some(function(k){return k.indexOf("App.vue")>=0;}); if(!hasApp) zip.file(folder+"src/App.vue","\n\n\n\n\n"); zip.file(folder+"src/assets/main.css","*{margin:0;padding:0;box-sizing:border-box}body{font-family:system-ui,sans-serif;background:#fff;color:#213547}\n"); zip.file(folder+"src/components/.gitkeep",""); zip.file(folder+"src/views/.gitkeep",""); zip.file(folder+"src/stores/.gitkeep",""); Object.keys(extracted).forEach(function(p){ var fp=p.startsWith("src/")?p:"src/"+p; zip.file(folder+fp,extracted[p]); }); zip.file(folder+"README.md","# "+slugTitle(pn)+"\n\nGenerated by PantheraHive BOS.\n\n## Setup\n\`\`\`bash\nnpm install\nnpm run dev\n\`\`\`\n\n## Build\n\`\`\`bash\nnpm run build\n\`\`\`\n\nOpen in VS Code or WebStorm.\n"); zip.file(folder+".gitignore","node_modules/\ndist/\n.env\n.DS_Store\n*.local\n"); } /* --- Angular (v19 standalone) --- */ function buildAngular(zip,folder,app,code,panelTxt){ var pn=pkgName(app); var C=cc(pn); var sel=pn.replace(/_/g,"-"); var extracted=extractCode(panelTxt); zip.file(folder+"package.json",'{\n "name": "'+pn+'",\n "version": "0.0.0",\n "scripts": {\n "ng": "ng",\n "start": "ng serve",\n "build": "ng build",\n "test": "ng test"\n },\n "dependencies": {\n "@angular/animations": "^19.0.0",\n "@angular/common": "^19.0.0",\n "@angular/compiler": "^19.0.0",\n "@angular/core": "^19.0.0",\n "@angular/forms": "^19.0.0",\n "@angular/platform-browser": "^19.0.0",\n "@angular/platform-browser-dynamic": "^19.0.0",\n "@angular/router": "^19.0.0",\n "rxjs": "~7.8.0",\n "tslib": "^2.3.0",\n "zone.js": "~0.15.0"\n },\n "devDependencies": {\n "@angular-devkit/build-angular": "^19.0.0",\n "@angular/cli": "^19.0.0",\n "@angular/compiler-cli": "^19.0.0",\n "typescript": "~5.6.0"\n }\n}\n'); zip.file(folder+"angular.json",'{\n "$schema": "./node_modules/@angular/cli/lib/config/schema.json",\n "version": 1,\n "newProjectRoot": "projects",\n "projects": {\n "'+pn+'": {\n "projectType": "application",\n "root": "",\n "sourceRoot": "src",\n "prefix": "app",\n "architect": {\n "build": {\n "builder": "@angular-devkit/build-angular:application",\n "options": {\n "outputPath": "dist/'+pn+'",\n "index": "src/index.html",\n "browser": "src/main.ts",\n "tsConfig": "tsconfig.app.json",\n "styles": ["src/styles.css"],\n "scripts": []\n }\n },\n "serve": {"builder":"@angular-devkit/build-angular:dev-server","configurations":{"production":{"buildTarget":"'+pn+':build:production"},"development":{"buildTarget":"'+pn+':build:development"}},"defaultConfiguration":"development"}\n }\n }\n }\n}\n'); zip.file(folder+"tsconfig.json",'{\n "compileOnSave": false,\n "compilerOptions": {"baseUrl":"./","outDir":"./dist/out-tsc","forceConsistentCasingInFileNames":true,"strict":true,"noImplicitOverride":true,"noPropertyAccessFromIndexSignature":true,"noImplicitReturns":true,"noFallthroughCasesInSwitch":true,"paths":{"@/*":["src/*"]},"skipLibCheck":true,"esModuleInterop":true,"sourceMap":true,"declaration":false,"experimentalDecorators":true,"moduleResolution":"bundler","importHelpers":true,"target":"ES2022","module":"ES2022","useDefineForClassFields":false,"lib":["ES2022","dom"]},\n "references":[{"path":"./tsconfig.app.json"}]\n}\n'); zip.file(folder+"tsconfig.app.json",'{\n "extends":"./tsconfig.json",\n "compilerOptions":{"outDir":"./dist/out-tsc","types":[]},\n "files":["src/main.ts"],\n "include":["src/**/*.d.ts"]\n}\n'); zip.file(folder+"src/index.html","\n\n\n \n "+slugTitle(pn)+"\n \n \n \n\n\n \n\n\n"); zip.file(folder+"src/main.ts","import { bootstrapApplication } from '@angular/platform-browser';\nimport { appConfig } from './app/app.config';\nimport { AppComponent } from './app/app.component';\n\nbootstrapApplication(AppComponent, appConfig)\n .catch(err => console.error(err));\n"); zip.file(folder+"src/styles.css","* { margin: 0; padding: 0; box-sizing: border-box; }\nbody { font-family: system-ui, -apple-system, sans-serif; background: #f9fafb; color: #111827; }\n"); var hasComp=Object.keys(extracted).some(function(k){return k.indexOf("app.component")>=0;}); if(!hasComp){ zip.file(folder+"src/app/app.component.ts","import { Component } from '@angular/core';\nimport { RouterOutlet } from '@angular/router';\n\n@Component({\n selector: 'app-root',\n standalone: true,\n imports: [RouterOutlet],\n templateUrl: './app.component.html',\n styleUrl: './app.component.css'\n})\nexport class AppComponent {\n title = '"+pn+"';\n}\n"); zip.file(folder+"src/app/app.component.html","
\n
\n

"+slugTitle(pn)+"

\n

Built with PantheraHive BOS

\n
\n \n
\n"); zip.file(folder+"src/app/app.component.css",".app-header{display:flex;flex-direction:column;align-items:center;justify-content:center;min-height:60vh;gap:16px}h1{font-size:2.5rem;font-weight:700;color:#6366f1}\n"); } zip.file(folder+"src/app/app.config.ts","import { ApplicationConfig, provideZoneChangeDetection } from '@angular/core';\nimport { provideRouter } from '@angular/router';\nimport { routes } from './app.routes';\n\nexport const appConfig: ApplicationConfig = {\n providers: [\n provideZoneChangeDetection({ eventCoalescing: true }),\n provideRouter(routes)\n ]\n};\n"); zip.file(folder+"src/app/app.routes.ts","import { Routes } from '@angular/router';\n\nexport const routes: Routes = [];\n"); Object.keys(extracted).forEach(function(p){ var fp=p.startsWith("src/")?p:"src/"+p; zip.file(folder+fp,extracted[p]); }); zip.file(folder+"README.md","# "+slugTitle(pn)+"\n\nGenerated by PantheraHive BOS.\n\n## Setup\n\`\`\`bash\nnpm install\nng serve\n# or: npm start\n\`\`\`\n\n## Build\n\`\`\`bash\nng build\n\`\`\`\n\nOpen in VS Code with Angular Language Service extension.\n"); zip.file(folder+".gitignore","node_modules/\ndist/\n.env\n.DS_Store\n*.local\n.angular/\n"); } /* --- Python --- */ function buildPython(zip,folder,app,code){ var title=slugTitle(app); var pn=pkgName(app); var src=code.replace(/^\`\`\`[\w]*\n?/m,"").replace(/\n?\`\`\`$/m,"").trim(); var reqMap={"numpy":"numpy","pandas":"pandas","sklearn":"scikit-learn","tensorflow":"tensorflow","torch":"torch","flask":"flask","fastapi":"fastapi","uvicorn":"uvicorn","requests":"requests","sqlalchemy":"sqlalchemy","pydantic":"pydantic","dotenv":"python-dotenv","PIL":"Pillow","cv2":"opencv-python","matplotlib":"matplotlib","seaborn":"seaborn","scipy":"scipy"}; var reqs=[]; Object.keys(reqMap).forEach(function(k){if(src.indexOf("import "+k)>=0||src.indexOf("from "+k)>=0)reqs.push(reqMap[k]);}); var reqsTxt=reqs.length?reqs.join("\n"):"# add dependencies here\n"; zip.file(folder+"main.py",src||"# "+title+"\n# Generated by PantheraHive BOS\n\nprint(title+\" loaded\")\n"); zip.file(folder+"requirements.txt",reqsTxt); zip.file(folder+".env.example","# Environment variables\n"); zip.file(folder+"README.md","# "+title+"\n\nGenerated by PantheraHive BOS.\n\n## Setup\n\`\`\`bash\npython3 -m venv .venv\nsource .venv/bin/activate\npip install -r requirements.txt\n\`\`\`\n\n## Run\n\`\`\`bash\npython main.py\n\`\`\`\n"); zip.file(folder+".gitignore",".venv/\n__pycache__/\n*.pyc\n.env\n.DS_Store\n"); } /* --- Node.js --- */ function buildNode(zip,folder,app,code){ var title=slugTitle(app); var pn=pkgName(app); var src=code.replace(/^\`\`\`[\w]*\n?/m,"").replace(/\n?\`\`\`$/m,"").trim(); var depMap={"mongoose":"^8.0.0","dotenv":"^16.4.5","axios":"^1.7.9","cors":"^2.8.5","bcryptjs":"^2.4.3","jsonwebtoken":"^9.0.2","socket.io":"^4.7.4","uuid":"^9.0.1","zod":"^3.22.4","express":"^4.18.2"}; var deps={}; Object.keys(depMap).forEach(function(k){if(src.indexOf(k)>=0)deps[k]=depMap[k];}); if(!deps["express"])deps["express"]="^4.18.2"; var pkgJson=JSON.stringify({"name":pn,"version":"1.0.0","main":"src/index.js","scripts":{"start":"node src/index.js","dev":"nodemon src/index.js"},"dependencies":deps,"devDependencies":{"nodemon":"^3.0.3"}},null,2)+"\n"; zip.file(folder+"package.json",pkgJson); var fallback="const express=require(\"express\");\nconst app=express();\napp.use(express.json());\n\napp.get(\"/\",(req,res)=>{\n res.json({message:\""+title+" API\"});\n});\n\nconst PORT=process.env.PORT||3000;\napp.listen(PORT,()=>console.log(\"Server on port \"+PORT));\n"; zip.file(folder+"src/index.js",src||fallback); zip.file(folder+".env.example","PORT=3000\n"); zip.file(folder+".gitignore","node_modules/\n.env\n.DS_Store\n"); zip.file(folder+"README.md","# "+title+"\n\nGenerated by PantheraHive BOS.\n\n## Setup\n\`\`\`bash\nnpm install\n\`\`\`\n\n## Run\n\`\`\`bash\nnpm run dev\n\`\`\`\n"); } /* --- Vanilla HTML --- */ function buildVanillaHtml(zip,folder,app,code){ var title=slugTitle(app); var isFullDoc=code.trim().toLowerCase().indexOf("=0||code.trim().toLowerCase().indexOf("=0; var indexHtml=isFullDoc?code:"\n\n\n\n\n"+title+"\n\n\n\n"+code+"\n\n\n\n"; zip.file(folder+"index.html",indexHtml); zip.file(folder+"style.css","/* "+title+" — styles */\n*{margin:0;padding:0;box-sizing:border-box}\nbody{font-family:system-ui,-apple-system,sans-serif;background:#fff;color:#1a1a2e}\n"); zip.file(folder+"script.js","/* "+title+" — scripts */\n"); zip.file(folder+"assets/.gitkeep",""); zip.file(folder+"README.md","# "+title+"\n\nGenerated by PantheraHive BOS.\n\n## Open\nDouble-click \`index.html\` in your browser.\n\nOr serve locally:\n\`\`\`bash\nnpx serve .\n# or\npython3 -m http.server 3000\n\`\`\`\n"); zip.file(folder+".gitignore",".DS_Store\nnode_modules/\n.env\n"); } /* ===== MAIN ===== */ var sc=document.createElement("script"); sc.src="https://cdnjs.cloudflare.com/ajax/libs/jszip/3.10.1/jszip.min.js"; sc.onerror=function(){ if(lbl)lbl.textContent="Download ZIP"; alert("JSZip load failed — check connection."); }; sc.onload=function(){ var zip=new JSZip(); var base=(_phFname||"output").replace(/\.[^.]+$/,""); var app=base.toLowerCase().replace(/[^a-z0-9]+/g,"_").replace(/^_+|_+$/g,"")||"my_app"; var folder=app+"/"; var vc=document.getElementById("panel-content"); var panelTxt=vc?(vc.innerText||vc.textContent||""):""; var lang=detectLang(_phCode,panelTxt); if(_phIsHtml){ buildVanillaHtml(zip,folder,app,_phCode); } else if(lang==="flutter"){ buildFlutter(zip,folder,app,_phCode,panelTxt); } else if(lang==="react-native"){ buildReactNative(zip,folder,app,_phCode,panelTxt); } else if(lang==="swift"){ buildSwift(zip,folder,app,_phCode,panelTxt); } else if(lang==="kotlin"){ buildKotlin(zip,folder,app,_phCode,panelTxt); } else if(lang==="react"){ buildReact(zip,folder,app,_phCode,panelTxt); } else if(lang==="vue"){ buildVue(zip,folder,app,_phCode,panelTxt); } else if(lang==="angular"){ buildAngular(zip,folder,app,_phCode,panelTxt); } else if(lang==="python"){ buildPython(zip,folder,app,_phCode); } else if(lang==="node"){ buildNode(zip,folder,app,_phCode); } else { /* Document/content workflow */ var title=app.replace(/_/g," "); var md=_phAll||_phCode||panelTxt||"No content"; zip.file(folder+app+".md",md); var h=""+title+""; h+="

"+title+"

"; var hc=md.replace(/&/g,"&").replace(//g,">"); hc=hc.replace(/^### (.+)$/gm,"

$1

"); hc=hc.replace(/^## (.+)$/gm,"

$1

"); hc=hc.replace(/^# (.+)$/gm,"

$1

"); hc=hc.replace(/\*\*(.+?)\*\*/g,"$1"); hc=hc.replace(/\n{2,}/g,"

"); h+="

"+hc+"

Generated by PantheraHive BOS
"; zip.file(folder+app+".html",h); zip.file(folder+"README.md","# "+title+"\n\nGenerated by PantheraHive BOS.\n\nFiles:\n- "+app+".md (Markdown)\n- "+app+".html (styled HTML)\n"); } zip.generateAsync({type:"blob"}).then(function(blob){ var a=document.createElement("a"); a.href=URL.createObjectURL(blob); a.download=app+".zip"; a.click(); URL.revokeObjectURL(a.href); if(lbl)lbl.textContent="Download ZIP"; }); }; document.head.appendChild(sc); } function phShare(){navigator.clipboard.writeText(window.location.href).then(function(){var el=document.getElementById("ph-share-lbl");if(el){el.textContent="Link copied!";setTimeout(function(){el.textContent="Copy share link";},2500);}});}function phEmbed(){var runId=window.location.pathname.split("/").pop().replace(".html","");var embedUrl="https://pantherahive.com/embed/"+runId;var code='';navigator.clipboard.writeText(code).then(function(){var el=document.getElementById("ph-embed-lbl");if(el){el.textContent="Embed code copied!";setTimeout(function(){el.textContent="Get Embed Code";},2500);}});}