Resource Versioning
Resources track file changes through content-addressable versioning. Each upload creates a new version record while deduplicating storage by SHA1 hash.
How Versioning Works
When you upload a new version of a resource:
- Stores the new file using content-addressable storage (files are stored by their SHA1 hash)
- Creates a version record with metadata (file size, dimensions, content type, etc.)
- Updates the resource to point to the new version
- Regenerates thumbnails and previews automatically
- Preserves all previous versions for comparison and restoration
Content Deduplication
Files are stored by hash, meaning identical files are only stored once regardless of how many versions reference them. This saves disk space when:
- You restore a previous version (creates a new version record but reuses the existing file)
- Multiple resources share the same file content
- A version is deleted but other versions still reference the same file
Version History Panel

The resource detail page includes a Versions panel listing all versions of the file.
For each version, you can see:
- Version number (v1, v2, v3, etc.)
- Creation date
- File size
- Comment (optional description of what changed)
- Current badge for the active version
Actions Available
| Action | Description |
|---|---|
| Download | Download that specific version's file |
| Restore | Create a new version from an older one, making it current |
| Delete | Remove a version (cannot delete the current version or the last remaining version) |
| Upload New | Add a new version with an optional comment |
Comparing Versions
Select two versions to compare by clicking the Compare button in the version panel, then checking two versions and clicking Compare Selected.

The comparison page shows:
Metadata Comparison Table
A side-by-side table displaying:
- Content type (with match/mismatch indicator)
- File size (with delta showing increase or decrease)
- Dimensions (for images)
- Hash match status
- Creation dates
- Comments
Content Comparison
Different comparison modes are available depending on file type:
Image Comparison
For images, four comparison modes are available:
| Mode | Description |
|---|---|
| Side-by-side | Both versions displayed next to each other |
| Slider | Drag a slider to reveal one image over the other |
| Onion skin | Overlay with adjustable opacity slider |
| Toggle | Click or press Space to switch between versions |
Text Comparison
For text files (plain text, code, markdown, etc.):
| Mode | Description |
|---|---|
| Unified | Single view with additions (green) and deletions (red) marked |
| Side-by-side | Two columns showing each version with changes highlighted |
The comparison also shows statistics: lines added and lines removed.
Binary and Other Files
For files without visual comparison support, you can:
- See thumbnails (if available)
- View file metadata
- Download both versions for local comparison
Cross-Resource Comparison
You can also compare versions between different resources. This is useful when:
- Finding which version of two similar files is newer
- Comparing files that may be related but stored separately
- Investigating potential duplicates
To compare across resources, use the resource picker on the comparison page to select different resources for each side.
Restoring a Version
To restore a previous version:
- Navigate to the resource's detail page
- Open the Versions panel
- Find the version you want to restore
- Click Restore
Restoring creates a new version with the content of the old version. It does not overwrite history - you can always see the full version timeline.
The restore action:
- Creates a new version (e.g., if current is v5 and you restore v2, you get v6 with v2's content)
- Updates the resource to use the restored content
- Regenerates thumbnails
- Logs the action with a default comment: "Restored from version X"
Uploading New Versions
To upload a new version:
- Navigate to the resource's detail page
- Open the Versions panel
- Use the file input at the bottom
- Optionally add a comment describing the changes
- Click Upload New Version
Add a comment describing the change (e.g., "Fixed typo in title", "Higher resolution scan").
Storage Implications
Disk Space
Each unique file is stored once. Version records are ~200 bytes each in the database. The main storage cost is the actual file content.
To estimate storage needs:
- Count unique file content (not versions)
- Consider that restored versions reuse existing files
- Deleting versions may or may not free space depending on references
Database Growth
Each version adds one row to the resource_versions table. For large databases with millions of resources, this can add up. Consider periodic cleanup of old versions.
Cleanup Options
Two cleanup modes are available:
Per-resource cleanup:
- Keep only the last N versions
- Delete versions older than X days
- Dry-run mode to preview what would be deleted
Bulk cleanup:
- Clean versions across all resources owned by a group
- Same criteria options (keep last N, older than X days)
Version deletion is permanent. Always use dry-run mode first to verify what will be deleted.
API Endpoints
| Method | Path | Description |
|---|---|---|
GET | /v1/resource/versions?resourceId={resourceId} | List all versions for a Resource |
GET | /v1/resource/version?id={versionId} | Get a single version by ID |
GET | /v1/resource/version/file?versionId={versionId} | Download a version's file |
POST | /v1/resource/versions?resourceId={resourceId} | Upload a new version (multipart: file, comment) |
POST | /v1/resource/version/restore | Restore a previous version (resourceId, versionId, comment) |
DELETE | /v1/resource/version | Delete a version (resourceId, versionId) |
POST | /v1/resource/version/delete | Delete a version (POST alias for DELETE) |
POST | /v1/resource/versions/cleanup | Cleanup old versions for a single Resource (JSON body) |
POST | /v1/resources/versions/cleanup | Bulk cleanup across Resources |
GET | /v1/resource/versions/compare | Compare two versions side-by-side |
Migration from Older Databases
On startup, a background migration automatically creates actual v1 records for Resources created before version was introduced:
- Finds Resources with no
current_version_id - Processes in batches of 500 (with 10ms sleep between batches)
- Creates a v1 record from the current Resource state
- Logs progress every 10,000 Resources
The migration does not block startup. For databases with millions of Resources, skip it and run during a maintenance window:
| Flag | Env Variable | Default |
|---|---|---|
-skip-version-migration | SKIP_VERSION_MIGRATION=1 | false |
./mahresources -skip-version-migration -db-type=SQLITE -db-dsn=./mahresources.db -file-save-path=./files