refactor: stage uploads before drive and git delivery

This commit is contained in:
Tom Boullay
2026-04-24 17:41:38 +02:00
parent 53c4c0ed60
commit 71bcf2b36d
8 changed files with 405 additions and 68 deletions
+55 -20
View File
@@ -1,9 +1,9 @@
# upload-GLTF
A secure web interface for uploading 3D assets (GLTF/GLB + textures) with two outputs:
A secure web interface for uploading `model.glb` and its associated textures with two outputs:
- **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.
- **GitHub** — Delivers compressed models (Draco via Blender) and compressed textures to the dev team's repository, ready for integration.
Built for La Fabrik Durable.
@@ -68,6 +68,8 @@ 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.
>
> Local 3D preview is currently supported for `.glb` files only.
### Production (Coolify / Docker)
@@ -90,17 +92,19 @@ The Docker image includes Blender headless (installed once at build time). On st
2. They select a folder containing:
- `model.glb` (**required**)
- Any associated textures (`.png/.jpg/.jpeg/.webp`)
4. The model is displayed in a 3D preview
3. The `.glb` model is displayed in a local 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)
- The app uploads the folder once to a temporary server-side staging area
- The app prepares the final Git payload from this staging area
- The app checks the remote Git repo for existing files and computes diffs
- 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
6. **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.
7. **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.
6. **Drive upload (archiving)** — Original files from the staging area 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.
7. **Git upload (delivery to devs)**The prepared Git payload is reused from staging: models are compressed with Blender Draco, textures are compressed server-side, 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)
@@ -124,9 +128,15 @@ Models/
All files are uploaded to `VF/` (not just diffs), because the move operation empties the previous folder.
### Upload safeguards
- The upload flow prevents duplicate submissions on the client (`Envoyer`, overwrite confirmation, and "Git only" confirmation are locked while processing)
- The server applies a lightweight per-folder lock on Drive and Git routes to avoid duplicate commits and concurrent writes
- The folder is staged server-side so the browser sends the payload only once during the full upload flow
### Commit messages
All changes are pushed in a **single commit** with a formatted message:
All changes are pushed in a **single commit** with a grouped formatted message:
**New folder:**
```
@@ -134,8 +144,14 @@ update: upload-gltf add a new model -> my-model
📦 Model
✅ model.glb (compressed)
🎨 Textures
✅ color.jpg
🎨 Textures (color)
✅ color_porte.jpg (compressed)
🪶 Textures (roughness)
✅ roughness_tuyaux.png (compressed)
🧩 Assets
✅ opacity_fenetre.png (compressed)
```
**Update (only one texture changed):**
@@ -143,28 +159,44 @@ update: upload-gltf add a new model -> my-model
update: upload-gltf update -> coffeetest
📦 Model
↔️ model.glb (inchange)
🎨 Textures
🔄 color_tuyaux.jpg
↔️ model.glb (compressed)
🎨 Textures (color)
🔄 color_tuyaux.jpg (compressed)
```
Sections currently used:
- `📦 Model`
- `🎨 Textures (color)`
- `🪶 Textures (roughness)`
- `🧭 Textures (normal)`
- `🔩 Textures (metalness)`
- `🧩 Assets`
- `🗑 Deleted`
Symbols: `✅` new — `🔄` modified — `↔️` unchanged (model always re-pushed) — `❌` deleted
8. Orphan files (present on remote but not in the new upload) are deleted in the same commit
9. If Blender is unavailable, the original model is pushed as-is (graceful fallback)
## Destinations
Uploaded models are pushed to `public/models/<folderName>/` in the target repo.
## Current Limitations
- Large uploads are faster than before because the folder is staged only once, but the Drive upload remains sequential.
- Git LFS uploads are still sequential.
- The current upload contract still expects a single `model.glb` file and a flat texture set.
## 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
│ ├── stage/route.ts # POST: upload folder once to temporary staging
│ ├── check/route.ts # POST: prepare staged Git assets and compare with remote files
── drive/route.ts # POST: upload staged originals to Nextcloud Drive (VF/Vx versioning)
│ └── git/route.ts # POST: push staged prepared assets to GitHub
├── globals.css # Tailwind + CSS variable fonts
├── layout.tsx # Root layout (next/font/google)
└── page.tsx # Home page
@@ -191,14 +223,17 @@ hooks/
└── useUploadOrchestrator.ts # Upload pipeline orchestration (Drive → Git)
lib/
├── constants.ts # Shared constants and extensions
├── types.ts # Server types (ParsedFile, FileDiff, etc.)
├── types.ts # Server types (ParsedFile, FileDiff, staged asset metadata, etc.)
├── client-types.ts # Client types (FolderEntry, DriveStatus, etc.)
├── upload-api.ts # Client-side API helpers (check, uploadDrive, uploadGit)
├── upload-api.ts # Client-side API helpers (stage, 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)
├── upload-staging.ts # Temporary server-side staging and prepared asset reuse
├── upload-lock.ts # Lightweight in-memory per-folder upload lock
├── asset-classification.ts # Group assets by family for commit messages
├── blender.ts # Blender Draco compression
├── commit-message.ts # Commit message builder
├── parse-upload.ts # FormData parser + validation
@@ -214,7 +249,7 @@ docker-entrypoint.sh # Startup: Blender check + launch
| Type | Extensions |
|------|------------|
| 3D Models | `.glb`, `.gltf` |
| 3D Models | `.glb` |
| Textures | `.png`, `.jpg`, `.jpeg`, `.webp` |
## License