8.9 KiB
This is a design draft. Lots of things are missing. Fill the holes during planning. Ask user if things are unclear.
Rules
- Project uses copyparty (github.com/9001/copyparty) for both application, content, user data and interactions trough it's web-dav like interface. Check github.com/9001/copyparty/blob/hovudstraum/docs/devnotes.md for http api reference.
- Project must be tested with a set of curl/nodejs sanity checks and an automated browser in docker, using gitea actions for releases and local docker for development loops.
- Project's repository lives at gitea.antonene.space/KORMsoftware/Copyparty-content-tag.
- Plan development and track progress in PLAN.md.
- Make tests for each feature (unless bulletproof or not observable from tests e.g. css styles on web-pages, that will require human interaction).
- All scripts must be served from copyparty. If you add an external library, download a local copy.
Functionality
- The tagging system is a fully-static html+js application running on copyparty as a backend. It is supposed to make image/video/animation uplolading, tagging and sorting easy.
- Meets users with a "directory" input field if "current-cwd-header" is not set (basically a login screen). It is a custom url text field that allows setting the working directory. Below is a "check" button and an "enter" button. Check button gets user's permissions for the chosen directory and tells the user whether it is accessible and whether the user has write access.
- After picking a working directory the user must be dropped into the main ui.
- App loads database for all files and their tags in tsv format stored under
{cwd}/db/latest.tsv. - App lists new content in
{cwd}/content/unmanaged, checks if files are not already in the loaded database: if not - moves them to{cwd}/content/managedunder their new db-related ids, processes hashes and adds them to the database. - App computes similarity hashes (phash.js) and main colors for images (1-4) (only images for now).
Copyparty API wrapper (verified against localhost:8086)
All operations use PW: <password> header for auth. Anonymous sees only read-only /public/. Authenticated user sees both read (/public/, no write) and admin (/private/user, full access). Server returns 403 on wrong password.
API endpoints used by the app
Directory resolution ("Check" button)
GET /{path}/?ls→ JSON withdirs[],files[],perms[]array. If"write"∈perms, directory is writable.- The "Enter" button stores
{path}as CWD in localStorage and redirects to main UI.
TSV database read/write
- Read:
GET /{cwd}/db/latest.tsv?txt&pw=...→ raw text body. File may not exist on first run (404) — app creates fresh empty DB. - Write full:
PUTto/private/user/db/latest.tsv?jwithReplace: 1header + binary body → JSON{filesz, fileurl, sha512}on success. - Append row:
PUTto same URL with?apnd&j(no Replace needed) appends bytes to existing file.
IMPORTANT: The ?replace URL parameter does NOT work for PUT uploads — only the Replace: 1 HTTP header overwrites. Without it, copyparty deduplicates by appending a timestamp suffix.
File management
- List dir:
GET /{path}/?ls&dots→ JSON listing (includes dotfiles). - Move file:
POST /{src}?move=/{dst}→ returns "k" on success. Source gone, dest appears with new name. - Copy file:
POST /{src}?copy=/{dst}→ same pattern as move. - Delete single:
POST /{path}to?delete&j→ JSON confirm. - Delete batch:
jPOST /?delete&jbody["/abs/path1", "/abs/path2"]at root level. - Mkdir:
mPOST /{parent}/?replacewithact=mkdir&name=<dir>→ HTML redirect + "aight" confirm. Auto-creates missing parents.
File upload
- PUT (binary):
PUT /{path}/?jbody=file bytes,Replace: 1header to overwrite. - Multipart:
mPOST /{dir}/?replace&jwithf=@file;filename=nameon same URL. Note: the raw curl syntax for multipart requires correct-Fformatting — malformed field names cause "expected field" errors.
Search
jPOST /?lsbody{"q":"raw_field=value"}→ plain-text result lines. Syntax from copyparty's search tab (name=,ext=, etc). Returns raw field matches, not a filtered JSON listing. For app use, prefer full-dir?lsthen client-side filtering over server search.
Permission model (confirmed)
- Root
/?ls: unauthenticated →acct: "*", perms: []. Authenticated →acct: "user", both routes visible with route-specific permissions. /private/user/→perms: ["read","write","move","delete","dot","get","admin"]/public/→perms: ["read","get"]- File-level tags include
w:(copyparty's W-hash),up_by,up_ip,.up_at.
Data model
All content metadata lives in a single TSV at {cwd}/db/latest.tsv. The app reads this on startup into memory, performs all tag operations in-memory, then writes back the full TSV after each batch of changes.
DB file ID strategy
File IDs use copyparty's W-hash (w tag from ?ls JSON response). Every file listing entry includes a tags.w field — a base32/sha512-derived unique identifier for that file on that volume. This is more stable than filename or path, surviving renames and moves within the same mount.
Alternative considered: Generate UUIDv4 on first scan. Rejected — adds state not present in copyparty itself; W-hash already exists server-side and survives across sessions. For new files scanned from unmanaged, we fetch ?ls to get each file's W-tag before importing.
TSV format specification
Tab-separated values. Each row = one managed content item. Columns:
| Col | Name | Type | Description | Example |
|---|---|---|---|---|
| 0 | file_id |
string | copyparty W-hash of the file | 72EJcK7mxEGM2ygG |
| 1 | original_name |
string | Filename as uploaded to unmanaged (for display) | IMG_1234.jpg |
| 2 | content_type |
enum | image, video, animation |
image |
| 3 | similarity_hash |
hex-string | phash output for fast nearest-neighbor search | 0a1b2c3d... (64-char hex) |
| 4 | base_colors |
string | Up to 4 hex colors, pipe-separated, each with percentage | #f5a623_45,#234_15|... → TBD exact format |
| 5 | album_uuid |
uuid or empty | v4 UUID string if assigned to album, empty otherwise | 550e8400-e29b-41d4-a716-446655440000 |
| 6 | album_position |
int or empty | Zero-based position within the album group | 3 |
| 7 | user_tags |
string | Comma-separated user-assigned tags, normalized to lowercase | sunset,beach,golden hour |
| 8 | modified_at |
unix-timestamp | Epoch seconds of last metadata modification (not file creation) | 1784243166 |
File rows are appended on new-file scan. Full-file rewrite on bulk tag changes. The app maintains an in-memory index by file_id for O(1) lookup and avoids writing unchanged rows.
Color format specification
Base colors extracted via quantization of the downsampled image (k-means or median-cut, TBD). Stored as pipe-separated entries: {hex}_{percentage}. Example:
#e85d04_34,#f48c06_28,#faed00_18,#fffef2_12,#ef7d00_1,#fcffb8_1,#ff004d_1
This permits up to N colors (implementation will decide) while keeping the CSV row parseable. Percentage sums ≈ 100%.
UI Architecture
Single-page application. No build step — all JS/CSS served from /public/ on copyparty. Three views:
- Login (CWD selector) — directory input + Check/Enter buttons
- Tagging (main work view) — untagged queue + tag editor
- Gallery — browse filtered/sorted managed content
The app uses browser localStorage for persistence of CWD, password, and UI preferences. All content state lives server-side (copyparty filesystem). The TSV is the source of truth; the browser never "owns" data permanently.
### HEADER (top) ###
[last part of current working directory (centered)] [change working directory button (aligned right)]
### MAIN TAGGING UI ###
[open gallery button]
[grid of untagged images (images load into viewer when clicked)]
[previous item arrow] [image viewer] [next item arrow]
[album selector field (select from last albums, type to search, create new)]
[album position field (disabled unless an album is set)]
[tag input field (suggests existing, splits tags on comma)]
[similarity hash]
[base colors display (small)]
[delete file button]
### GALLERY ###
### HEADER (top) ###
[back button (to tagging ui)]
[tag filter picker (user tags/albums)]
[order-by picker (creation time/modification time/similarity/color similarity)]
[image grid (grouped by album)] [tag editor (sidebar) - allow for album/album position/user tag modifications when an image is clicked]
[context menu (right-click) - find similar by similarity hash/color, trash file]
Tag types
- DB modification time - timestamp of last tag modification time
- Content type - image/video/animation
- Similarity hash
- Base colors
- Album UUID - if set, content belongs to an ordered group of content items
- № in album - position of the item inside the group
- User tags - a comma-separated list of entries