mirror of
https://github.com/snapotter-hq/SnapOtter.git
synced 2026-08-03 07:46:42 +02:00
docs: comprehensive API sync and documentation audit
- Rewrite OpenAPI spec to match actual code (988 lines changed):
- Fix ToolResponse schema (add previewUrl, savedFileId)
- Fix Error schema shape ({error, details} not {statusCode, error, message})
- Fix POST /api/auth/register URL (was /api/auth/users)
- Fix login/session responses (7 missing user fields + expiresAt)
- Fix 8 endpoints returning 204 → 200 with {ok: true}
- Fix pipeline execute field name (steps → pipeline)
- Fix API keys response key (keys → apiKeys)
- Fix settings response wrapper, teams UUID type
- Rewrite 7 major tool response schemas (info, barcode-read,
find-duplicates, compare, remove-background, upscale, ocr, blur-faces)
- Fix files/save-result (JSON → multipart), files/upload (201 + array)
- Fix SSE progress schema (integers not arrays)
- Add 422/501 error responses to AI and processing tools
- Fix settings required → optional on 29 tool endpoints
- Add 5 missing color adjustment fields to alias endpoints
- Rewrite rest.md tool parameter descriptions (12 tools fixed)
- Add Tool Sub-Routes section to rest.md (11 endpoints)
- Fix file library, settings, pipeline, auth docs in rest.md
- Fix API key hashing description (SHA-256 → scrypt)
- Fix "GitHub Pages" → "Cloudflare Pages" in architecture + deployment docs
- Fix tool count "45+" → "47" across all doc surfaces
- Fix branding endpoint paths in rest.md (/branding/logo → /settings/logo)
This commit is contained in:
@@ -25,9 +25,9 @@
|
|||||||
## Quick Start
|
## Quick Start
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
docker run -d --name SnapOtter -p 1349:1349 -v SnapOtter-data:/data snapotter/snapotter:latest
|
docker run -d --name SnapOtter -p 1349:1349 -v SnapOtter-data:/data snapotter/snapotter:latest \
|
||||||
|
&& printf "\n 🦦 SnapOtter\n ────────────────────────────────────────\n\n ➜ Open http://localhost:1349\n ➜ Login admin / admin\n ➜ Docs https://docs.snapotter.com\n\n"
|
||||||
```
|
```
|
||||||
Open http://localhost:1349 in your browser.
|
|
||||||
|
|
||||||
<details>
|
<details>
|
||||||
<summary><sub>Have an NVIDIA GPU? Click here for GPU acceleration.</sub></summary>
|
<summary><sub>Have an NVIDIA GPU? Click here for GPU acceleration.</sub></summary>
|
||||||
@@ -36,7 +36,8 @@ Open http://localhost:1349 in your browser.
|
|||||||
Add `--gpus all` for GPU-accelerated background removal, upscaling, and OCR:
|
Add `--gpus all` for GPU-accelerated background removal, upscaling, and OCR:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
docker run -d --name SnapOtter -p 1349:1349 --gpus all -v SnapOtter-data:/data snapotter/snapotter:latest
|
docker run -d --name SnapOtter -p 1349:1349 --gpus all -v SnapOtter-data:/data snapotter/snapotter:latest \
|
||||||
|
&& printf "\n 🦦 SnapOtter\n ────────────────────────────────────────\n\n ➜ Open http://localhost:1349\n ➜ Login admin / admin\n ➜ Docs https://docs.snapotter.com\n\n"
|
||||||
```
|
```
|
||||||
|
|
||||||
> Requires an NVIDIA GPU and [Container Toolkit](https://docs.nvidia.com/datacenter/cloud-native/container-toolkit/latest/install-guide.html). Falls back to CPU if no GPU is found. See [Docker Tags](https://docs.snapotter.com/guide/docker-tags) for benchmarks and Docker Compose examples.
|
> Requires an NVIDIA GPU and [Container Toolkit](https://docs.nvidia.com/datacenter/cloud-native/container-toolkit/latest/install-guide.html). Falls back to CPU if no GPU is found. See [Docker Tags](https://docs.snapotter.com/guide/docker-tags) for benchmarks and Docker Compose examples.
|
||||||
|
|||||||
@@ -67,7 +67,7 @@ export interface ValidationError {
|
|||||||
const RAW_EXTENSIONS = new Set(["dng", "cr2", "nef", "arw", "orf", "rw2"]);
|
const RAW_EXTENSIONS = new Set(["dng", "cr2", "nef", "arw", "orf", "rw2"]);
|
||||||
|
|
||||||
/** Formats that Sharp cannot decode natively — skip dimension check. */
|
/** Formats that Sharp cannot decode natively — skip dimension check. */
|
||||||
const CLI_DECODED_FORMATS = new Set(["raw", "ico", "tga", "psd", "exr", "hdr"]);
|
const CLI_DECODED_FORMATS = new Set(["raw", "ico", "tga", "psd", "exr", "hdr", "bmp", "jxl"]);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Check whether a file extension corresponds to a Camera RAW format.
|
* Check whether a file extension corresponds to a Camera RAW format.
|
||||||
|
|||||||
+761
-217
File diff suppressed because it is too large
Load Diff
+51
-27
@@ -42,7 +42,7 @@ curl http://localhost:1349/api/v1/tools/resize \
|
|||||||
-H "Authorization: Bearer si_<your-key>"
|
-H "Authorization: Bearer si_<your-key>"
|
||||||
```
|
```
|
||||||
|
|
||||||
Keys are prefixed `si_` and stored as SHA-256 hashes - the raw key is shown once and never retrievable again.
|
Keys are prefixed `si_` and stored as scrypt hashes - the raw key is shown once and never retrievable again.
|
||||||
|
|
||||||
### Auth Endpoints
|
### Auth Endpoints
|
||||||
|
|
||||||
@@ -57,6 +57,7 @@ Keys are prefixed `si_` and stored as SHA-256 hashes - the raw key is shown once
|
|||||||
| `PUT` | `/api/auth/users/:id` | Admin | Update user role or team |
|
| `PUT` | `/api/auth/users/:id` | Admin | Update user role or team |
|
||||||
| `POST` | `/api/auth/users/:id/reset-password` | Admin | Reset user's password |
|
| `POST` | `/api/auth/users/:id/reset-password` | Admin | Reset user's password |
|
||||||
| `DELETE` | `/api/auth/users/:id` | Admin | Delete a user |
|
| `DELETE` | `/api/auth/users/:id` | Admin | Delete a user |
|
||||||
|
| `GET` | `/api/v1/config/auth` | Public | Check if authentication is enabled (`{ authEnabled: bool }`) |
|
||||||
|
|
||||||
### Permissions
|
### Permissions
|
||||||
|
|
||||||
@@ -107,16 +108,16 @@ curl -X POST http://localhost:1349/api/v1/tools/<toolId>/batch \
|
|||||||
| Tool ID | Name | Key settings |
|
| Tool ID | Name | Key settings |
|
||||||
|---------|------|-------------|
|
|---------|------|-------------|
|
||||||
| `resize` | Resize | `width`, `height`, `fit` (cover/contain/fill/inside/outside), `percentage`, `withoutEnlargement`, plus 23 social media presets |
|
| `resize` | Resize | `width`, `height`, `fit` (cover/contain/fill/inside/outside), `percentage`, `withoutEnlargement`, plus 23 social media presets |
|
||||||
| `crop` | Crop | `left`, `top`, `width`, `height`, `aspectRatio`, `shape` (rectangle/circle/rounded) |
|
| `crop` | Crop | `left`, `top`, `width`, `height`, `unit` (px/percent) |
|
||||||
| `rotate` | Rotate & Flip | `angle`, `flip` (horizontal/vertical/both), `background` |
|
| `rotate` | Rotate & Flip | `angle`, `horizontal` (bool), `vertical` (bool) |
|
||||||
| `convert` | Convert | `format` (jpeg/png/webp/avif/tiff/gif/heif), `quality` |
|
| `convert` | Convert | `format` (jpg/png/webp/avif/tiff/gif/heic/heif), `quality` |
|
||||||
| `compress` | Compress | `quality` (1–100), `format`, `targetSizeKB` |
|
| `compress` | Compress | `mode` (quality/targetSize), `quality` (1–100), `targetSizeKb` |
|
||||||
|
|
||||||
### Optimization
|
### Optimization
|
||||||
|
|
||||||
| Tool ID | Name | Key settings |
|
| Tool ID | Name | Key settings |
|
||||||
|---------|------|-------------|
|
|---------|------|-------------|
|
||||||
| `optimize-for-web` | Optimize for Web | `format` (auto/jpeg/webp/avif), `quality`, `maxWidthPx`, `stripMetadata` |
|
| `optimize-for-web` | Optimize for Web | `format` (webp/jpeg/avif/png), `quality`, `maxWidth`, `maxHeight`, `progressive`, `stripMetadata` |
|
||||||
| `strip-metadata` | Strip Metadata | - |
|
| `strip-metadata` | Strip Metadata | - |
|
||||||
| `edit-metadata` | Edit Metadata | `title`, `description`, `author`, `copyright`, `keywords`, `gps` (lat/lon), `dateTime` |
|
| `edit-metadata` | Edit Metadata | `title`, `description`, `author`, `copyright`, `keywords`, `gps` (lat/lon), `dateTime` |
|
||||||
| `bulk-rename` | Bulk Rename | `pattern` (supports `{n}`, `{date}`, `{original}`), `startIndex`, `padding` |
|
| `bulk-rename` | Bulk Rename | `pattern` (supports `{n}`, `{date}`, `{original}`), `startIndex`, `padding` |
|
||||||
@@ -127,9 +128,9 @@ curl -X POST http://localhost:1349/api/v1/tools/<toolId>/batch \
|
|||||||
|
|
||||||
| Tool ID | Name | Key settings |
|
| Tool ID | Name | Key settings |
|
||||||
|---------|------|-------------|
|
|---------|------|-------------|
|
||||||
| `adjust-colors` | Adjust Colors | `brightness`, `contrast`, `exposure`, `saturation`, `temperature`, `sharpness`, `vibrance`, effects (grayscale/sepia/invert/vignette) |
|
| `adjust-colors` | Adjust Colors | `brightness`, `contrast`, `exposure`, `saturation`, `temperature`, `tint`, `hue`, `sharpness`, `red`, `green`, `blue`, `effect` (none/grayscale/sepia/invert) |
|
||||||
| `sharpening` | Sharpening | `mode` (adaptive/unsharp/highpass), `amount`, `radius`, `threshold` |
|
| `sharpening` | Sharpening | `method` (adaptive/unsharp-mask/high-pass), `sigma`, `m1`, `m2`, `x1`, `y2`, `y3`, `amount`, `radius`, `threshold`, `strength`, `kernelSize` (3/5), `denoise` (off/light/medium/strong) |
|
||||||
| `replace-color` | Replace Color | `targetColor`, `replacementColor`, `tolerance`, `invert` |
|
| `replace-color` | Replace Color | `sourceColor`, `targetColor` (replacement), `makeTransparent`, `tolerance` |
|
||||||
|
|
||||||
### AI Tools
|
### AI Tools
|
||||||
|
|
||||||
@@ -137,16 +138,16 @@ All AI tools run on your hardware (CPU or NVIDIA GPU). No internet required.
|
|||||||
|
|
||||||
| Tool ID | Name | AI Model | Key settings |
|
| Tool ID | Name | AI Model | Key settings |
|
||||||
|---------|------|---------|-------------|
|
|---------|------|---------|-------------|
|
||||||
| `remove-background` | Remove Background | rembg (BiRefNet / U2-Net) | `model`, `alphaMattingForeground`, `alphaMattingBackground`, `returnMask`, background color/image |
|
| `remove-background` | Remove Background | rembg (BiRefNet / U2-Net) | `model`, `backgroundType` (transparent/color/gradient/blur/image), `backgroundColor`, `gradientColor1`, `gradientColor2`, `gradientAngle`, `blurEnabled`, `blurIntensity`, `shadowEnabled`, `shadowOpacity` |
|
||||||
| `upscale` | Image Upscaling | RealESRGAN | `scale` (2/4), `model`, `faceEnhance`, `denoise`, `format`, `quality` |
|
| `upscale` | Image Upscaling | RealESRGAN | `scale` (2/4), `model`, `faceEnhance`, `denoise`, `format`, `quality` |
|
||||||
| `erase-object` | Object Eraser | LaMa (ONNX) | `maskData` (base64 PNG), `maskThreshold` |
|
| `erase-object` | Object Eraser | LaMa (ONNX) | Mask sent as second file part (fieldname `mask`), `format`, `quality` |
|
||||||
| `ocr` | OCR / Text Extraction | PaddleOCR / Tesseract | `quality` (fast/balanced/best), `language`, `enhance` |
|
| `ocr` | OCR / Text Extraction | PaddleOCR / Tesseract | `quality` (fast/balanced/best), `language`, `enhance` |
|
||||||
| `blur-faces` | Face / PII Blur | MediaPipe | `blurRadius`, `sensitivity` |
|
| `blur-faces` | Face / PII Blur | MediaPipe | `blurRadius`, `sensitivity` |
|
||||||
| `smart-crop` | Smart Crop | MediaPipe + Sharp | `mode` (subject/face/trim), `width`, `height`, `facePreset` (close-up/head-and-shoulders/upper-body/half-body) |
|
| `smart-crop` | Smart Crop | MediaPipe + Sharp | `mode` (subject/face/trim), `strategy` (attention/entropy), `width`, `height`, `padding`, `facePreset` (closeup/head-shoulders/upper-body/half-body), `sensitivity`, `threshold`, `padToSquare`, `padColor`, `targetSize`, `quality` |
|
||||||
| `image-enhancement` | Image Enhancement | Analysis-based | `mode` (auto/exposure/contrast/color/sharpness), `strength` |
|
| `image-enhancement` | Image Enhancement | Analysis-based | `mode` (auto/exposure/contrast/color/sharpness), `strength` |
|
||||||
| `enhance-faces` | Face Enhancement | GFPGAN / CodeFormer | `model` (gfpgan/codeformer), `strength`, `sensitivity`, `centerFace` |
|
| `enhance-faces` | Face Enhancement | GFPGAN / CodeFormer | `model` (gfpgan/codeformer), `strength`, `sensitivity`, `centerFace` |
|
||||||
| `colorize` | AI Colorization | DDColor | `intensity`, `model` |
|
| `colorize` | AI Colorization | DDColor | `intensity`, `model` |
|
||||||
| `noise-removal` | Noise Removal | Tiered denoising | `quality` (fast/balanced/best), `strength`, `preserveDetail`, `colorNoise` |
|
| `noise-removal` | Noise Removal | Tiered denoising | `tier` (quick/balanced/quality/maximum), `strength`, `detailPreservation`, `colorNoise`, `format`, `quality` |
|
||||||
| `red-eye-removal` | Red Eye Removal | Face landmark + color analysis | `sensitivity`, `strength` |
|
| `red-eye-removal` | Red Eye Removal | Face landmark + color analysis | `sensitivity`, `strength` |
|
||||||
| `restore-photo` | Photo Restoration | Multi-step pipeline | `mode` (auto/light/heavy), `scratchRemoval`, `faceEnhancement`, `fidelity`, `denoise`, `denoiseStrength`, `colorize` |
|
| `restore-photo` | Photo Restoration | Multi-step pipeline | `mode` (auto/light/heavy), `scratchRemoval`, `faceEnhancement`, `fidelity`, `denoise`, `denoiseStrength`, `colorize` |
|
||||||
| `passport-photo` | Passport Photo | MediaPipe landmarks | `country` (37 countries), `printLayout` (4x6/A4/none), `backgroundColor` |
|
| `passport-photo` | Passport Photo | MediaPipe landmarks | `country` (37 countries), `printLayout` (4x6/A4/none), `backgroundColor` |
|
||||||
@@ -191,6 +192,24 @@ All AI tools run on your hardware (CPU or NVIDIA GPU). No internet required.
|
|||||||
| `gif-tools` | GIF Tools | `action` (resize/optimize/reverse/speed/extract-frames/rotate/add-text), action-specific params |
|
| `gif-tools` | GIF Tools | `action` (resize/optimize/reverse/speed/extract-frames/rotate/add-text), action-specific params |
|
||||||
| `pdf-to-image` | PDF to Image | `pages` (all/range), `format`, `dpi`, `quality` |
|
| `pdf-to-image` | PDF to Image | `pages` (all/range), `format`, `dpi`, `quality` |
|
||||||
|
|
||||||
|
### Tool Sub-Routes
|
||||||
|
|
||||||
|
Some tools expose additional endpoints beyond the standard `POST /api/v1/tools/<toolId>`:
|
||||||
|
|
||||||
|
| Method | Path | Description |
|
||||||
|
|--------|------|-------------|
|
||||||
|
| `POST` | `/api/v1/tools/remove-background/effects` | Apply background effects (color/gradient/blur/shadow) without re-running AI. Uses cached mask from initial removal. |
|
||||||
|
| `POST` | `/api/v1/tools/edit-metadata/inspect` | Read existing EXIF/IPTC/XMP metadata from an image |
|
||||||
|
| `POST` | `/api/v1/tools/strip-metadata/inspect` | Inspect metadata fields before stripping |
|
||||||
|
| `POST` | `/api/v1/tools/passport-photo/analyze` | Phase 1: AI face detection + background removal. Returns face landmarks and cached data. |
|
||||||
|
| `POST` | `/api/v1/tools/passport-photo/generate` | Phase 2: Crop, resize, and tile using cached analysis. No AI re-run. |
|
||||||
|
| `POST` | `/api/v1/tools/gif-tools/info` | Get GIF metadata (frame count, dimensions, duration) |
|
||||||
|
| `POST` | `/api/v1/tools/pdf-to-image/info` | Get PDF metadata (page count, dimensions) |
|
||||||
|
| `POST` | `/api/v1/tools/pdf-to-image/preview` | Generate a preview of a specific PDF page |
|
||||||
|
| `POST` | `/api/v1/tools/svg-to-raster/batch` | Batch convert multiple SVGs to raster |
|
||||||
|
| `POST` | `/api/v1/tools/image-enhancement/analyze` | Analyze image quality and return enhancement recommendations |
|
||||||
|
| `POST` | `/api/v1/tools/optimize-for-web/preview` | Lightweight preview for live parameter tuning. Returns optimized image with size headers. |
|
||||||
|
|
||||||
## Batch Processing
|
## Batch Processing
|
||||||
|
|
||||||
Apply any tool to multiple files at once. Returns a ZIP archive.
|
Apply any tool to multiple files at once. Returns a ZIP archive.
|
||||||
@@ -215,18 +234,18 @@ Concurrency is controlled by `CONCURRENT_JOBS` (default: auto-detected from CPU
|
|||||||
curl -X POST http://localhost:1349/api/v1/pipeline/execute \
|
curl -X POST http://localhost:1349/api/v1/pipeline/execute \
|
||||||
-H "Authorization: Bearer <token>" \
|
-H "Authorization: Bearer <token>" \
|
||||||
-F "file=@input.jpg" \
|
-F "file=@input.jpg" \
|
||||||
-F 'pipeline=[
|
-F 'pipeline={"steps":[
|
||||||
{"toolId":"resize","settings":{"width":1200}},
|
{"toolId":"resize","settings":{"width":1200}},
|
||||||
{"toolId":"compress","settings":{"quality":80}},
|
{"toolId":"compress","settings":{"quality":80}},
|
||||||
{"toolId":"watermark-text","settings":{"text":"© 2025"}}
|
{"toolId":"watermark-text","settings":{"text":"© 2025"}}
|
||||||
]'
|
]}'
|
||||||
|
|
||||||
# Batch (multiple files → ZIP)
|
# Batch (multiple files → ZIP)
|
||||||
curl -X POST http://localhost:1349/api/v1/pipeline/batch \
|
curl -X POST http://localhost:1349/api/v1/pipeline/batch \
|
||||||
-H "Authorization: Bearer <token>" \
|
-H "Authorization: Bearer <token>" \
|
||||||
-F "files=@a.jpg" \
|
-F "files=@a.jpg" \
|
||||||
-F "files=@b.jpg" \
|
-F "files=@b.jpg" \
|
||||||
-F 'pipeline=[{"toolId":"resize","settings":{"width":800}}]'
|
-F 'pipeline={"steps":[{"toolId":"resize","settings":{"width":800}}]}'
|
||||||
```
|
```
|
||||||
|
|
||||||
Each step's output is the next step's input. Unlimited steps per pipeline by default (configurable via `MAX_PIPELINE_STEPS`).
|
Each step's output is the next step's input. Unlimited steps per pipeline by default (configurable via `MAX_PIPELINE_STEPS`).
|
||||||
@@ -235,7 +254,7 @@ Each step's output is the next step's input. Unlimited steps per pipeline by def
|
|||||||
|
|
||||||
| Method | Path | Description |
|
| Method | Path | Description |
|
||||||
|--------|------|-------------|
|
|--------|------|-------------|
|
||||||
| `POST` | `/api/v1/pipeline/save` | Save a named pipeline (`name`, `description`, `steps`) |
|
| `POST` | `/api/v1/pipeline/save` | Save a named pipeline (`name`, `description`, `steps[]`) |
|
||||||
| `GET` | `/api/v1/pipeline/list` | List saved pipelines (admins see all; users see own) |
|
| `GET` | `/api/v1/pipeline/list` | List saved pipelines (admins see all; users see own) |
|
||||||
| `DELETE` | `/api/v1/pipeline/:id` | Delete (owner or admin) |
|
| `DELETE` | `/api/v1/pipeline/:id` | Delete (owner or admin) |
|
||||||
| `GET` | `/api/v1/pipeline/tools` | List tool IDs valid for pipeline steps |
|
| `GET` | `/api/v1/pipeline/tools` | List tool IDs valid for pipeline steps |
|
||||||
@@ -262,12 +281,16 @@ Persistent file storage with version history.
|
|||||||
|
|
||||||
| Method | Path | Description |
|
| Method | Path | Description |
|
||||||
|--------|------|-------------|
|
|--------|------|-------------|
|
||||||
| `POST` | `/api/v1/upload` | Upload files to workspace |
|
| `POST` | `/api/v1/upload` | Upload files to workspace (temp processing) |
|
||||||
|
| `POST` | `/api/v1/files/upload` | Upload files to the persistent file library |
|
||||||
|
| `POST` | `/api/v1/files/save-result` | Save a tool processing result as a new file version |
|
||||||
| `GET` | `/api/v1/files` | List saved files (paginated, with search) |
|
| `GET` | `/api/v1/files` | List saved files (paginated, with search) |
|
||||||
| `GET` | `/api/v1/files/:id` | Get file metadata + version chain |
|
| `GET` | `/api/v1/files/:id` | Get file metadata + version chain |
|
||||||
| `GET` | `/api/v1/files/:id/download` | Download file |
|
| `GET` | `/api/v1/files/:id/download` | Download file |
|
||||||
| `GET` | `/api/v1/files/:id/thumbnail` | Get 300px JPEG thumbnail |
|
| `GET` | `/api/v1/files/:id/thumbnail` | Get 300px JPEG thumbnail |
|
||||||
| `DELETE` | `/api/v1/files/:id` | Delete file (and its version chain) |
|
| `DELETE` | `/api/v1/files` | Bulk delete files and their version chains (body: `{ ids: [...] }`) |
|
||||||
|
| `POST` | `/api/v1/preview` | Generate a browser-compatible WebP preview (for HEIC/HEIF/RAW formats) |
|
||||||
|
| `GET` | `/api/v1/download/:jobId/:filename` | Download a processed file from a workspace |
|
||||||
|
|
||||||
To auto-save a tool result to the library, include `fileId` in the settings payload referencing an existing library file. The processed result will be saved as a new version.
|
To auto-save a tool result to the library, include `fileId` in the settings payload referencing an existing library file. The processed result will be saved as a new version.
|
||||||
|
|
||||||
@@ -283,18 +306,18 @@ To auto-save a tool result to the library, include `fileId` in the settings payl
|
|||||||
|
|
||||||
| Method | Path | Access | Description |
|
| Method | Path | Access | Description |
|
||||||
|--------|------|--------|-------------|
|
|--------|------|--------|-------------|
|
||||||
| `GET` | `/api/v1/teams` | Auth | List teams |
|
| `GET` | `/api/v1/teams` | Admin (`teams:manage`) | List teams |
|
||||||
| `POST` | `/api/v1/teams` | Admin | Create team |
|
| `POST` | `/api/v1/teams` | Admin (`teams:manage`) | Create team |
|
||||||
| `PUT` | `/api/v1/teams/:id` | Admin | Rename team |
|
| `PUT` | `/api/v1/teams/:id` | Admin (`teams:manage`) | Rename team |
|
||||||
| `DELETE` | `/api/v1/teams/:id` | Admin | Delete team (cannot delete default team or teams with members) |
|
| `DELETE` | `/api/v1/teams/:id` | Admin (`teams:manage`) | Delete team (cannot delete default team or teams with members) |
|
||||||
|
|
||||||
## Branding
|
## Branding
|
||||||
|
|
||||||
| Method | Path | Access | Description |
|
| Method | Path | Access | Description |
|
||||||
|--------|------|--------|-------------|
|
|--------|------|--------|-------------|
|
||||||
| `POST` | `/api/v1/branding/logo` | Admin | Upload custom logo (max 500 KB, converted to 128×128 PNG) |
|
| `POST` | `/api/v1/settings/logo` | Admin | Upload custom logo (max 500 KB, converted to 128×128 PNG) |
|
||||||
| `GET` | `/api/v1/branding/logo` | Public | Serve current logo |
|
| `GET` | `/api/v1/settings/logo` | Public | Serve current logo |
|
||||||
| `DELETE` | `/api/v1/branding/logo` | Admin | Remove custom logo |
|
| `DELETE` | `/api/v1/settings/logo` | Admin | Remove custom logo |
|
||||||
|
|
||||||
## Settings
|
## Settings
|
||||||
|
|
||||||
@@ -303,7 +326,8 @@ Runtime key-value configuration (read by any authenticated user, write by admin
|
|||||||
| Method | Path | Description |
|
| Method | Path | Description |
|
||||||
|--------|------|-------------|
|
|--------|------|-------------|
|
||||||
| `GET` | `/api/v1/settings` | Get all settings |
|
| `GET` | `/api/v1/settings` | Get all settings |
|
||||||
| `PUT` | `/api/v1/settings/:key` | Set a value |
|
| `PUT` | `/api/v1/settings` | Bulk update settings (JSON body with key-value pairs) |
|
||||||
|
| `GET` | `/api/v1/settings/:key` | Get a specific setting by key |
|
||||||
|
|
||||||
Known keys: `disabledTools` (JSON array of tool IDs), `enableExperimentalTools` (bool string), `loginAttemptLimit` (number), `customLogo` (managed via branding endpoint).
|
Known keys: `disabledTools` (JSON array of tool IDs), `enableExperimentalTools` (bool string), `loginAttemptLimit` (number), `customLogo` (managed via branding endpoint).
|
||||||
|
|
||||||
|
|||||||
@@ -70,7 +70,7 @@ The built frontend gets served by the Fastify backend in production, so there is
|
|||||||
|
|
||||||
### Docs (`apps/docs`)
|
### Docs (`apps/docs`)
|
||||||
|
|
||||||
This VitePress site. Deployed to GitHub Pages automatically on push to `main`.
|
This VitePress site. Deployed to Cloudflare Pages automatically on push to `main`.
|
||||||
|
|
||||||
## How a request flows
|
## How a request flows
|
||||||
|
|
||||||
|
|||||||
@@ -393,7 +393,7 @@ The GitHub repository has three workflows:
|
|||||||
|
|
||||||
- **ci.yml** -- Runs automatically on every push and PR. Lints, typechecks, tests, builds, and validates the Docker image (without pushing).
|
- **ci.yml** -- Runs automatically on every push and PR. Lints, typechecks, tests, builds, and validates the Docker image (without pushing).
|
||||||
- **release.yml** -- Triggered manually via `workflow_dispatch`. Runs semantic-release to create a version tag and GitHub release, then builds a multi-arch Docker image (amd64 + arm64) and pushes to Docker Hub (`snapotter/snapotter`) and GitHub Container Registry (`ghcr.io/snapotter-hq/snapotter`).
|
- **release.yml** -- Triggered manually via `workflow_dispatch`. Runs semantic-release to create a version tag and GitHub release, then builds a multi-arch Docker image (amd64 + arm64) and pushes to Docker Hub (`snapotter/snapotter`) and GitHub Container Registry (`ghcr.io/snapotter-hq/snapotter`).
|
||||||
- **deploy-docs.yml** -- Builds this documentation site and deploys it to GitHub Pages on push to `main`.
|
- **deploy-docs.yml** -- Builds this documentation site and deploys it to Cloudflare Pages on push to `main`.
|
||||||
|
|
||||||
To create a release, go to **Actions > Release > Run workflow** in the GitHub UI, or run:
|
To create a release, go to **Actions > Release > Run workflow** in the GitHub UI, or run:
|
||||||
|
|
||||||
|
|||||||
@@ -5,17 +5,17 @@ SnapOtter ships as a single Docker image that works on all platforms.
|
|||||||
## Quick start
|
## Quick start
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
docker run -d --name SnapOtter -p 1349:1349 -v SnapOtter-data:/data snapotter/snapotter:latest
|
docker run -d --name SnapOtter -p 1349:1349 -v SnapOtter-data:/data snapotter/snapotter:latest \
|
||||||
|
&& printf "\n 🦦 SnapOtter\n ────────────────────────────────────────\n\n ➜ Open http://localhost:1349\n ➜ Login admin / admin\n ➜ Docs https://docs.snapotter.com\n\n"
|
||||||
```
|
```
|
||||||
|
|
||||||
The app is available at `http://localhost:1349`.
|
|
||||||
|
|
||||||
## GPU acceleration
|
## GPU acceleration
|
||||||
|
|
||||||
The image includes CUDA support on amd64. If you have an NVIDIA GPU with the [NVIDIA Container Toolkit](https://docs.nvidia.com/datacenter/cloud-native/container-toolkit/latest/install-guide.html) installed, add `--gpus all`:
|
The image includes CUDA support on amd64. If you have an NVIDIA GPU with the [NVIDIA Container Toolkit](https://docs.nvidia.com/datacenter/cloud-native/container-toolkit/latest/install-guide.html) installed, add `--gpus all`:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
docker run -d --name SnapOtter --gpus all -p 1349:1349 -v SnapOtter-data:/data snapotter/snapotter:latest
|
docker run -d --name SnapOtter --gpus all -p 1349:1349 -v SnapOtter-data:/data snapotter/snapotter:latest \
|
||||||
|
&& printf "\n 🦦 SnapOtter\n ────────────────────────────────────────\n\n ➜ Open http://localhost:1349\n ➜ Login admin / admin\n ➜ Docs https://docs.snapotter.com\n\n"
|
||||||
```
|
```
|
||||||
|
|
||||||
The image auto-detects your GPU at runtime. Without `--gpus all`, it runs on CPU. Same image either way.
|
The image auto-detects your GPU at runtime. Without `--gpus all`, it runs on CPU. Same image either way.
|
||||||
|
|||||||
@@ -3,33 +3,27 @@
|
|||||||
## Quick Start
|
## Quick Start
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
docker run -d --name SnapOtter -p 1349:1349 -v SnapOtter-data:/data snapotter/snapotter:latest
|
docker run -d --name SnapOtter -p 1349:1349 -v SnapOtter-data:/data snapotter/snapotter:latest \
|
||||||
|
&& printf "\n 🦦 SnapOtter\n ────────────────────────────────────────\n\n ➜ Open http://localhost:1349\n ➜ Login admin / admin\n ➜ Docs https://docs.snapotter.com\n\n"
|
||||||
```
|
```
|
||||||
|
|
||||||
Open [http://localhost:1349](http://localhost:1349) in your browser.
|
You will be asked to change your password on first login.
|
||||||
|
|
||||||
::: tip Also on GHCR
|
::: tip Also on GHCR
|
||||||
```bash
|
```bash
|
||||||
docker run -d --name SnapOtter -p 1349:1349 -v SnapOtter-data:/data ghcr.io/snapotter-hq/snapotter:latest
|
docker run -d --name SnapOtter -p 1349:1349 -v SnapOtter-data:/data ghcr.io/snapotter-hq/snapotter:latest \
|
||||||
|
&& printf "\n 🦦 SnapOtter\n ────────────────────────────────────────\n\n ➜ Open http://localhost:1349\n ➜ Login admin / admin\n ➜ Docs https://docs.snapotter.com\n\n"
|
||||||
```
|
```
|
||||||
|
|
||||||
Both registries publish the same image on every release.
|
Both registries publish the same image on every release.
|
||||||
:::
|
:::
|
||||||
|
|
||||||
**Default credentials:**
|
|
||||||
|
|
||||||
| Field | Value |
|
|
||||||
|----------|---------|
|
|
||||||
| Username | `admin` |
|
|
||||||
| Password | `admin` |
|
|
||||||
|
|
||||||
You will be asked to change your password on first login.
|
|
||||||
|
|
||||||
::: tip NVIDIA GPU acceleration
|
::: tip NVIDIA GPU acceleration
|
||||||
Add `--gpus all` for GPU-accelerated background removal, upscaling, OCR, face enhancement, and restoration:
|
Add `--gpus all` for GPU-accelerated background removal, upscaling, OCR, face enhancement, and restoration:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
docker run -d --name SnapOtter -p 1349:1349 --gpus all -v SnapOtter-data:/data snapotter/snapotter:latest
|
docker run -d --name SnapOtter -p 1349:1349 --gpus all -v SnapOtter-data:/data snapotter/snapotter:latest \
|
||||||
|
&& printf "\n 🦦 SnapOtter\n ────────────────────────────────────────\n\n ➜ Open http://localhost:1349\n ➜ Login admin / admin\n ➜ Docs https://docs.snapotter.com\n\n"
|
||||||
```
|
```
|
||||||
|
|
||||||
Requires the [NVIDIA Container Toolkit](https://docs.nvidia.com/datacenter/cloud-native/container-toolkit/latest/install-guide.html). Falls back to CPU automatically. See [Docker Tags](/guide/docker-tags) for benchmarks.
|
Requires the [NVIDIA Container Toolkit](https://docs.nvidia.com/datacenter/cloud-native/container-toolkit/latest/install-guide.html). Falls back to CPU automatically. See [Docker Tags](/guide/docker-tags) for benchmarks.
|
||||||
@@ -73,7 +67,7 @@ pnpm dev
|
|||||||
|
|
||||||
## What You Can Do
|
## What You Can Do
|
||||||
|
|
||||||
### Image Processing (45+ Tools)
|
### Image Processing (47 Tools)
|
||||||
|
|
||||||
| Category | Tools |
|
| Category | Tools |
|
||||||
|----------|-------|
|
|----------|-------|
|
||||||
|
|||||||
+3
-2
@@ -14,7 +14,7 @@ hero:
|
|||||||
link: /api/rest
|
link: /api/rest
|
||||||
|
|
||||||
features:
|
features:
|
||||||
- title: 45+ Image Tools
|
- title: 47 Image Tools
|
||||||
details: Resize, crop, compress, convert, watermark, color adjust, vectorize, create GIFs, build collages, generate passport photos, find duplicates, and more.
|
details: Resize, crop, compress, convert, watermark, color adjust, vectorize, create GIFs, build collages, generate passport photos, find duplicates, and more.
|
||||||
- title: Local AI
|
- title: Local AI
|
||||||
details: 14 AI-powered tools - remove backgrounds, upscale, enhance images, restore and colorize old photos, erase objects, blur faces, enhance faces, extract text (OCR). All on your hardware, no internet required.
|
details: 14 AI-powered tools - remove backgrounds, upscale, enhance images, restore and colorize old photos, erase objects, blur faces, enhance faces, extract text (OCR). All on your hardware, no internet required.
|
||||||
@@ -31,7 +31,8 @@ features:
|
|||||||
<div class="quick-start-banner">
|
<div class="quick-start-banner">
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
docker run -d --name SnapOtter -p 1349:1349 -v SnapOtter-data:/data snapotter/snapotter:latest
|
docker run -d --name SnapOtter -p 1349:1349 -v SnapOtter-data:/data snapotter/snapotter:latest \
|
||||||
|
&& printf "\n 🦦 SnapOtter\n ────────────────────────────────────────\n\n ➜ Open http://localhost:1349\n ➜ Login admin / admin\n ➜ Docs https://docs.snapotter.com\n\n"
|
||||||
```
|
```
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -148,6 +148,7 @@ RUN for i in 1 2 3; do apt-get -o Acquire::Retries=3 update && break || sleep $(
|
|||||||
apt-get install -y --no-install-recommends \
|
apt-get install -y --no-install-recommends \
|
||||||
tini \
|
tini \
|
||||||
imagemagick \
|
imagemagick \
|
||||||
|
libmagickcore-6.q16-7-extra \
|
||||||
libraw-dev \
|
libraw-dev \
|
||||||
libopenexr-dev \
|
libopenexr-dev \
|
||||||
potrace \
|
potrace \
|
||||||
|
|||||||
@@ -25,6 +25,18 @@ if [ ! -d "$AI_VENV" ] && [ -d "/opt/venv" ]; then
|
|||||||
echo "AI venv ready at $AI_VENV"
|
echo "AI venv ready at $AI_VENV"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
print_banner() {
|
||||||
|
RST='\033[0m'
|
||||||
|
printf '\n'
|
||||||
|
printf ' \033[1;36m🦦 SnapOtter%b\n' "$RST"
|
||||||
|
printf ' \033[2m────────────────────────────────────────%b\n' "$RST"
|
||||||
|
printf '\n'
|
||||||
|
printf ' \033[32m➜%b Open \033[1;4mhttp://localhost:%s%b\n' "$RST" "${PORT:-1349}" "$RST"
|
||||||
|
printf ' \033[33m➜%b Login \033[1m%s%b / \033[1m%s%b\n' "$RST" "${DEFAULT_USERNAME}" "$RST" "${DEFAULT_PASSWORD}" "$RST"
|
||||||
|
printf ' \033[36m➜%b Docs \033[2mhttps://docs.snapotter.com%b\n' "$RST" "$RST"
|
||||||
|
printf '\n'
|
||||||
|
}
|
||||||
|
|
||||||
# Fix ownership of mounted volumes so the non-root snapotter user can write.
|
# Fix ownership of mounted volumes so the non-root snapotter user can write.
|
||||||
# This runs as root, fixes permissions, then drops to snapotter via gosu.
|
# This runs as root, fixes permissions, then drops to snapotter via gosu.
|
||||||
if [ "$(id -u)" = "0" ]; then
|
if [ "$(id -u)" = "0" ]; then
|
||||||
@@ -68,8 +80,10 @@ if [ "$(id -u)" = "0" ]; then
|
|||||||
chown -R snapotter:snapotter /data /tmp/workspace 2>&1 || \
|
chown -R snapotter:snapotter /data /tmp/workspace 2>&1 || \
|
||||||
echo "WARNING: Could not fix volume permissions. Use named volumes (not Windows bind mounts) to avoid this. See docs for details." >&2
|
echo "WARNING: Could not fix volume permissions. Use named volumes (not Windows bind mounts) to avoid this. See docs for details." >&2
|
||||||
|
|
||||||
|
print_banner
|
||||||
exec gosu snapotter "$@"
|
exec gosu snapotter "$@"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Already running as snapotter (e.g. Kubernetes runAsUser)
|
# Already running as snapotter (e.g. Kubernetes runAsUser)
|
||||||
|
print_banner
|
||||||
exec "$@"
|
exec "$@"
|
||||||
|
|||||||
@@ -24,6 +24,14 @@ export async function resize(image: Sharp, options: ResizeOptions): Promise<Shar
|
|||||||
throw new Error("Resize requires width, height, or percentage");
|
throw new Error("Resize requires width, height, or percentage");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (withoutEnlargement) {
|
||||||
|
const meta = await image.metadata();
|
||||||
|
const curW = meta.width ?? 0;
|
||||||
|
const curH = meta.height ?? 0;
|
||||||
|
if (width !== undefined && width > curW) width = curW;
|
||||||
|
if (height !== undefined && height > curH) height = curH;
|
||||||
|
}
|
||||||
|
|
||||||
return image.resize({
|
return image.resize({
|
||||||
width,
|
width,
|
||||||
height,
|
height,
|
||||||
|
|||||||
@@ -304,12 +304,12 @@ describe("validateImageBuffer", () => {
|
|||||||
expect(result.valid).toBe(false);
|
expect(result.valid).toBe(false);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("rejects BMP magic with no actual BMP structure", async () => {
|
it("accepts BMP magic (decoded via CLI, not Sharp)", async () => {
|
||||||
const fakeBmp = Buffer.from([0x42, 0x4d, 0x00, 0x00]);
|
const fakeBmp = Buffer.from([0x42, 0x4d, 0x00, 0x00]);
|
||||||
const result = await validateImageBuffer(fakeBmp);
|
const result = await validateImageBuffer(fakeBmp);
|
||||||
expect(result.valid).toBe(false);
|
expect(result.valid).toBe(true);
|
||||||
if (!result.valid) {
|
if (result.valid) {
|
||||||
expect(result.reason).toBe("Failed to read image metadata");
|
expect(result.format).toBe("bmp");
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user