261 lines
10 KiB
Markdown
261 lines
10 KiB
Markdown
# upload-GLTF
|
|
|
|
A secure web interface for uploading `model.gltf` with its associated `.bin` file and 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 GLTF assets and compressed textures 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
|
|
- **Sharp** for server-side texture compression
|
|
- **Coolify** (Docker) for hosting
|
|
|
|
## Installation
|
|
|
|
```bash
|
|
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:
|
|
|
|
```env
|
|
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
|
|
|
|
# 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 |
|
|
| `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
|
|
|
|
```bash
|
|
npm run dev
|
|
```
|
|
|
|
Access the app at `http://localhost:3000`
|
|
|
|
> **Note:** The current upload contract accepts `model.gltf` and preserves it as GLTF. `.glb` uploads are rejected by validation.
|
|
>
|
|
> Local 3D preview supports `model.gltf` folders by resolving dropped companion files such as `model.bin` and textures through local object URLs.
|
|
> The preview also shows a small model stats helper with estimated draw calls, meshes, triangles, materials, and texture count.
|
|
|
|
### Production (Coolify / Docker)
|
|
|
|
```bash
|
|
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 runs the Next.js app and server-side asset preparation in a single container.
|
|
|
|
## How it works
|
|
|
|
1. The user enters their access key
|
|
2. They select a folder containing:
|
|
- `model.gltf` (**required**)
|
|
- Any associated binary buffer (`.bin`, for example `model.bin`)
|
|
- Any associated textures (`.png/.jpg/.jpeg/.webp`)
|
|
3. The folder is validated locally. `.glb` files are not accepted.
|
|
4. On clicking "Envoyer":
|
|
- 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
|
|
|
|
5. **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.
|
|
6. **Git upload (delivery to devs)** — The prepared Git payload is reused from staging: `model.gltf` and `.bin` files are preserved, 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)
|
|
|
|
The Drive uses a `VF` (version finale) / `Vx` (archived versions) structure:
|
|
|
|
```
|
|
Models/
|
|
VF/ ← latest version
|
|
coffeetest/
|
|
model.gltf
|
|
model.bin
|
|
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.
|
|
|
|
### 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 grouped formatted message:
|
|
|
|
**New folder:**
|
|
```
|
|
update: upload-gltf add a new model -> my-model
|
|
|
|
📦 Model
|
|
✅ model.gltf
|
|
🎨 Textures (color)
|
|
✅ color_porte.jpg (compressed)
|
|
|
|
🪶 Textures (roughness)
|
|
✅ roughness_tuyaux.png (compressed)
|
|
|
|
🧩 Assets
|
|
✅ model.bin
|
|
✅ opacity_fenetre.png (compressed)
|
|
```
|
|
|
|
**Update (only one texture changed):**
|
|
```
|
|
update: upload-gltf update -> coffeetest
|
|
|
|
📦 Model
|
|
↔️ model.gltf
|
|
|
|
🎨 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
|
|
|
|
7. Orphan files (present on remote but not in the new upload) are deleted in the same commit
|
|
8. `model.gltf` is pushed as-is so companion files like `model.bin` remain valid
|
|
|
|
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 expects a single `model.gltf` file plus optional flat support files (`.bin`, `.png`, `.jpg`, `.jpeg`, `.webp`).
|
|
|
|
## Project Structure
|
|
|
|
```
|
|
app/
|
|
├── api/upload/
|
|
│ ├── 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
|
|
components/
|
|
├── ui/
|
|
│ ├── icons.tsx # Shared SVG icon components
|
|
│ └── Modal.tsx # Shared modal wrapper + ModalActions
|
|
├── upload/
|
|
│ ├── SecretInput.tsx # Access key input
|
|
│ ├── 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 and extensions
|
|
├── 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 (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 # Legacy Blender compression helper
|
|
├── 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 # Legacy Blender compression script
|
|
Dockerfile # Multi-stage build: Node 20 slim + tini
|
|
docker-entrypoint.sh # Startup check + launch
|
|
```
|
|
|
|
## Supported Formats
|
|
|
|
| Type | Extensions |
|
|
|------|------------|
|
|
| 3D Models | `.gltf` |
|
|
| Binary buffers | `.bin` |
|
|
| Textures | `.png`, `.jpg`, `.jpeg`, `.webp` |
|
|
|
|
## License
|
|
|
|
MIT
|