Files
upload-gltf/README.md
T
Tom Boullay 78f4aa83e0 refactor: full codebase audit — extract modules, fix type safety, clean dead code
- Extract API helpers from UploadZone into lib/upload-api.ts (FormData builder, checkFolderDiffs, uploadDrive, uploadGit)
- Extract upload orchestration into hooks/useUploadOrchestrator.ts (UploadZone: 489 → 162 lines)
- Extract file diff classification into lib/diff-files.ts (from git route)
- Extract shared SVG icons into components/ui/icons.tsx (7 icons, 0 duplication)
- Extract shared modal wrapper into components/ui/Modal.tsx + ModalActions
- Extract DriveStatusLine sub-component from FolderCard
- Fix checkFolderDiffs silently swallowing auth/network errors (now throws)
- Fix type safety: remove as never casts, add isHttpError type guard, use discriminated union for validateFolder
- Fix nextcloud: cache getConfig, add max bound to findNextVersion, optimize mkdirRecursive (skip PROPFIND)
- Fix drive route: remove req.clone(), extend parseMultiUpload to return extra fields
- Fix commit message: model shown as unchanged with ↔️ on updates (not falsely marked as modified)
- Clean dead code: unused folderExists import, FileStatus/DriveStatus exports, ParsedFile.textureName, getConfig basePath
- Add security headers in next.config.ts (HSTS, X-Content-Type-Options, X-Frame-Options, etc.)
- Update README with new project structure
2026-04-14 17:19:10 +02:00

9.5 KiB

upload-GLTF

A secure web interface for uploading 3D assets (GLTF/GLB + textures) to two destinations:

  • Nextcloud Drive — Archives the original files with automatic versioning (VF/V1/V2...), so artists always have a history of past versions.
  • GitHub — Delivers compressed models (Draco via Blender) to the dev team's repository, ready for integration.

Built for La Fabrik Durable.

Stack

  • Next.js 16 (App Router) + React 19 + TypeScript
  • Three.js (@react-three/fiber + @react-three/drei) for 3D preview
  • Tailwind CSS for styling
  • Octokit for pushing via the GitHub API
  • Nextcloud WebDAV for Drive archiving with automatic versioning
  • Blender (headless) for Draco mesh compression
  • Coolify (Docker) for hosting

Installation

git clone https://github.com/La-Fabrik-Durable/upload-GLTF.git
cd upload-GLTF
npm install

Configuration

Copy .env.example to .env.local and fill in the values:

UPLOAD_SECRET_KEY=your-secret-key-here
GITHUB_TOKEN=ghp_your-github-personal-access-token
GIT_BRANCH=main
GIT_REPO_URL=https://github.com/your-org/your-repo.git
BLENDER_PATH=/Applications/Blender.app/Contents/MacOS/Blender

# Nextcloud Drive (public share WebDAV)
NEXTCLOUD_URL=https://cloud.example.com
NEXTCLOUD_SHARE_TOKEN=your-public-share-token
NEXTCLOUD_SHARE_PASSWORD=
NEXTCLOUD_BASE_PATH=Models
Variable Description Required
UPLOAD_SECRET_KEY Secret key for upload authentication Yes
GITHUB_TOKEN GitHub Personal Access Token (fine-grained, Contents: Read and write) Yes
GIT_BRANCH Target branch (default: main) No
GIT_REPO_URL Target GitHub repository URL Yes
BLENDER_PATH Path to Blender binary (default: blender) No
NEXTCLOUD_URL Nextcloud instance URL Yes
NEXTCLOUD_SHARE_TOKEN Public share token (the part after /s/ in the share link) Yes
NEXTCLOUD_SHARE_PASSWORD Public share password (empty if none) No
NEXTCLOUD_BASE_PATH Root folder on the Drive (default: Models) No

To create a GitHub token: GitHub > Settings > Developer settings > Fine-grained personal access tokens > select the target repo > Permissions > Contents: Read and write.

Usage

Development

npm run dev

Access the app at http://localhost:3000

Note: Draco compression requires Blender installed locally. On macOS, Blender is typically at /Applications/Blender.app/Contents/MacOS/Blender. Set BLENDER_PATH in .env.local accordingly. If Blender is not available, models are pushed to GitHub without compression.

Production (Coolify / Docker)

docker build -t upload-gltf .
docker run -p 3000:3000 \
  -e UPLOAD_SECRET_KEY=your-key \
  -e GITHUB_TOKEN=ghp_xxx \
  -e GIT_REPO_URL=https://github.com/org/repo.git \
  -e NEXTCLOUD_URL=https://cloud.example.com \
  -e NEXTCLOUD_SHARE_TOKEN=your-share-token \
  upload-gltf

The Docker image includes Blender headless (installed once at build time). On startup, the entrypoint checks if Blender is available and logs its version. No extra configuration is needed in production — BLENDER_PATH defaults to blender which is in the container's PATH.

How it works

  1. The user enters their access key
  2. They pick a destination (farm, map, powergrid, workshop, general, environment)
  3. They select a folder containing:
    • model.glb or model.gltf (required)
    • Textures: roughness, normal, metalness, color, displace (.png/.jpg/.webp, optional — missing textures show a warning but don't block the upload)
  4. The model is displayed in a 3D preview
  5. On clicking "Envoyer":
    • The app checks the remote Git repo for existing files and computes diffs (textures by size, models always re-pushed)
    • If the folder doesn't exist, upload proceeds directly
    • If the folder exists and files differ, a confirmation dialog shows only the actual changes
    • If nothing changed, the upload is skipped entirely

Upload flow: Drive first, then Git

  1. Drive upload (archiving) — Original files (before Blender compression) are uploaded to the Nextcloud Drive with automatic versioning (see below). This serves as the artists' source of truth and version history. If the Drive upload fails, a modal asks the user whether to send to Git only or cancel entirely.
  2. Git upload (delivery to devs) — Models are compressed with Blender Draco, then all changed files are pushed to GitHub in a single commit. This is what the dev team consumes in the application.

Drive versioning (Nextcloud WebDAV)

The Drive uses a VF (version finale) / Vx (archived versions) structure:

Models/
  VF/                    ← latest version
    coffeetest/
      model.gltf
      color.jpg
  V1/                    ← first archive
    coffeetest/
  V2/                    ← second archive
    coffeetest/
  • New folder (doesn't exist in VF/): files are uploaded directly to VF/{folderName}/
  • Replace (folder exists in VF/ with diffs): VF/{folderName} is moved to Vx/{folderName} (next available version), then all files are re-uploaded to VF/{folderName}/
  • No changes: nothing happens on the Drive

All files are uploaded to VF/ (not just diffs), because the move operation empties the previous folder.

Commit messages

All changes are pushed in a single commit with a formatted message:

New folder:

update: upload-gltf add a new model -> farm/my-model

📦 Model
  ✅ model.gltf (compressed)
🎨 Textures
  ✅ color.jpg
  ❌ metalness (manquant)

Update (only metalness changed):

update: upload-gltf update -> general/coffeetest

📦 Model
  ↔️ model.gltf (inchange)
🎨 Textures
  🔄 metalness.jpg

Symbols: new — 🔄 modified — ↔️ unchanged (model always re-pushed) — missing or deleted

  1. Orphan files (present on remote but not in the new upload) are deleted in the same commit
  2. If Blender is unavailable, the original model is pushed as-is (graceful fallback)

Destinations

Uploaded models are pushed to public/models/<destination>/<folderName>/ in the target repo:

Destination Path
Farm public/models/farm/
Map public/models/map/
Powergrid public/models/powergrid/
Workshop public/models/workshop/
General public/models/general/
Environment public/models/environment/

Project Structure

app/
├── api/upload/
│   ├── check/route.ts     # GET: check remote folder + file sizes for diff
│   ├── drive/route.ts     # POST: upload originals to Nextcloud Drive (VF/Vx versioning)
│   └── git/route.ts       # POST: compress with Blender + push to GitHub
├── globals.css            # Tailwind + CSS variable fonts
├── layout.tsx             # Root layout (next/font/google)
└── page.tsx               # Home page
components/
├── ui/
│   ├── icons.tsx              # Shared SVG icon components
│   └── Modal.tsx              # Shared modal wrapper + ModalActions
├── upload/
│   ├── SecretInput.tsx        # Access key input
│   ├── DestinationPicker.tsx  # Destination selector
│   ├── FolderDropzone.tsx     # Folder drag & drop / picker
│   ├── FolderCard.tsx         # Folder status card (Drive + Git)
│   ├── DriveStatusLine.tsx    # Drive/Git status sub-line
│   ├── WarningBanner.tsx      # Missing texture warnings
│   ├── OverwriteConfirmModal.tsx  # Diff confirmation dialog
│   ├── NoChangesModal.tsx     # "No changes detected" dialog
│   ├── DriveErrorModal.tsx    # "Drive failed, continue?" dialog
│   └── ActionButtons.tsx      # Upload / Cancel / Reset buttons
├── UploadZone.tsx         # Main upload page (rendering only)
├── ModelViewer.tsx        # Lazy wrapper for 3D viewer
└── SceneViewer.tsx        # Three.js Canvas
hooks/
├── useSecret.ts               # Secret key state management
├── useFolderEntries.ts        # Folder entries state management
└── useUploadOrchestrator.ts   # Upload pipeline orchestration (Drive → Git)
lib/
├── constants.ts           # Shared constants, destinations, extensions
├── types.ts               # Server types (ParsedFile, FileDiff, etc.)
├── client-types.ts        # Client types (FolderEntry, DriveStatus, etc.)
├── upload-api.ts          # Client-side API helpers (check, uploadDrive, uploadGit)
├── diff-files.ts          # File diff classification (new/changed/unchanged/deleted)
├── sanitize.ts            # Filename sanitization
├── auth.ts                # Upload secret validation (timing-safe)
├── github.ts              # Octokit helpers (getRemoteFolder, pushAllToGitHub)
├── nextcloud.ts           # Nextcloud WebDAV client (native fetch, cached config)
├── blender.ts             # Blender Draco compression
├── commit-message.ts      # Commit message builder
├── parse-upload.ts        # FormData parser + validation
├── validate-folder.ts     # Client-side folder validation (discriminated union)
└── format-bytes.ts        # Byte formatting utility
scripts/
└── compress.py            # Blender Draco compression script
Dockerfile                 # Multi-stage build: Node 20 slim + Blender headless + tini
docker-entrypoint.sh       # Startup: Blender check + launch

Supported Formats

Type Extensions
3D Models .glb, .gltf
Textures .png, .jpg, .jpeg, .webp

License

MIT