Compare commits

...
Sign in to create a new pull request.

12 commits

Author SHA1 Message Date
520489ab48
docs: finalize hacking guidelines
Signed-off-by: NotAShelf <raf@notashelf.dev>
Change-Id: I2d3a5a9d745630f5da305664f0bdc66e6a6a6964
2026-03-23 03:30:56 +03:00
273d0244aa
chore: generate a documentation index for REST API docs in docs/api
Signed-off-by: NotAShelf <raf@notashelf.dev>
Change-Id: Ia8426a63a50d07a6cec2b104951d58eb6a6a6964
2026-03-23 03:30:55 +03:00
d61b5d32d1
docs: link to 'hacking' guidelines in README; document project license
Signed-off-by: NotAShelf <raf@notashelf.dev>
Change-Id: I6caae0f7de73aa02150b48a3d7d8dcd06a6a6964
2026-03-23 03:30:54 +03:00
00bab69598
meta: move public crates to packages/
Signed-off-by: NotAShelf <raf@notashelf.dev>
Change-Id: I928162008cb1ba02e1aa0e7aa971e8326a6a6964
2026-03-23 03:30:53 +03:00
70b0113d8a
meta: release under EUPL v1.2
Signed-off-by: NotAShelf <raf@notashelf.dev>
Change-Id: I81153bc119c58300fc2b1e7efab871496a6a6964
2026-03-23 03:30:52 +03:00
d18317b49b
meta: drop plugin stubs for now
Signed-off-by: NotAShelf <raf@notashelf.dev>
Change-Id: Iedf1d829183c258d0f6ef8a313053d4d6a6a6964
2026-03-23 03:30:51 +03:00
e7e9ea6036
nix: drop sccache
Did not work.

Signed-off-by: NotAShelf <raf@notashelf.dev>
Change-Id: I09802cd4835347115ba51bdffd0af1096a6a6964
2026-03-23 03:30:47 +03:00
6900984e46
meta: set up editorconfig
Signed-off-by: NotAShelf <raf@notashelf.dev>
Change-Id: Ie44e5a7239a053d674e6aa06e14c3ce56a6a6964
2026-03-22 23:58:29 +03:00
9e5eb41d39
nix: set up project-wide formatter
Signed-off-by: NotAShelf <raf@notashelf.dev>
Change-Id: I4806c58aa0a17f504c9312723ad770166a6a6964
2026-03-22 23:58:28 +03:00
aa9c55277c
docs: document usage for Just intrumentation
Signed-off-by: NotAShelf <raf@notashelf.dev>
Change-Id: I11f818ca94867d483caf89c1753e28876a6a6964
2026-03-22 23:58:27 +03:00
f55edcdedd
meta: set up Just for general maintenance tasks
Signed-off-by: NotAShelf <raf@notashelf.dev>
Change-Id: I16a6121ab52fbc732a56720f622321496a6a6964
2026-03-22 23:58:26 +03:00
raf
035825a402 Merge pull request 'treewide: general cleanup' (#11) from notashelf/push-wsulzmvymvxq into main
Reviewed-on: #11
2026-03-22 20:14:10 +00:00
403 changed files with 57223 additions and 53193 deletions

32
.editorconfig Normal file
View file

@ -0,0 +1,32 @@
root = true
[*]
charset = utf-8
end_of_line = lf
insert_final_newline = true
trim_trailing_whitespace = true
[*.rs]
indent_style = space
indent_size = 2
[justfile]
indent_style = space
indent_size = 2
[*.toml]
indent_style = space
indent_size = 2
[*.md]
indent_style = space
indent_size = 2
trim_trailing_whitespace = false
[*.nix]
indent_style = space
indent_size = 2
[*.scss]
indent_style = space
indent_size = 2

BIN
Cargo.lock generated

Binary file not shown.

View file

@ -1,5 +1,5 @@
[workspace] [workspace]
members = ["crates/*", "xtask"] members = ["crates/*", "packages/*", "xtask"]
exclude = ["crates/pinakes-core/tests/fixtures/test-plugin"] exclude = ["crates/pinakes-core/tests/fixtures/test-plugin"]
resolver = "3" resolver = "3"
@ -11,35 +11,44 @@ readme = true
rust-version = "1.95.0" # follows nightly Rust rust-version = "1.95.0" # follows nightly Rust
[workspace.dependencies] [workspace.dependencies]
# Crate components for Pinakes. # Crate components for Pinakes. Those are the internal dependencies that are built
# while building any package.
pinakes-core = { path = "./crates/pinakes-core" } pinakes-core = { path = "./crates/pinakes-core" }
pinakes-server = { path = "./crates/pinakes-server" }
pinakes-plugin-api = { path = "./crates/pinakes-plugin-api" } pinakes-plugin-api = { path = "./crates/pinakes-plugin-api" }
pinakes-ui = { path = "./crates/pinakes-ui" }
pinakes-tui = { path = "./crates/pinakes-tui" }
tokio = { version = "1.49.0", features = ["full"] } # Pinakes itself is a REST API server. UI and TUI are official visual components
# that connect to the server. Using the API documentation, the user can write
# their own clients, but we separate "crates" and "packages" to establish the
# distinction properly.
pinakes-server = { path = "./packages/pinakes-server" }
pinakes-ui = { path = "./packages/pinakes-ui" }
pinakes-tui = { path = "./packages/pinakes-tui" }
# Other dependencies. Declaring them in the virtual manifests lets use reuse the crates
# without having to track individual crate version across different types of crates. This
# also includes *dev* dependencies.
tokio = { version = "1.50.0", features = ["full"] }
tokio-util = { version = "0.7.18", features = ["rt"] } tokio-util = { version = "0.7.18", features = ["rt"] }
serde = { version = "1.0.228", features = ["derive"] } serde = { version = "1.0.228", features = ["derive"] }
serde_json = "1.0.149" serde_json = "1.0.149"
toml = "1.0.3" toml = "1.0.7"
clap = { version = "4.5.60", features = ["derive", "env"] } clap = { version = "4.6.0", features = ["derive", "env"] }
chrono = { version = "0.4.44", features = ["serde"] } chrono = { version = "0.4.44", features = ["serde"] }
uuid = { version = "1.21.0", features = ["v7", "serde"] } uuid = { version = "1.22.0", features = ["v7", "serde"] }
thiserror = "2.0.18" thiserror = "2.0.18"
anyhow = "1.0.102" anyhow = "1.0.102"
tracing = "0.1.44" tracing = "0.1.44"
tracing-subscriber = { version = "0.3.22", features = ["env-filter", "json"] } tracing-subscriber = { version = "0.3.23", features = ["env-filter", "json"] }
blake3 = "1.8.3" blake3 = "1.8.3"
rustc-hash = "2.1.1" rustc-hash = "2.1.1"
ed25519-dalek = { version = "2.1.1", features = ["std"] } ed25519-dalek = { version = "2.2.0", features = ["std"] }
lofty = "0.23.2" lofty = "0.23.3"
lopdf = "0.39.0" lopdf = "0.40.0"
epub = "2.1.5" epub = "2.1.5"
matroska = "0.30.0" matroska = "0.30.0"
gray_matter = "0.3.2" gray_matter = "0.3.2"
kamadak-exif = "0.6.1" kamadak-exif = "0.6.1"
rusqlite = { version = "=0.37.0", features = ["bundled", "column_decltype"] } rusqlite = { version = "0.37.0", features = ["bundled", "column_decltype"] }
tokio-postgres = { version = "0.7.16", features = [ tokio-postgres = { version = "0.7.16", features = [
"with-uuid-1", "with-uuid-1",
"with-chrono-0_4", "with-chrono-0_4",
@ -52,7 +61,7 @@ native-tls = "0.2.18"
refinery = { version = "0.9.0", features = ["rusqlite", "tokio-postgres"] } refinery = { version = "0.9.0", features = ["rusqlite", "tokio-postgres"] }
walkdir = "2.5.0" walkdir = "2.5.0"
notify = { version = "8.2.0", features = ["macos_fsevent"] } notify = { version = "8.2.0", features = ["macos_fsevent"] }
winnow = "0.7.14" winnow = "1.0.0"
axum = { version = "0.8.8", features = ["macros", "multipart"] } axum = { version = "0.8.8", features = ["macros", "multipart"] }
axum-server = { version = "0.8.0" } axum-server = { version = "0.8.0" }
tower = "0.5.3" tower = "0.5.3"
@ -67,7 +76,7 @@ dioxus = { version = "0.7.3", features = ["desktop", "router"] }
dioxus-core = { version = "0.7.3" } dioxus-core = { version = "0.7.3" }
async-trait = "0.1.89" async-trait = "0.1.89"
futures = "0.3.32" futures = "0.3.32"
image = { version = "0.25.9", default-features = false, features = [ image = { version = "0.25.10", default-features = false, features = [
"jpeg", "jpeg",
"png", "png",
"webp", "webp",
@ -75,7 +84,7 @@ image = { version = "0.25.9", default-features = false, features = [
"tiff", "tiff",
"bmp", "bmp",
] } ] }
pulldown-cmark = "0.13.1" pulldown-cmark = "0.13.3"
ammonia = "4.1.2" ammonia = "4.1.2"
argon2 = { version = "0.5.3", features = ["std"] } argon2 = { version = "0.5.3", features = ["std"] }
mime_guess = "2.0.5" mime_guess = "2.0.5"
@ -84,17 +93,18 @@ dioxus-free-icons = { version = "0.10.0", features = ["font-awesome-solid"] }
rfd = "0.17.2" rfd = "0.17.2"
gloo-timers = { version = "0.3.0", features = ["futures"] } gloo-timers = { version = "0.3.0", features = ["futures"] }
rand = "0.10.0" rand = "0.10.0"
moka = { version = "0.12.14", features = ["future"] } moka = { version = "0.12.15", features = ["future"] }
urlencoding = "2.1.3" urlencoding = "2.1.3"
image_hasher = "3.1.1" image_hasher = "3.1.1"
percent-encoding = "2.3.2" percent-encoding = "2.3.2"
http = "1.4.0" http = "1.4.0"
wasmtime = { version = "42.0.1", features = ["component-model"] } wasmtime = { version = "43.0.0", features = ["component-model"] }
wit-bindgen = "0.53.1" wit-bindgen = "0.54.0"
tempfile = "3.26.0" tempfile = "3.27.0"
utoipa = { version = "5.4.0", features = ["axum_extras", "uuid", "chrono"] } utoipa = { version = "5.4.0", features = ["axum_extras", "uuid", "chrono"] }
utoipa-axum = { version = "0.2.0" } utoipa-axum = { version = "0.2.0" }
utoipa-swagger-ui = { version = "9.0.2", features = ["axum"] } utoipa-swagger-ui = { version = "9.0.2", features = ["axum"] }
http-body-util = "0.1.3"
# See: # See:
# <https://doc.rust-lang.org/rustc/lints/listing/allowed-by-default.html> # <https://doc.rust-lang.org/rustc/lints/listing/allowed-by-default.html>

View file

@ -1,32 +0,0 @@
# Hacking Pinakes
Pinakes is a lot of things. One of the things it aims to be is _complete_. To be
complete in features, to be complete in documentation and to be complete in
hackability. This document covers, very comprehensively, how you may:
- Build Pinakes
- Develop Pinakes
- Contribute to Pinakes
for developers as well as:
- Distribute Pinakes
for Pinakes maintainers and packagers.
## Building Pinakes
Pinakes is built with Rust (nightly edition) and various crates. The most
_notable_ crate among those is Dioxus, which provides its own toolkit. The UI
for Pinakes is usually _not_ built with the Dioxus CLI but instead with
`cargo build`. This also applies to distributable build results.
[Direnv]: https://direnv.net
To build Pinakes, simply pick the components you want and build them with
`cargo build --release --package <component>`. A Nix shell is provided for
reproducible developer environments and you may obtain all build dependencies by
simply running `nix develop` or `direnv allow` if you use [Direnv]. Nix is a
cross-platform build tool and works on most Linux distributions as well as
Darwin. While distro-specific package managers _might_ work, Nix is the only
supported one.

BIN
LICENSE Normal file

Binary file not shown.

View file

@ -4,6 +4,9 @@ edition.workspace = true
version.workspace = true version.workspace = true
license.workspace = true license.workspace = true
[features]
ffmpeg-tests = []
[dependencies] [dependencies]
tokio = { workspace = true } tokio = { workspace = true }
serde = { workspace = true } serde = { workspace = true }
@ -43,18 +46,13 @@ moka = { workspace = true }
urlencoding = { workspace = true } urlencoding = { workspace = true }
image_hasher = { workspace = true } image_hasher = { workspace = true }
rustc-hash = { workspace = true } rustc-hash = { workspace = true }
pinakes-plugin-api = { workspace = true }
# Plugin system wasmtime = { workspace = true }
pinakes-plugin-api.workspace = true ed25519-dalek = { workspace = true }
wasmtime.workspace = true
ed25519-dalek.workspace = true
[features]
ffmpeg-tests = []
[lints]
workspace = true
[dev-dependencies] [dev-dependencies]
tempfile = { workspace = true } tempfile = { workspace = true }
rand = { workspace = true } rand = { workspace = true }
[lints]
workspace = true

View file

@ -4,32 +4,25 @@ version.workspace = true
edition.workspace = true edition.workspace = true
license.workspace = true license.workspace = true
[features]
default = []
wasm = ["wit-bindgen"]
[dependencies] [dependencies]
# Core dependencies
serde = { workspace = true } serde = { workspace = true }
serde_json = { workspace = true } serde_json = { workspace = true }
thiserror = { workspace = true } thiserror = { workspace = true }
async-trait = { workspace = true } async-trait = { workspace = true }
tracing = { workspace = true } tracing = { workspace = true }
# For plugin manifest parsing
toml = { workspace = true } toml = { workspace = true }
# For media types and identifiers
uuid = { workspace = true } uuid = { workspace = true }
chrono = { workspace = true } chrono = { workspace = true }
mime_guess = { workspace = true } mime_guess = { workspace = true }
rustc-hash = { workspace = true } rustc-hash = { workspace = true }
# WASM bridge types
wit-bindgen = { workspace = true, optional = true } wit-bindgen = { workspace = true, optional = true }
[lints]
workspace = true
[features]
default = []
wasm = ["wit-bindgen"]
[dev-dependencies] [dev-dependencies]
tokio = { workspace = true, features = ["rt", "rt-multi-thread", "macros"] } tokio = { workspace = true, features = ["rt", "rt-multi-thread", "macros"] }
[lints]
workspace = true

File diff suppressed because one or more lines are too long

276
docs/HACKING.md Normal file
View file

@ -0,0 +1,276 @@
# Hacking Pinakes
Pinakes is a lot of things. It also _plans_ to be a lot of things. One of those
things, as you might have noticed, is _complete_. To be complete in features, to
be complete in documentation and to be complete in hackability. This document
covers, very comprehensively, how you may:
- Develop Pinakes
- Build Pinakes
- Contribute to Pinakes
for developers as well as:
- Distribute Pinakes
for Pinakes maintainers and packagers.
## Development Environment
[Nix]: https://nixos.org
[Direnv]: https://direnv.net
Pinakes uses [Nix] with flakes for pure, reproducible developer environments.
All of the build dependencies that you need, from Rust to GTK and webkit, are
provided in the devshell. In addition to _not_ requiring system libraries, we'd
like to _actively discourage you_ from relying on system libraries.
[Direnv] may be preferred by some users as an alternative to `nix develop`.
```bash
# Enter the dev shell
$ nix develop
# Or with direnv (recommended)
$ direnv allow
```
Nix is the only supported way to get all dependencies. Distro package managers
_may_ work for the core crates but are not supported for the UI.
## Building Pinakes
Pinakes is a Cargo workspace with multiple crates targeting multiple aspects of
the projects. The user-facing crates are provided in `packages/*`.
`pinakes-server` and `pinakes-tui` can be built normally with `cargo` while the
UI crate, i.e., `pinakes-ui` **must** be built with the Dioxus CLI (`dx`) so
that the SCSS stylesheets are compiled correctly. Everything else should work
perfectly fine with the standard `cargo build`.
[Just]: https://just.systems
To make the things a _little_ bit more convenient, this project uses [Just] as
its command runner and provides a few recipes for common tasks.
```bash
# Build all crates (server + TUI + UI)
$ just build-all
# Build individual components
$ just build-server # pinakes-server (HTTP API)
$ just build-tui # pinakes-tui (terminal UI)
$ just build-ui # pinakes-ui (Dioxus desktop/web UI, requires dx)
```
The dependencies required for the recipes, including Just itself, is provided by
the Nix devshell. You may also run the cargo equivalents of those commands:
```bash
# Core crates
$ cargo build -p pinakes-server
$ cargo build -p pinakes-tui
# The UI crate must be built with dx, not cargo.
$ dx build -p pinakes-ui
```
Release builds follow the same pattern, but with `--release`. Nothing else
should be required for the most part.
## Running Pinakes
For the time being, UI clients require the server to be running before they can
function. Offline sync is planned, but likely come later alongside cross-device
sync. All UIs connect to the server over HTTP.
```bash
# 1. Copy and edit the example config (first time only)
$ cp pinakes.example.toml pinakes.toml
# 2. Start the server (defaults to 127.0.0.1:3000)
$ cargo run -p pinakes-server -- pinakes.toml
# 3a. Run the TUI client
$ cargo run -p pinakes-tui
# 3b. Run the desktop/web UI
$ cargo run -p pinakes-ui
# Connect TUI to a non-default server address
$ cargo run -p pinakes-tui -- --server http://localhost:3000
```
The GUI reads `PINAKES_SERVER_URL` if set; otherwise it assumes
`http://127.0.0.1:3000`. You may change the host and port if you require.
## Testing
[cargo-nextest]: https://nexte.st
Pinakes boasts, or well, attempts to boast a very comprehensive testing suite to
ensure no regressions are introduced while in development. For fast parallel
test execution, we use [cargo-nextest] and while you are recommended to use it
over `cargo test` both should be supported. For CI and package tests, only
`cargo-nextest` is supported.
```bash
# Run all workspace tests (preferred)
$ just test
# Equivalent without Just
$ cargo nextest run --workspace
# Tests for a single crate
$ cargo nextest run -p pinakes-core
$ cargo nextest run -p pinakes-server
# Run a specific test by name and show output
$ cargo test -p pinakes-core -- test_name --nocapture
```
## Linting and Formatting
### Formatting
There is a treewide formatter provided within the `flake.nix` that invokes the
recommended formatter tooling across the various filetypes, including database
migrations and Markdown sources. While a `just fmt` recipe is provided, it will
not cover most of the files. Still, you may format the Rust sources as such:
```bash
# Format all code
$ just fmt # or: cargo fmt
# Check formatting without modifying files
$ cargo fmt --check
```
### Linting
For now lints only cover Rust sources, and are provided by the Clippy tool. You
may, as usual, invoke it with `just lint`.
```bash
# Run Clippy
$ just lint # or: cargo clippy --workspace
# Treat Clippy warnings as errors (used in CI)
$ cargo clippy --workspace -- -D warnings # `-D warnings` is recommended
```
All Clippy warnings, besides some of the really annoying ones or false
positives, must be resolved at the source. **Do not** use `#[allow(...)]` or
`#[expect(...)]` to silence warnings unless there is a documented reason.
It may be okay, at times, to suppress lints but you should focus on _resolving_
them rather than suppressing them. _If_ suppressing, prefer `#[expect]` and
provide the `reason` parameter.
## Generating API Documentation
The REST API documentation is generated from OpenAPI annotations in
`pinakes-server` using `cargo xtask`:
```bash
# Generate the API documentation at docs/api/
$ just docs # or: cargo xtask docs
```
This writes:
- `docs/api.md` - Index linking all generated files
- `docs/api/<tag>.md` - One Markdown file per API tag
- `docs/api/openapi.json` - Full OpenAPI 3.0 specification
Please do not edit files under `docs/api/` by hand; they are regenerated on each
run.
## Code Style
There are many conventions that are in play within the Pinakes codebase. You'll
get used and adjust to most of them naturally, but here are some of the general
conventions written down for convenience.
### General
1. **Make illegal states unrepresentable**; ıse ADTs and newtype wrappers to
encode constraints in the type system. Parse and validate at system
boundaries (user input, external APIs); trust internal types everywhere else.
2. This one is kind of obvious, but please use the `tracing` crate with
structured fields. Do not use `println!` or `eprintln!` in library or server
code.
3. All storage operations must be implemented for both the SQLite and PostgreSQL
backends. Neither backend is optional.
Naming is kind of obvious, but I'd like to remind you that we use `snake_case`
for functions and variables, `PascalCase` for types and traits,
`SCREAMING_SNAKE_CASE` for constants. There is not much else to it.
### Error Handling
- **`unwrap()` and `expect()` are banned** in non-test code. Use `?`,
`ok_or_else`, `match`, or `if let` instead.
- The only accepted exception is when a failure is truly impossible (e.g., a
hardcoded regex that is known-valid at compile time). In that case, add
`#[expect(clippy::expect_used, reason = "...")]` with a clear explanation.
- Wrap storage errors with the `db_ctx` helper so error messages include the
operation and relevant ID:
```rust
stmt.execute(params).map_err(db_ctx("insert_media", &media_id))?;
```
### Disallowed Types and APIs
Pinakes uses hashers from `rustc_hash`, i.e., `FxHashMap` and `FxHashSet` over
the `std` equivalents due to their faster nature without random state. This is
enforced by clippy.
As Pinakes uses Rust 1.90+, the now-deprecated `once_cell` crate is also banned.
Use the stdlib equivalents:
- `once_cell::unsync::OnceCell` -> `std::cell::OnceCell`
- `once_cell::sync::OnceCell` -> `std::sync::OnceLock`
- `once_cell::unsync::Lazy` -> `std::cell::LazyCell`
- `once_cell::sync::Lazy` -> `std::sync::LazyLock`
## Contributing
1. Fork the repository and create a feature branch.
2. Enter the dev shell: `nix develop` (or `direnv allow`).
3. Make your changes; keep commits focused and descriptive. We use **scoped
commits**.
4. Run `just fmt` and `just lint` before opening a pull request.
5. Run `just test` to verify nothing is broken.
6. Open a pull request against `main`. Describe what you changed and why.
For significant changes, consider opening an issue first to discuss the
approach.
## Packaging and Distribution
Pinakes is released under EUPL v1.2. For distributable release builds, please
respect the license. You may create release builds for the user-facing crates
using `cargo build --release` for server and TUI components. Use `dx` instead
for the GUI:
```bash
# Server
$ cargo build --release -p pinakes-server
# TUI
$ cargo build --release -p pinakes-tui
# GUI
$ dx build --release -p pinakes-ui
```
The resulting binaries are self-contained except for system libraries required
by the UI (GTK3, libsoup, webkit2gtk). Database migrations run automatically on
first server startup via the `refinery` crate; no manual migration step is
needed.
SQLite (the default backend) requires no external database server. PostgreSQL
deployments need the `pg_trgm` extension enabled on the target database. It is
generally advisable to document those requirements for the users, even though
they do not require additional packaging steps.

View file

@ -11,19 +11,41 @@ PostgreSQL (production deployments) as available database backends.
## Building ## Building
This project uses [Just](https://just.systems/) as its command runner to help
run cargo with a consistent interface. You are recommended to get it with Nix,
using the default devshell which provides Just.
```bash ```bash
# Build all compilable crates # Build everything (core crates + UI)
$ just build
# Build only core crates (cargo)
$ just build-core
# Build only the UI (uses dx - see note below)
$ just build-ui
```
> [!IMPORTANT]
> The Dioxus UI (`pinakes-ui`) must be built with `dx` (Dioxus CLI) to compile
> SCSS stylesheets. Using `cargo build -p pinakes-ui` will not work correctly as
> it skips the SCSS compilation step. This was previously "remedied" with an
> intermediate build wrapper, which turned out to be fragile. It is highly
> recommended that you prefer `just` to build.
Manual build commands if not using Just:
```bash
# Core crates (standard cargo)
$ cargo build -p pinakes-core -p pinakes-server -p pinakes-tui $ cargo build -p pinakes-core -p pinakes-server -p pinakes-tui
# The Dioxus UI requires GTK3 and libsoup system libraries: # UI (requires Dioxus CLI for SCSS compilation)
$ dx build -p pinakes-ui
# System dependencies for the UI:
# On Debian/Ubuntu: apt install libgtk-3-dev libsoup-3.0-dev libwebkit2gtk-4.1-dev # On Debian/Ubuntu: apt install libgtk-3-dev libsoup-3.0-dev libwebkit2gtk-4.1-dev
# On Fedora: dnf install gtk3-devel libsoup3-devel webkit2gtk4.1-devel # On Fedora: dnf install gtk3-devel libsoup3-devel webkit2gtk4.1-devel
# On Nix: Use the dev shell, everything is provided :) # On Nix: Use the dev shell, everything is provided :)
$ cargo build -p pinakes-ui
# Alternatively, while app deps are in PATH, you may simply build the entire
# workspace.
$ cargo build --workspace
``` ```
## Configuration ## Configuration
@ -53,17 +75,20 @@ Key settings:
## Running ## Running
All commands are available via Just. Run `just --list` to see all available
recipes.
### Server ### Server
To use Pinakes, you will need the server to be running. The GUI on its own will To use Pinakes, you will need the server to be running. The GUI on its own will
work, but it will not be functional without the server. work, but it will not be functional without the server.
```sh ```bash
# Start the server first # Using Just
$ cargo run -p pinakes-server -- pinakes.toml $ just run-server
# or: # Or manually:
$ cargo run -p pinakes-server -- --config pinakes.toml $ cargo run -p pinakes-server -- pinakes.toml
``` ```
The server starts on the configured host:port (default `127.0.0.1:3000`). In a The server starts on the configured host:port (default `127.0.0.1:3000`). In a
@ -77,11 +102,11 @@ terminal. While the server is running you may connect to it using the `--server`
flag. flag.
```bash ```bash
# Using defaults # Using Just
$ cargo run -p pinakes-tui $ just run-tui
# or with a custom server URL: # Or manually:
$ cargo run -p pinakes-tui -- --server http://localhost:3000 $ cargo run -p pinakes-tui
``` ```
#### Keybindings #### Keybindings
@ -120,9 +145,15 @@ Pinakes features a fully fledged Desktop and Web UI powered by Dioxus. Those two
components are meant as a GUI frontend for the Pinakes server, and are components are meant as a GUI frontend for the Pinakes server, and are
interchangeable in terms of usage. interchangeable in terms of usage.
> [!IMPORTANT]
> The UI must be run with `dx` (Dioxus CLI), not `cargo run`.
```bash ```bash
# Build the UI # Using Just
$ cargo run -p pinakes-ui $ just run-ui
# Or manually with dx:
$ dx serve -p pinakes-ui
``` ```
> [!TIP] > [!TIP]
@ -170,8 +201,11 @@ and design.
## Storage Backends ## Storage Backends
Two storage backends are supported. For convenience, SQLite is the default Two storage backends are supported. For convenience, SQLite is the default
backend out of the box but for production deployments you may choose to prefer backend out of the box but for production deployments, with improved search and
PostgreSQL. scaling capabilities you may choose to prefer PostgreSQL. Both backends are
considered first-class citizens, and will be developed as such going further. If
your needs for Pinakes are modest, or if you are simply testing it out, SQLite
is the recommended database.
### **SQLite** (default) ### **SQLite** (default)
@ -180,6 +214,34 @@ guarantees FTS5 availability.
### **PostgreSQL** ### **PostgreSQL**
[pg_trgm]: https://www.postgresql.org/docs/current/pgtrgm.html
Native async with connection pooling (deadpool-postgres). Uses tsvector with Native async with connection pooling (deadpool-postgres). Uses tsvector with
weighted columns for full-text search and pg_trgm for fuzzy matching. Requires weighted columns for full-text search and [pg_trgm] for fuzzy matching. Requires
the `pg_trgm` extension. the `pg_trgm` extension.
## Contributing
[HACKING document]: ./HACKING.md
Pinakes, despite all the work going into it, is still in an early beta. Some of
the features still lack the polish they deserve and there _may_ be breaking
changes to the UI, databases, and the APIs. You may find a comprehensive
introduction to developing Pinakes in the [HACKING document].
It is generally advisable that you familiarize yourself with the codebase before
proposing or contributing changes. Pinakes consists of _many_ moving parts, and
it is better for the contributor experience to approach issues slowly and
discuss them beforehand.
## License
<!-- markdownlint-disable MD059 -->
[here]: https://interoperable-europe.ec.europa.eu/sites/default/files/custom-page/attachment/eupl_v1.2_en.pdf
This project is made available under European Union Public Licence (EUPL)
version 1.2. See [LICENSE](LICENSE) for more details on the exact conditions. An
online copy is provided [here].
<!-- markdownlint-enable MD059 -->

47
docs/api.md Normal file
View file

@ -0,0 +1,47 @@
# API Documentation
This is the index of all generated REST API documentation for Pinakes.
Documentation is generated from OpenAPI annotations via `cargo xtask docs` (or
`just docs`). Do not edit generated files by hand.
## Reference
- [openapi.json](api/openapi.json) - Full OpenAPI 3.0 specification
## Endpoints by Tag
- [Analytics](api/analytics.md) - Usage analytics and viewing history
- [Audit](api/audit.md) - Audit log entries
- [Auth](api/auth.md) - Authentication and session management
- [Backup](api/backup.md) - Database backup
- [Books](api/books.md) - Book metadata, series, authors, and reading progress
- [Collections](api/collections.md) - Media collections
- [Config](api/config.md) - Server configuration
- [Database](api/database.md) - Database administration
- [Duplicates](api/duplicates.md) - Duplicate media detection
- [Enrichment](api/enrichment.md) - External metadata enrichment
- [Export](api/export.md) - Media library export
- [Health](api/health.md) - Server health checks
- [Integrity](api/integrity.md) - Library integrity checks and repairs
- [Jobs](api/jobs.md) - Background job management
- [Media](api/media.md) - Media item management
- [Notes](api/notes.md) - Markdown notes link graph
- [Photos](api/photos.md) - Photo timeline and map view
- [Playlists](api/playlists.md) - Media playlists
- [Plugins](api/plugins.md) - Plugin management
- [Saved_searches](api/saved_searches.md) - Saved search queries
- [Scan](api/scan.md) - Directory scanning
- [Scheduled_tasks](api/scheduled_tasks.md) - Scheduled background tasks
- [Search](api/search.md) - Full-text media search
- [Shares](api/shares.md) - Media sharing and notifications
- [Social](api/social.md) - Ratings, comments, favorites, and share links
- [Statistics](api/statistics.md) - Library statistics
- [Streaming](api/streaming.md) - HLS and DASH adaptive streaming
- [Subtitles](api/subtitles.md) - Media subtitle management
- [Sync](api/sync.md) - Multi-device library synchronization
- [Tags](api/tags.md) - Media tag management
- [Transcode](api/transcode.md) - Video transcoding sessions
- [Upload](api/upload.md) - File upload and managed storage
- [Users](api/users.md) - User and library access management
- [Webhooks](api/webhooks.md) - Webhook configuration

19
docs/api/analytics.md vendored
View file

@ -17,7 +17,7 @@ See `docs/api/openapi.json` for the full schema.
#### Responses #### Responses
| Status | Description | | Status | Description |
|--------|-------------| | ------ | --------------------- |
| 200 | Event recorded | | 200 | Event recorded |
| 401 | Unauthorized | | 401 | Unauthorized |
| 500 | Internal server error | | 500 | Internal server error |
@ -31,14 +31,14 @@ See `docs/api/openapi.json` for the full schema.
#### Parameters #### Parameters
| Name | In | Required | Description | | Name | In | Required | Description |
|------|----|----------|-------------| | -------- | ----- | -------- | ------------------------- |
| `limit` | query | No | Maximum number of results | | `limit` | query | No | Maximum number of results |
| `offset` | query | No | Pagination offset | | `offset` | query | No | Pagination offset |
#### Responses #### Responses
| Status | Description | | Status | Description |
|--------|-------------| | ------ | --------------------- |
| 200 | Most viewed media | | 200 | Most viewed media |
| 401 | Unauthorized | | 401 | Unauthorized |
| 500 | Internal server error | | 500 | Internal server error |
@ -52,14 +52,14 @@ See `docs/api/openapi.json` for the full schema.
#### Parameters #### Parameters
| Name | In | Required | Description | | Name | In | Required | Description |
|------|----|----------|-------------| | -------- | ----- | -------- | ------------------------- |
| `limit` | query | No | Maximum number of results | | `limit` | query | No | Maximum number of results |
| `offset` | query | No | Pagination offset | | `offset` | query | No | Pagination offset |
#### Responses #### Responses
| Status | Description | | Status | Description |
|--------|-------------| | ------ | --------------------- |
| 200 | Recently viewed media | | 200 | Recently viewed media |
| 401 | Unauthorized | | 401 | Unauthorized |
| 500 | Internal server error | | 500 | Internal server error |
@ -73,13 +73,13 @@ See `docs/api/openapi.json` for the full schema.
#### Parameters #### Parameters
| Name | In | Required | Description | | Name | In | Required | Description |
|------|----|----------|-------------| | ---- | ---- | -------- | ------------- |
| `id` | path | Yes | Media item ID | | `id` | path | Yes | Media item ID |
#### Responses #### Responses
| Status | Description | | Status | Description |
|--------|-------------| | ------ | --------------------- |
| 200 | Watch progress | | 200 | Watch progress |
| 401 | Unauthorized | | 401 | Unauthorized |
| 404 | Not found | | 404 | Not found |
@ -94,7 +94,7 @@ See `docs/api/openapi.json` for the full schema.
#### Parameters #### Parameters
| Name | In | Required | Description | | Name | In | Required | Description |
|------|----|----------|-------------| | ---- | ---- | -------- | ------------- |
| `id` | path | Yes | Media item ID | | `id` | path | Yes | Media item ID |
#### Request Body #### Request Body
@ -106,7 +106,7 @@ See `docs/api/openapi.json` for the full schema.
#### Responses #### Responses
| Status | Description | | Status | Description |
|--------|-------------| | ------ | --------------------- |
| 200 | Progress updated | | 200 | Progress updated |
| 400 | Bad request | | 400 | Bad request |
| 401 | Unauthorized | | 401 | Unauthorized |
@ -114,4 +114,3 @@ See `docs/api/openapi.json` for the full schema.
| 500 | Internal server error | | 500 | Internal server error |
--- ---

5
docs/api/audit.md vendored
View file

@ -11,17 +11,16 @@ Audit log entries
#### Parameters #### Parameters
| Name | In | Required | Description | | Name | In | Required | Description |
|------|----|----------|-------------| | -------- | ----- | -------- | ----------------- |
| `offset` | query | No | Pagination offset | | `offset` | query | No | Pagination offset |
| `limit` | query | No | Page size | | `limit` | query | No | Page size |
#### Responses #### Responses
| Status | Description | | Status | Description |
|--------|-------------| | ------ | --------------------- |
| 200 | Audit log entries | | 200 | Audit log entries |
| 401 | Unauthorized | | 401 | Unauthorized |
| 500 | Internal server error | | 500 | Internal server error |
--- ---

16
docs/api/auth.md vendored
View file

@ -17,7 +17,7 @@ See `docs/api/openapi.json` for the full schema.
#### Responses #### Responses
| Status | Description | | Status | Description |
|--------|-------------| | ------ | --------------------- |
| 200 | Login successful | | 200 | Login successful |
| 400 | Bad request | | 400 | Bad request |
| 401 | Invalid credentials | | 401 | Invalid credentials |
@ -32,7 +32,7 @@ See `docs/api/openapi.json` for the full schema.
#### Responses #### Responses
| Status | Description | | Status | Description |
|--------|-------------| | ------ | --------------------- |
| 200 | Logged out | | 200 | Logged out |
| 401 | Unauthorized | | 401 | Unauthorized |
| 500 | Internal server error | | 500 | Internal server error |
@ -46,7 +46,7 @@ See `docs/api/openapi.json` for the full schema.
#### Responses #### Responses
| Status | Description | | Status | Description |
|--------|-------------| | ------ | --------------------- |
| 200 | Current user info | | 200 | Current user info |
| 401 | Unauthorized | | 401 | Unauthorized |
| 500 | Internal server error | | 500 | Internal server error |
@ -55,15 +55,14 @@ See `docs/api/openapi.json` for the full schema.
### POST /api/v1/auth/refresh ### POST /api/v1/auth/refresh
Refresh the current session, extending its expiry by the configured Refresh the current session, extending its expiry by the configured duration.
duration.
**Authentication:** Required (Bearer JWT) **Authentication:** Required (Bearer JWT)
#### Responses #### Responses
| Status | Description | | Status | Description |
|--------|-------------| | ------ | --------------------- |
| 200 | Session refreshed | | 200 | Session refreshed |
| 401 | Unauthorized | | 401 | Unauthorized |
| 500 | Internal server error | | 500 | Internal server error |
@ -79,7 +78,7 @@ Revoke all sessions for the current user
#### Responses #### Responses
| Status | Description | | Status | Description |
|--------|-------------| | ------ | --------------------- |
| 200 | All sessions revoked | | 200 | All sessions revoked |
| 401 | Unauthorized | | 401 | Unauthorized |
| 500 | Internal server error | | 500 | Internal server error |
@ -93,11 +92,10 @@ Revoke all sessions for the current user
#### Responses #### Responses
| Status | Description | | Status | Description |
|--------|-------------| | ------ | --------------------- |
| 200 | Active sessions | | 200 | Active sessions |
| 401 | Unauthorized | | 401 | Unauthorized |
| 403 | Forbidden | | 403 | Forbidden |
| 500 | Internal server error | | 500 | Internal server error |
--- ---

11
docs/api/backup.md vendored
View file

@ -6,22 +6,21 @@ Database backup
### POST /api/v1/admin/backup ### POST /api/v1/admin/backup
Create a database backup and return it as a downloadable file. Create a database backup and return it as a downloadable file. POST
POST /api/v1/admin/backup /api/v1/admin/backup
For `SQLite`: creates a backup via VACUUM INTO and returns the file. For `SQLite`: creates a backup via VACUUM INTO and returns the file. For
For `PostgreSQL`: returns unsupported error (use `pg_dump` instead). `PostgreSQL`: returns unsupported error (use `pg_dump` instead).
**Authentication:** Required (Bearer JWT) **Authentication:** Required (Bearer JWT)
#### Responses #### Responses
| Status | Description | | Status | Description |
|--------|-------------| | ------ | --------------------- |
| 200 | Backup file download | | 200 | Backup file download |
| 401 | Unauthorized | | 401 | Unauthorized |
| 403 | Forbidden | | 403 | Forbidden |
| 500 | Internal server error | | 500 | Internal server error |
--- ---

37
docs/api/books.md vendored
View file

@ -13,7 +13,7 @@ List all books with optional search filters
#### Parameters #### Parameters
| Name | In | Required | Description | | Name | In | Required | Description |
|------|----|----------|-------------| | ----------- | ----- | -------- | ------------------- |
| `isbn` | query | No | Filter by ISBN | | `isbn` | query | No | Filter by ISBN |
| `author` | query | No | Filter by author | | `author` | query | No | Filter by author |
| `series` | query | No | Filter by series | | `series` | query | No | Filter by series |
@ -25,7 +25,7 @@ List all books with optional search filters
#### Responses #### Responses
| Status | Description | | Status | Description |
|--------|-------------| | ------ | --------------------- |
| 200 | List of books | | 200 | List of books |
| 401 | Unauthorized | | 401 | Unauthorized |
| 500 | Internal server error | | 500 | Internal server error |
@ -41,14 +41,14 @@ List all authors with book counts
#### Parameters #### Parameters
| Name | In | Required | Description | | Name | In | Required | Description |
|------|----|----------|-------------| | -------- | ----- | -------- | ----------------- |
| `offset` | query | No | Pagination offset | | `offset` | query | No | Pagination offset |
| `limit` | query | No | Pagination limit | | `limit` | query | No | Pagination limit |
#### Responses #### Responses
| Status | Description | | Status | Description |
|--------|-------------| | ------ | ------------------------ |
| 200 | Authors with book counts | | 200 | Authors with book counts |
| 401 | Unauthorized | | 401 | Unauthorized |
@ -63,7 +63,7 @@ Get books by a specific author
#### Parameters #### Parameters
| Name | In | Required | Description | | Name | In | Required | Description |
|------|----|----------|-------------| | -------- | ----- | -------- | ----------------- |
| `name` | path | Yes | Author name | | `name` | path | Yes | Author name |
| `offset` | query | No | Pagination offset | | `offset` | query | No | Pagination offset |
| `limit` | query | No | Pagination limit | | `limit` | query | No | Pagination limit |
@ -71,7 +71,7 @@ Get books by a specific author
#### Responses #### Responses
| Status | Description | | Status | Description |
|--------|-------------| | ------ | --------------- |
| 200 | Books by author | | 200 | Books by author |
| 401 | Unauthorized | | 401 | Unauthorized |
@ -86,13 +86,13 @@ Get user's reading list
#### Parameters #### Parameters
| Name | In | Required | Description | | Name | In | Required | Description |
|------|----|----------|-------------| | -------- | ----- | -------- | ------------------------------------------------------------------------------ |
| `status` | query | No | Filter by reading status | | `status` | query | No | Filter by reading status. Valid values: to_read, reading, completed, abandoned |
#### Responses #### Responses
| Status | Description | | Status | Description |
|--------|-------------| | ------ | ------------ |
| 200 | Reading list | | 200 | Reading list |
| 401 | Unauthorized | | 401 | Unauthorized |
@ -107,7 +107,7 @@ List all series with book counts
#### Responses #### Responses
| Status | Description | | Status | Description |
|--------|-------------| | ------ | -------------------------- |
| 200 | List of series with counts | | 200 | List of series with counts |
| 401 | Unauthorized | | 401 | Unauthorized |
@ -122,13 +122,13 @@ Get books in a specific series
#### Parameters #### Parameters
| Name | In | Required | Description | | Name | In | Required | Description |
|------|----|----------|-------------| | ------ | ---- | -------- | ----------- |
| `name` | path | Yes | Series name | | `name` | path | Yes | Series name |
#### Responses #### Responses
| Status | Description | | Status | Description |
|--------|-------------| | ------ | --------------- |
| 200 | Books in series | | 200 | Books in series |
| 401 | Unauthorized | | 401 | Unauthorized |
@ -143,13 +143,13 @@ Get book metadata by media ID
#### Parameters #### Parameters
| Name | In | Required | Description | | Name | In | Required | Description |
|------|----|----------|-------------| | ---- | ---- | -------- | ------------- |
| `id` | path | Yes | Media item ID | | `id` | path | Yes | Media item ID |
#### Responses #### Responses
| Status | Description | | Status | Description |
|--------|-------------| | ------ | ------------- |
| 200 | Book metadata | | 200 | Book metadata |
| 401 | Unauthorized | | 401 | Unauthorized |
| 404 | Not found | | 404 | Not found |
@ -165,13 +165,13 @@ Get reading progress for a book
#### Parameters #### Parameters
| Name | In | Required | Description | | Name | In | Required | Description |
|------|----|----------|-------------| | ---- | ---- | -------- | ------------- |
| `id` | path | Yes | Media item ID | | `id` | path | Yes | Media item ID |
#### Responses #### Responses
| Status | Description | | Status | Description |
|--------|-------------| | ------ | ---------------- |
| 200 | Reading progress | | 200 | Reading progress |
| 401 | Unauthorized | | 401 | Unauthorized |
| 404 | Not found | | 404 | Not found |
@ -187,7 +187,7 @@ Update reading progress for a book
#### Parameters #### Parameters
| Name | In | Required | Description | | Name | In | Required | Description |
|------|----|----------|-------------| | ---- | ---- | -------- | ------------- |
| `id` | path | Yes | Media item ID | | `id` | path | Yes | Media item ID |
#### Request Body #### Request Body
@ -199,10 +199,9 @@ See `docs/api/openapi.json` for the full schema.
#### Responses #### Responses
| Status | Description | | Status | Description |
|--------|-------------| | ------ | ---------------- |
| 204 | Progress updated | | 204 | Progress updated |
| 400 | Bad request | | 400 | Bad request |
| 401 | Unauthorized | | 401 | Unauthorized |
--- ---

View file

@ -11,7 +11,7 @@ Media collections
#### Responses #### Responses
| Status | Description | | Status | Description |
|--------|-------------| | ------ | --------------------- |
| 200 | List of collections | | 200 | List of collections |
| 401 | Unauthorized | | 401 | Unauthorized |
| 500 | Internal server error | | 500 | Internal server error |
@ -31,7 +31,7 @@ See `docs/api/openapi.json` for the full schema.
#### Responses #### Responses
| Status | Description | | Status | Description |
|--------|-------------| | ------ | --------------------- |
| 200 | Collection created | | 200 | Collection created |
| 400 | Bad request | | 400 | Bad request |
| 401 | Unauthorized | | 401 | Unauthorized |
@ -47,13 +47,13 @@ See `docs/api/openapi.json` for the full schema.
#### Parameters #### Parameters
| Name | In | Required | Description | | Name | In | Required | Description |
|------|----|----------|-------------| | ---- | ---- | -------- | ------------- |
| `id` | path | Yes | Collection ID | | `id` | path | Yes | Collection ID |
#### Responses #### Responses
| Status | Description | | Status | Description |
|--------|-------------| | ------ | --------------------- |
| 200 | Collection | | 200 | Collection |
| 401 | Unauthorized | | 401 | Unauthorized |
| 404 | Not found | | 404 | Not found |
@ -68,13 +68,13 @@ See `docs/api/openapi.json` for the full schema.
#### Parameters #### Parameters
| Name | In | Required | Description | | Name | In | Required | Description |
|------|----|----------|-------------| | ---- | ---- | -------- | ------------- |
| `id` | path | Yes | Collection ID | | `id` | path | Yes | Collection ID |
#### Responses #### Responses
| Status | Description | | Status | Description |
|--------|-------------| | ------ | --------------------- |
| 200 | Collection deleted | | 200 | Collection deleted |
| 401 | Unauthorized | | 401 | Unauthorized |
| 403 | Forbidden | | 403 | Forbidden |
@ -90,13 +90,13 @@ See `docs/api/openapi.json` for the full schema.
#### Parameters #### Parameters
| Name | In | Required | Description | | Name | In | Required | Description |
|------|----|----------|-------------| | ---- | ---- | -------- | ------------- |
| `id` | path | Yes | Collection ID | | `id` | path | Yes | Collection ID |
#### Responses #### Responses
| Status | Description | | Status | Description |
|--------|-------------| | ------ | --------------------- |
| 200 | Collection members | | 200 | Collection members |
| 401 | Unauthorized | | 401 | Unauthorized |
| 404 | Not found | | 404 | Not found |
@ -111,7 +111,7 @@ See `docs/api/openapi.json` for the full schema.
#### Parameters #### Parameters
| Name | In | Required | Description | | Name | In | Required | Description |
|------|----|----------|-------------| | ---- | ---- | -------- | ------------- |
| `id` | path | Yes | Collection ID | | `id` | path | Yes | Collection ID |
#### Request Body #### Request Body
@ -123,7 +123,7 @@ See `docs/api/openapi.json` for the full schema.
#### Responses #### Responses
| Status | Description | | Status | Description |
|--------|-------------| | ------ | --------------------- |
| 200 | Member added | | 200 | Member added |
| 401 | Unauthorized | | 401 | Unauthorized |
| 403 | Forbidden | | 403 | Forbidden |
@ -139,14 +139,14 @@ See `docs/api/openapi.json` for the full schema.
#### Parameters #### Parameters
| Name | In | Required | Description | | Name | In | Required | Description |
|------|----|----------|-------------| | ---------- | ---- | -------- | ------------- |
| `id` | path | Yes | Collection ID | | `id` | path | Yes | Collection ID |
| `media_id` | path | Yes | Media item ID | | `media_id` | path | Yes | Media item ID |
#### Responses #### Responses
| Status | Description | | Status | Description |
|--------|-------------| | ------ | --------------------- |
| 200 | Member removed | | 200 | Member removed |
| 401 | Unauthorized | | 401 | Unauthorized |
| 403 | Forbidden | | 403 | Forbidden |
@ -154,4 +154,3 @@ See `docs/api/openapi.json` for the full schema.
| 500 | Internal server error | | 500 | Internal server error |
--- ---

13
docs/api/config.md vendored
View file

@ -11,7 +11,7 @@ Server configuration
#### Responses #### Responses
| Status | Description | | Status | Description |
|--------|-------------| | ------ | ---------------------------- |
| 200 | Current server configuration | | 200 | Current server configuration |
| 401 | Unauthorized | | 401 | Unauthorized |
| 403 | Forbidden | | 403 | Forbidden |
@ -32,7 +32,7 @@ See `docs/api/openapi.json` for the full schema.
#### Responses #### Responses
| Status | Description | | Status | Description |
|--------|-------------| | ------ | --------------------- |
| 200 | Updated configuration | | 200 | Updated configuration |
| 400 | Bad request | | 400 | Bad request |
| 401 | Unauthorized | | 401 | Unauthorized |
@ -54,7 +54,7 @@ See `docs/api/openapi.json` for the full schema.
#### Responses #### Responses
| Status | Description | | Status | Description |
|--------|-------------| | ------ | --------------------- |
| 200 | Updated configuration | | 200 | Updated configuration |
| 401 | Unauthorized | | 401 | Unauthorized |
| 403 | Forbidden | | 403 | Forbidden |
@ -75,7 +75,7 @@ See `docs/api/openapi.json` for the full schema.
#### Responses #### Responses
| Status | Description | | Status | Description |
|--------|-------------| | ------ | --------------------- |
| 200 | Updated configuration | | 200 | Updated configuration |
| 401 | Unauthorized | | 401 | Unauthorized |
| 403 | Forbidden | | 403 | Forbidden |
@ -90,7 +90,7 @@ See `docs/api/openapi.json` for the full schema.
#### Responses #### Responses
| Status | Description | | Status | Description |
|--------|-------------| | ------ | --------------------- |
| 200 | UI configuration | | 200 | UI configuration |
| 401 | Unauthorized | | 401 | Unauthorized |
| 500 | Internal server error | | 500 | Internal server error |
@ -110,11 +110,10 @@ See `docs/api/openapi.json` for the full schema.
#### Responses #### Responses
| Status | Description | | Status | Description |
|--------|-------------| | ------ | ------------------------ |
| 200 | Updated UI configuration | | 200 | Updated UI configuration |
| 401 | Unauthorized | | 401 | Unauthorized |
| 403 | Forbidden | | 403 | Forbidden |
| 500 | Internal server error | | 500 | Internal server error |
--- ---

View file

@ -11,7 +11,7 @@ Database administration
#### Responses #### Responses
| Status | Description | | Status | Description |
|--------|-------------| | ------ | --------------------- |
| 200 | Database cleared | | 200 | Database cleared |
| 401 | Unauthorized | | 401 | Unauthorized |
| 403 | Forbidden | | 403 | Forbidden |
@ -26,7 +26,7 @@ Database administration
#### Responses #### Responses
| Status | Description | | Status | Description |
|--------|-------------| | ------ | --------------------- |
| 200 | Database statistics | | 200 | Database statistics |
| 401 | Unauthorized | | 401 | Unauthorized |
| 403 | Forbidden | | 403 | Forbidden |
@ -41,11 +41,10 @@ Database administration
#### Responses #### Responses
| Status | Description | | Status | Description |
|--------|-------------| | ------ | --------------------- |
| 200 | Database vacuumed | | 200 | Database vacuumed |
| 401 | Unauthorized | | 401 | Unauthorized |
| 403 | Forbidden | | 403 | Forbidden |
| 500 | Internal server error | | 500 | Internal server error |
--- ---

View file

@ -11,10 +11,9 @@ Duplicate media detection
#### Responses #### Responses
| Status | Description | | Status | Description |
|--------|-------------| | ------ | --------------------- |
| 200 | Duplicate groups | | 200 | Duplicate groups |
| 401 | Unauthorized | | 401 | Unauthorized |
| 500 | Internal server error | | 500 | Internal server error |
--- ---

View file

@ -17,7 +17,7 @@ See `docs/api/openapi.json` for the full schema.
#### Responses #### Responses
| Status | Description | | Status | Description |
|--------|-------------| | ------ | ------------------------ |
| 200 | Enrichment job submitted | | 200 | Enrichment job submitted |
| 400 | Bad request | | 400 | Bad request |
| 401 | Unauthorized | | 401 | Unauthorized |
@ -33,13 +33,13 @@ See `docs/api/openapi.json` for the full schema.
#### Parameters #### Parameters
| Name | In | Required | Description | | Name | In | Required | Description |
|------|----|----------|-------------| | ---- | ---- | -------- | ------------- |
| `id` | path | Yes | Media item ID | | `id` | path | Yes | Media item ID |
#### Responses #### Responses
| Status | Description | | Status | Description |
|--------|-------------| | ------ | ------------------------ |
| 200 | Enrichment job submitted | | 200 | Enrichment job submitted |
| 401 | Unauthorized | | 401 | Unauthorized |
| 403 | Forbidden | | 403 | Forbidden |
@ -55,17 +55,16 @@ See `docs/api/openapi.json` for the full schema.
#### Parameters #### Parameters
| Name | In | Required | Description | | Name | In | Required | Description |
|------|----|----------|-------------| | ---- | ---- | -------- | ------------- |
| `id` | path | Yes | Media item ID | | `id` | path | Yes | Media item ID |
#### Responses #### Responses
| Status | Description | | Status | Description |
|--------|-------------| | ------ | --------------------- |
| 200 | External metadata | | 200 | External metadata |
| 401 | Unauthorized | | 401 | Unauthorized |
| 404 | Not found | | 404 | Not found |
| 500 | Internal server error | | 500 | Internal server error |
--- ---

5
docs/api/export.md vendored
View file

@ -11,7 +11,7 @@ Media library export
#### Responses #### Responses
| Status | Description | | Status | Description |
|--------|-------------| | ------ | --------------------- |
| 200 | Export job submitted | | 200 | Export job submitted |
| 401 | Unauthorized | | 401 | Unauthorized |
| 403 | Forbidden | | 403 | Forbidden |
@ -32,11 +32,10 @@ See `docs/api/openapi.json` for the full schema.
#### Responses #### Responses
| Status | Description | | Status | Description |
|--------|-------------| | ------ | --------------------- |
| 200 | Export job submitted | | 200 | Export job submitted |
| 401 | Unauthorized | | 401 | Unauthorized |
| 403 | Forbidden | | 403 | Forbidden |
| 500 | Internal server error | | 500 | Internal server error |
--- ---

17
docs/api/health.md vendored
View file

@ -13,7 +13,7 @@ Comprehensive health check - includes database, filesystem, and cache status
#### Responses #### Responses
| Status | Description | | Status | Description |
|--------|-------------| | ------ | ------------- |
| 200 | Health status | | 200 | Health status |
--- ---
@ -25,39 +25,38 @@ Comprehensive health check - includes database, filesystem, and cache status
#### Responses #### Responses
| Status | Description | | Status | Description |
|--------|-------------| | ------ | ---------------------- |
| 200 | Detailed health status | | 200 | Detailed health status |
--- ---
### GET /api/v1/health/live ### GET /api/v1/health/live
Liveness probe - just checks if the server is running Liveness probe - just checks if the server is running Returns 200 OK if the
Returns 200 OK if the server process is alive server process is alive
**Authentication:** Required (Bearer JWT) **Authentication:** Required (Bearer JWT)
#### Responses #### Responses
| Status | Description | | Status | Description |
|--------|-------------| | ------ | --------------- |
| 200 | Server is alive | | 200 | Server is alive |
--- ---
### GET /api/v1/health/ready ### GET /api/v1/health/ready
Readiness probe - checks if the server can serve requests Readiness probe - checks if the server can serve requests Returns 200 OK if
Returns 200 OK if database is accessible database is accessible
**Authentication:** Required (Bearer JWT) **Authentication:** Required (Bearer JWT)
#### Responses #### Responses
| Status | Description | | Status | Description |
|--------|-------------| | ------ | ---------------- |
| 200 | Server is ready | | 200 | Server is ready |
| 503 | Server not ready | | 503 | Server not ready |
--- ---

11
docs/api/integrity.md vendored
View file

@ -11,7 +11,7 @@ Library integrity checks and repairs
#### Responses #### Responses
| Status | Description | | Status | Description |
|--------|-------------| | ------ | ------------------------------ |
| 200 | Orphan detection job submitted | | 200 | Orphan detection job submitted |
| 401 | Unauthorized | | 401 | Unauthorized |
| 403 | Forbidden | | 403 | Forbidden |
@ -32,7 +32,7 @@ See `docs/api/openapi.json` for the full schema.
#### Responses #### Responses
| Status | Description | | Status | Description |
|--------|-------------| | ------ | --------------------- |
| 200 | Orphans resolved | | 200 | Orphans resolved |
| 401 | Unauthorized | | 401 | Unauthorized |
| 403 | Forbidden | | 403 | Forbidden |
@ -47,7 +47,7 @@ See `docs/api/openapi.json` for the full schema.
#### Responses #### Responses
| Status | Description | | Status | Description |
|--------|-------------| | ------ | ------------------------------- |
| 200 | Thumbnail cleanup job submitted | | 200 | Thumbnail cleanup job submitted |
| 401 | Unauthorized | | 401 | Unauthorized |
| 403 | Forbidden | | 403 | Forbidden |
@ -68,7 +68,7 @@ See `docs/api/openapi.json` for the full schema.
#### Responses #### Responses
| Status | Description | | Status | Description |
|--------|-------------| | ------ | ---------------------------------- |
| 200 | Thumbnail generation job submitted | | 200 | Thumbnail generation job submitted |
| 401 | Unauthorized | | 401 | Unauthorized |
| 403 | Forbidden | | 403 | Forbidden |
@ -89,11 +89,10 @@ See `docs/api/openapi.json` for the full schema.
#### Responses #### Responses
| Status | Description | | Status | Description |
|--------|-------------| | ------ | ------------------------------------ |
| 200 | Integrity verification job submitted | | 200 | Integrity verification job submitted |
| 401 | Unauthorized | | 401 | Unauthorized |
| 403 | Forbidden | | 403 | Forbidden |
| 500 | Internal server error | | 500 | Internal server error |
--- ---

11
docs/api/jobs.md vendored
View file

@ -11,7 +11,7 @@ Background job management
#### Responses #### Responses
| Status | Description | | Status | Description |
|--------|-------------| | ------ | ------------ |
| 200 | List of jobs | | 200 | List of jobs |
| 401 | Unauthorized | | 401 | Unauthorized |
| 403 | Forbidden | | 403 | Forbidden |
@ -25,13 +25,13 @@ Background job management
#### Parameters #### Parameters
| Name | In | Required | Description | | Name | In | Required | Description |
|------|----|----------|-------------| | ---- | ---- | -------- | ----------- |
| `id` | path | Yes | Job ID | | `id` | path | Yes | Job ID |
#### Responses #### Responses
| Status | Description | | Status | Description |
|--------|-------------| | ------ | ------------ |
| 200 | Job details | | 200 | Job details |
| 401 | Unauthorized | | 401 | Unauthorized |
| 403 | Forbidden | | 403 | Forbidden |
@ -46,17 +46,16 @@ Background job management
#### Parameters #### Parameters
| Name | In | Required | Description | | Name | In | Required | Description |
|------|----|----------|-------------| | ---- | ---- | -------- | ----------- |
| `id` | path | Yes | Job ID | | `id` | path | Yes | Job ID |
#### Responses #### Responses
| Status | Description | | Status | Description |
|--------|-------------| | ------ | ------------- |
| 200 | Job cancelled | | 200 | Job cancelled |
| 401 | Unauthorized | | 401 | Unauthorized |
| 403 | Forbidden | | 403 | Forbidden |
| 404 | Not found | | 404 | Not found |
--- ---

89
docs/api/media.md vendored
View file

@ -11,7 +11,7 @@ Media item management
#### Parameters #### Parameters
| Name | In | Required | Description | | Name | In | Required | Description |
|------|----|----------|-------------| | -------- | ----- | -------- | ----------------- |
| `offset` | query | No | Pagination offset | | `offset` | query | No | Pagination offset |
| `limit` | query | No | Page size | | `limit` | query | No | Page size |
| `sort` | query | No | Sort field | | `sort` | query | No | Sort field |
@ -19,7 +19,7 @@ Media item management
#### Responses #### Responses
| Status | Description | | Status | Description |
|--------|-------------| | ------ | --------------------- |
| 200 | List of media items | | 200 | List of media items |
| 401 | Unauthorized | | 401 | Unauthorized |
| 500 | Internal server error | | 500 | Internal server error |
@ -33,7 +33,7 @@ Media item management
#### Responses #### Responses
| Status | Description | | Status | Description |
|--------|-------------| | ------ | --------------------- |
| 200 | All media deleted | | 200 | All media deleted |
| 401 | Unauthorized | | 401 | Unauthorized |
| 403 | Forbidden | | 403 | Forbidden |
@ -54,7 +54,7 @@ See `docs/api/openapi.json` for the full schema.
#### Responses #### Responses
| Status | Description | | Status | Description |
|--------|-------------| | ------ | ----------------------- |
| 200 | Batch collection result | | 200 | Batch collection result |
| 400 | Bad request | | 400 | Bad request |
| 401 | Unauthorized | | 401 | Unauthorized |
@ -76,7 +76,7 @@ See `docs/api/openapi.json` for the full schema.
#### Responses #### Responses
| Status | Description | | Status | Description |
|--------|-------------| | ------ | --------------------- |
| 200 | Batch delete result | | 200 | Batch delete result |
| 400 | Bad request | | 400 | Bad request |
| 401 | Unauthorized | | 401 | Unauthorized |
@ -98,7 +98,7 @@ See `docs/api/openapi.json` for the full schema.
#### Responses #### Responses
| Status | Description | | Status | Description |
|--------|-------------| | ------ | --------------------- |
| 200 | Batch move result | | 200 | Batch move result |
| 400 | Bad request | | 400 | Bad request |
| 401 | Unauthorized | | 401 | Unauthorized |
@ -120,7 +120,7 @@ See `docs/api/openapi.json` for the full schema.
#### Responses #### Responses
| Status | Description | | Status | Description |
|--------|-------------| | ------ | --------------------- |
| 200 | Batch tag result | | 200 | Batch tag result |
| 400 | Bad request | | 400 | Bad request |
| 401 | Unauthorized | | 401 | Unauthorized |
@ -142,7 +142,7 @@ See `docs/api/openapi.json` for the full schema.
#### Responses #### Responses
| Status | Description | | Status | Description |
|--------|-------------| | ------ | --------------------- |
| 200 | Batch update result | | 200 | Batch update result |
| 400 | Bad request | | 400 | Bad request |
| 401 | Unauthorized | | 401 | Unauthorized |
@ -158,7 +158,7 @@ See `docs/api/openapi.json` for the full schema.
#### Responses #### Responses
| Status | Description | | Status | Description |
|--------|-------------| | ------ | --------------------- |
| 200 | Media count | | 200 | Media count |
| 401 | Unauthorized | | 401 | Unauthorized |
| 500 | Internal server error | | 500 | Internal server error |
@ -178,7 +178,7 @@ See `docs/api/openapi.json` for the full schema.
#### Responses #### Responses
| Status | Description | | Status | Description |
|--------|-------------| | ------ | --------------------- |
| 200 | Media imported | | 200 | Media imported |
| 400 | Bad request | | 400 | Bad request |
| 401 | Unauthorized | | 401 | Unauthorized |
@ -200,7 +200,7 @@ See `docs/api/openapi.json` for the full schema.
#### Responses #### Responses
| Status | Description | | Status | Description |
|--------|-------------| | ------ | --------------------- |
| 200 | Batch import results | | 200 | Batch import results |
| 400 | Bad request | | 400 | Bad request |
| 401 | Unauthorized | | 401 | Unauthorized |
@ -222,7 +222,7 @@ See `docs/api/openapi.json` for the full schema.
#### Responses #### Responses
| Status | Description | | Status | Description |
|--------|-------------| | ------ | ------------------------ |
| 200 | Directory import results | | 200 | Directory import results |
| 400 | Bad request | | 400 | Bad request |
| 401 | Unauthorized | | 401 | Unauthorized |
@ -244,7 +244,7 @@ See `docs/api/openapi.json` for the full schema.
#### Responses #### Responses
| Status | Description | | Status | Description |
|--------|-------------| | ------ | --------------------- |
| 200 | Media imported | | 200 | Media imported |
| 400 | Bad request | | 400 | Bad request |
| 401 | Unauthorized | | 401 | Unauthorized |
@ -266,7 +266,7 @@ See `docs/api/openapi.json` for the full schema.
#### Responses #### Responses
| Status | Description | | Status | Description |
|--------|-------------| | ------ | --------------------- |
| 200 | Directory preview | | 200 | Directory preview |
| 400 | Bad request | | 400 | Bad request |
| 401 | Unauthorized | | 401 | Unauthorized |
@ -282,14 +282,14 @@ See `docs/api/openapi.json` for the full schema.
#### Parameters #### Parameters
| Name | In | Required | Description | | Name | In | Required | Description |
|------|----|----------|-------------| | -------- | ----- | -------- | ----------------- |
| `offset` | query | No | Pagination offset | | `offset` | query | No | Pagination offset |
| `limit` | query | No | Page size | | `limit` | query | No | Page size |
#### Responses #### Responses
| Status | Description | | Status | Description |
|--------|-------------| | ------ | --------------------- |
| 200 | Trashed media items | | 200 | Trashed media items |
| 401 | Unauthorized | | 401 | Unauthorized |
| 500 | Internal server error | | 500 | Internal server error |
@ -303,7 +303,7 @@ See `docs/api/openapi.json` for the full schema.
#### Responses #### Responses
| Status | Description | | Status | Description |
|--------|-------------| | ------ | --------------------- |
| 200 | Trash emptied | | 200 | Trash emptied |
| 401 | Unauthorized | | 401 | Unauthorized |
| 403 | Forbidden | | 403 | Forbidden |
@ -318,7 +318,7 @@ See `docs/api/openapi.json` for the full schema.
#### Responses #### Responses
| Status | Description | | Status | Description |
|--------|-------------| | ------ | --------------------- |
| 200 | Trash info | | 200 | Trash info |
| 401 | Unauthorized | | 401 | Unauthorized |
| 500 | Internal server error | | 500 | Internal server error |
@ -332,13 +332,13 @@ See `docs/api/openapi.json` for the full schema.
#### Parameters #### Parameters
| Name | In | Required | Description | | Name | In | Required | Description |
|------|----|----------|-------------| | ---- | ---- | -------- | ------------- |
| `id` | path | Yes | Media item ID | | `id` | path | Yes | Media item ID |
#### Responses #### Responses
| Status | Description | | Status | Description |
|--------|-------------| | ------ | --------------------- |
| 200 | Media item | | 200 | Media item |
| 401 | Unauthorized | | 401 | Unauthorized |
| 404 | Not found | | 404 | Not found |
@ -353,7 +353,7 @@ See `docs/api/openapi.json` for the full schema.
#### Parameters #### Parameters
| Name | In | Required | Description | | Name | In | Required | Description |
|------|----|----------|-------------| | ---- | ---- | -------- | ------------- |
| `id` | path | Yes | Media item ID | | `id` | path | Yes | Media item ID |
#### Request Body #### Request Body
@ -365,7 +365,7 @@ See `docs/api/openapi.json` for the full schema.
#### Responses #### Responses
| Status | Description | | Status | Description |
|--------|-------------| | ------ | --------------------- |
| 200 | Updated media item | | 200 | Updated media item |
| 400 | Bad request | | 400 | Bad request |
| 401 | Unauthorized | | 401 | Unauthorized |
@ -382,13 +382,13 @@ See `docs/api/openapi.json` for the full schema.
#### Parameters #### Parameters
| Name | In | Required | Description | | Name | In | Required | Description |
|------|----|----------|-------------| | ---- | ---- | -------- | ------------- |
| `id` | path | Yes | Media item ID | | `id` | path | Yes | Media item ID |
#### Responses #### Responses
| Status | Description | | Status | Description |
|--------|-------------| | ------ | --------------------- |
| 200 | Media deleted | | 200 | Media deleted |
| 401 | Unauthorized | | 401 | Unauthorized |
| 403 | Forbidden | | 403 | Forbidden |
@ -404,7 +404,7 @@ See `docs/api/openapi.json` for the full schema.
#### Parameters #### Parameters
| Name | In | Required | Description | | Name | In | Required | Description |
|------|----|----------|-------------| | ---- | ---- | -------- | ------------- |
| `id` | path | Yes | Media item ID | | `id` | path | Yes | Media item ID |
#### Request Body #### Request Body
@ -416,7 +416,7 @@ See `docs/api/openapi.json` for the full schema.
#### Responses #### Responses
| Status | Description | | Status | Description |
|--------|-------------| | ------ | --------------------- |
| 200 | Custom field set | | 200 | Custom field set |
| 400 | Bad request | | 400 | Bad request |
| 401 | Unauthorized | | 401 | Unauthorized |
@ -433,14 +433,14 @@ See `docs/api/openapi.json` for the full schema.
#### Parameters #### Parameters
| Name | In | Required | Description | | Name | In | Required | Description |
|------|----|----------|-------------| | ------ | ---- | -------- | ----------------- |
| `id` | path | Yes | Media item ID | | `id` | path | Yes | Media item ID |
| `name` | path | Yes | Custom field name | | `name` | path | Yes | Custom field name |
#### Responses #### Responses
| Status | Description | | Status | Description |
|--------|-------------| | ------ | --------------------- |
| 200 | Custom field deleted | | 200 | Custom field deleted |
| 401 | Unauthorized | | 401 | Unauthorized |
| 403 | Forbidden | | 403 | Forbidden |
@ -456,7 +456,7 @@ See `docs/api/openapi.json` for the full schema.
#### Parameters #### Parameters
| Name | In | Required | Description | | Name | In | Required | Description |
|------|----|----------|-------------| | ---- | ---- | -------- | ------------- |
| `id` | path | Yes | Media item ID | | `id` | path | Yes | Media item ID |
#### Request Body #### Request Body
@ -468,7 +468,7 @@ See `docs/api/openapi.json` for the full schema.
#### Responses #### Responses
| Status | Description | | Status | Description |
|--------|-------------| | ------ | --------------------- |
| 200 | Moved media item | | 200 | Moved media item |
| 400 | Bad request | | 400 | Bad request |
| 401 | Unauthorized | | 401 | Unauthorized |
@ -485,13 +485,13 @@ See `docs/api/openapi.json` for the full schema.
#### Parameters #### Parameters
| Name | In | Required | Description | | Name | In | Required | Description |
|------|----|----------|-------------| | ---- | ---- | -------- | ------------- |
| `id` | path | Yes | Media item ID | | `id` | path | Yes | Media item ID |
#### Responses #### Responses
| Status | Description | | Status | Description |
|--------|-------------| | ------ | --------------------- |
| 200 | Media opened | | 200 | Media opened |
| 401 | Unauthorized | | 401 | Unauthorized |
| 404 | Not found | | 404 | Not found |
@ -506,14 +506,14 @@ See `docs/api/openapi.json` for the full schema.
#### Parameters #### Parameters
| Name | In | Required | Description | | Name | In | Required | Description |
|------|----|----------|-------------| | ----------- | ----- | -------- | ------------------------------------ |
| `id` | path | Yes | Media item ID | | `id` | path | Yes | Media item ID |
| `permanent` | query | No | Set to 'true' for permanent deletion | | `permanent` | query | No | Set to 'true' for permanent deletion |
#### Responses #### Responses
| Status | Description | | Status | Description |
|--------|-------------| | ------ | --------------------- |
| 200 | Media deleted | | 200 | Media deleted |
| 401 | Unauthorized | | 401 | Unauthorized |
| 403 | Forbidden | | 403 | Forbidden |
@ -529,7 +529,7 @@ See `docs/api/openapi.json` for the full schema.
#### Parameters #### Parameters
| Name | In | Required | Description | | Name | In | Required | Description |
|------|----|----------|-------------| | ---- | ---- | -------- | ------------- |
| `id` | path | Yes | Media item ID | | `id` | path | Yes | Media item ID |
#### Request Body #### Request Body
@ -541,7 +541,7 @@ See `docs/api/openapi.json` for the full schema.
#### Responses #### Responses
| Status | Description | | Status | Description |
|--------|-------------| | ------ | --------------------- |
| 200 | Renamed media item | | 200 | Renamed media item |
| 400 | Bad request | | 400 | Bad request |
| 401 | Unauthorized | | 401 | Unauthorized |
@ -558,13 +558,13 @@ See `docs/api/openapi.json` for the full schema.
#### Parameters #### Parameters
| Name | In | Required | Description | | Name | In | Required | Description |
|------|----|----------|-------------| | ---- | ---- | -------- | ------------- |
| `id` | path | Yes | Media item ID | | `id` | path | Yes | Media item ID |
#### Responses #### Responses
| Status | Description | | Status | Description |
|--------|-------------| | ------ | --------------------- |
| 200 | Media restored | | 200 | Media restored |
| 401 | Unauthorized | | 401 | Unauthorized |
| 403 | Forbidden | | 403 | Forbidden |
@ -580,13 +580,13 @@ See `docs/api/openapi.json` for the full schema.
#### Parameters #### Parameters
| Name | In | Required | Description | | Name | In | Required | Description |
|------|----|----------|-------------| | ---- | ---- | -------- | ------------- |
| `id` | path | Yes | Media item ID | | `id` | path | Yes | Media item ID |
#### Responses #### Responses
| Status | Description | | Status | Description |
|--------|-------------| | ------ | --------------------- |
| 200 | Media stream | | 200 | Media stream |
| 206 | Partial content | | 206 | Partial content |
| 401 | Unauthorized | | 401 | Unauthorized |
@ -602,13 +602,13 @@ See `docs/api/openapi.json` for the full schema.
#### Parameters #### Parameters
| Name | In | Required | Description | | Name | In | Required | Description |
|------|----|----------|-------------| | ---- | ---- | -------- | ------------- |
| `id` | path | Yes | Media item ID | | `id` | path | Yes | Media item ID |
#### Responses #### Responses
| Status | Description | | Status | Description |
|--------|-------------| | ------ | --------------------- |
| 200 | Thumbnail image | | 200 | Thumbnail image |
| 401 | Unauthorized | | 401 | Unauthorized |
| 404 | Not found | | 404 | Not found |
@ -623,13 +623,13 @@ See `docs/api/openapi.json` for the full schema.
#### Parameters #### Parameters
| Name | In | Required | Description | | Name | In | Required | Description |
|------|----|----------|-------------| | ---- | ---- | -------- | ------------- |
| `id` | path | Yes | Media item ID | | `id` | path | Yes | Media item ID |
#### Responses #### Responses
| Status | Description | | Status | Description |
|--------|-------------| | ------ | --------------------- |
| 200 | Media moved to trash | | 200 | Media moved to trash |
| 401 | Unauthorized | | 401 | Unauthorized |
| 403 | Forbidden | | 403 | Forbidden |
@ -637,4 +637,3 @@ See `docs/api/openapi.json` for the full schema.
| 500 | Internal server error | | 500 | Internal server error |
--- ---

21
docs/api/notes.md vendored
View file

@ -15,13 +15,13 @@ GET /api/v1/media/{id}/backlinks
#### Parameters #### Parameters
| Name | In | Required | Description | | Name | In | Required | Description |
|------|----|----------|-------------| | ---- | ---- | -------- | ------------- |
| `id` | path | Yes | Media item ID | | `id` | path | Yes | Media item ID |
#### Responses #### Responses
| Status | Description | | Status | Description |
|--------|-------------| | ------ | --------------------- |
| 200 | Backlinks | | 200 | Backlinks |
| 401 | Unauthorized | | 401 | Unauthorized |
| 404 | Not found | | 404 | Not found |
@ -40,13 +40,13 @@ GET /api/v1/media/{id}/outgoing-links
#### Parameters #### Parameters
| Name | In | Required | Description | | Name | In | Required | Description |
|------|----|----------|-------------| | ---- | ---- | -------- | ------------- |
| `id` | path | Yes | Media item ID | | `id` | path | Yes | Media item ID |
#### Responses #### Responses
| Status | Description | | Status | Description |
|--------|-------------| | ------ | --------------------- |
| 200 | Outgoing links | | 200 | Outgoing links |
| 401 | Unauthorized | | 401 | Unauthorized |
| 404 | Not found | | 404 | Not found |
@ -65,13 +65,13 @@ POST /api/v1/media/{id}/reindex-links
#### Parameters #### Parameters
| Name | In | Required | Description | | Name | In | Required | Description |
|------|----|----------|-------------| | ---- | ---- | -------- | ------------- |
| `id` | path | Yes | Media item ID | | `id` | path | Yes | Media item ID |
#### Responses #### Responses
| Status | Description | | Status | Description |
|--------|-------------| | ------ | --------------------- |
| 200 | Links reindexed | | 200 | Links reindexed |
| 401 | Unauthorized | | 401 | Unauthorized |
| 404 | Not found | | 404 | Not found |
@ -90,14 +90,14 @@ GET /api/v1/notes/graph?center={uuid}&depth={n}
#### Parameters #### Parameters
| Name | In | Required | Description | | Name | In | Required | Description |
|------|----|----------|-------------| | -------- | ----- | -------- | ---------------------------------- |
| `center` | query | No | Center node ID | | `center` | query | No | Center node ID |
| `depth` | query | No | Traversal depth (max 5, default 2) | | `depth` | query | No | Traversal depth (max 5, default 2) |
#### Responses #### Responses
| Status | Description | | Status | Description |
|--------|-------------| | ------ | --------------------- |
| 200 | Graph data | | 200 | Graph data |
| 401 | Unauthorized | | 401 | Unauthorized |
| 500 | Internal server error | | 500 | Internal server error |
@ -115,7 +115,7 @@ POST /api/v1/notes/resolve-links
#### Responses #### Responses
| Status | Description | | Status | Description |
|--------|-------------| | ------ | --------------------- |
| 200 | Links resolved | | 200 | Links resolved |
| 401 | Unauthorized | | 401 | Unauthorized |
| 500 | Internal server error | | 500 | Internal server error |
@ -133,10 +133,9 @@ GET /api/v1/notes/unresolved-count
#### Responses #### Responses
| Status | Description | | Status | Description |
|--------|-------------| | ------ | --------------------- |
| 200 | Unresolved link count | | 200 | Unresolved link count |
| 401 | Unauthorized | | 401 | Unauthorized |
| 500 | Internal server error | | 500 | Internal server error |
--- ---

63
docs/api/openapi.json vendored
View file

@ -1275,7 +1275,7 @@
{ {
"name": "status", "name": "status",
"in": "query", "in": "query",
"description": "Filter by reading status", "description": "Filter by reading status. Valid values: to_read, reading, completed, abandoned",
"required": false, "required": false,
"schema": { "schema": {
"type": "string" "type": "string"
@ -4704,14 +4704,11 @@
], ],
"responses": { "responses": {
"200": { "200": {
"description": "Subtitles", "description": "Subtitles and available embedded tracks",
"content": { "content": {
"application/json": { "application/json": {
"schema": { "schema": {
"type": "array", "$ref": "#/components/schemas/SubtitleListResponse"
"items": {
"$ref": "#/components/schemas/SubtitleResponse"
}
} }
} }
} }
@ -8529,6 +8526,7 @@
"integer", "integer",
"null" "null"
], ],
"format": "int32",
"minimum": 0 "minimum": 0
} }
} }
@ -11787,6 +11785,28 @@
], ],
"description": "Response for accessing shared content.\nSingle-media shares return the media object directly (backwards compatible).\nCollection/Tag/SavedSearch shares return a list of items." "description": "Response for accessing shared content.\nSingle-media shares return the media object directly (backwards compatible).\nCollection/Tag/SavedSearch shares return a list of items."
}, },
"SubtitleListResponse": {
"type": "object",
"description": "Response for listing subtitles on a media item.",
"required": [
"subtitles",
"available_tracks"
],
"properties": {
"available_tracks": {
"type": "array",
"items": {
"$ref": "#/components/schemas/SubtitleTrackInfoResponse"
}
},
"subtitles": {
"type": "array",
"items": {
"$ref": "#/components/schemas/SubtitleResponse"
}
}
}
},
"SubtitleResponse": { "SubtitleResponse": {
"type": "object", "type": "object",
"required": [ "required": [
@ -11829,10 +11849,41 @@
"integer", "integer",
"null" "null"
], ],
"format": "int32",
"minimum": 0 "minimum": 0
} }
} }
}, },
"SubtitleTrackInfoResponse": {
"type": "object",
"description": "Information about an embedded subtitle track available for extraction.",
"required": [
"index",
"format"
],
"properties": {
"format": {
"type": "string"
},
"index": {
"type": "integer",
"format": "int32",
"minimum": 0
},
"language": {
"type": [
"string",
"null"
]
},
"title": {
"type": [
"string",
"null"
]
}
}
},
"SyncChangeResponse": { "SyncChangeResponse": {
"type": "object", "type": "object",
"required": [ "required": [

9
docs/api/photos.md vendored
View file

@ -13,7 +13,7 @@ Get photos in a bounding box for map view
#### Parameters #### Parameters
| Name | In | Required | Description | | Name | In | Required | Description |
|------|----|----------|-------------| | ------ | ----- | -------- | ------------------------ |
| `lat1` | query | Yes | Bounding box latitude 1 | | `lat1` | query | Yes | Bounding box latitude 1 |
| `lon1` | query | Yes | Bounding box longitude 1 | | `lon1` | query | Yes | Bounding box longitude 1 |
| `lat2` | query | Yes | Bounding box latitude 2 | | `lat2` | query | Yes | Bounding box latitude 2 |
@ -22,7 +22,7 @@ Get photos in a bounding box for map view
#### Responses #### Responses
| Status | Description | | Status | Description |
|--------|-------------| | ------ | --------------------- |
| 200 | Map markers | | 200 | Map markers |
| 400 | Bad request | | 400 | Bad request |
| 401 | Unauthorized | | 401 | Unauthorized |
@ -39,7 +39,7 @@ Get timeline of photos grouped by date
#### Parameters #### Parameters
| Name | In | Required | Description | | Name | In | Required | Description |
|------|----|----------|-------------| | ---------- | ----- | -------- | -------------------------- |
| `group_by` | query | No | Grouping: day, month, year | | `group_by` | query | No | Grouping: day, month, year |
| `year` | query | No | Filter by year | | `year` | query | No | Filter by year |
| `month` | query | No | Filter by month | | `month` | query | No | Filter by month |
@ -48,10 +48,9 @@ Get timeline of photos grouped by date
#### Responses #### Responses
| Status | Description | | Status | Description |
|--------|-------------| | ------ | --------------------- |
| 200 | Photo timeline groups | | 200 | Photo timeline groups |
| 401 | Unauthorized | | 401 | Unauthorized |
| 500 | Internal server error | | 500 | Internal server error |
--- ---

37
docs/api/playlists.md vendored
View file

@ -11,7 +11,7 @@ Media playlists
#### Responses #### Responses
| Status | Description | | Status | Description |
|--------|-------------| | ------ | --------------------- |
| 200 | List of playlists | | 200 | List of playlists |
| 401 | Unauthorized | | 401 | Unauthorized |
| 500 | Internal server error | | 500 | Internal server error |
@ -31,7 +31,7 @@ See `docs/api/openapi.json` for the full schema.
#### Responses #### Responses
| Status | Description | | Status | Description |
|--------|-------------| | ------ | --------------------- |
| 200 | Playlist created | | 200 | Playlist created |
| 400 | Bad request | | 400 | Bad request |
| 401 | Unauthorized | | 401 | Unauthorized |
@ -46,13 +46,13 @@ See `docs/api/openapi.json` for the full schema.
#### Parameters #### Parameters
| Name | In | Required | Description | | Name | In | Required | Description |
|------|----|----------|-------------| | ---- | ---- | -------- | ----------- |
| `id` | path | Yes | Playlist ID | | `id` | path | Yes | Playlist ID |
#### Responses #### Responses
| Status | Description | | Status | Description |
|--------|-------------| | ------ | ---------------- |
| 200 | Playlist details | | 200 | Playlist details |
| 401 | Unauthorized | | 401 | Unauthorized |
| 403 | Forbidden | | 403 | Forbidden |
@ -67,7 +67,7 @@ See `docs/api/openapi.json` for the full schema.
#### Parameters #### Parameters
| Name | In | Required | Description | | Name | In | Required | Description |
|------|----|----------|-------------| | ---- | ---- | -------- | ----------- |
| `id` | path | Yes | Playlist ID | | `id` | path | Yes | Playlist ID |
#### Request Body #### Request Body
@ -79,7 +79,7 @@ See `docs/api/openapi.json` for the full schema.
#### Responses #### Responses
| Status | Description | | Status | Description |
|--------|-------------| | ------ | ---------------- |
| 200 | Playlist updated | | 200 | Playlist updated |
| 400 | Bad request | | 400 | Bad request |
| 401 | Unauthorized | | 401 | Unauthorized |
@ -95,13 +95,13 @@ See `docs/api/openapi.json` for the full schema.
#### Parameters #### Parameters
| Name | In | Required | Description | | Name | In | Required | Description |
|------|----|----------|-------------| | ---- | ---- | -------- | ----------- |
| `id` | path | Yes | Playlist ID | | `id` | path | Yes | Playlist ID |
#### Responses #### Responses
| Status | Description | | Status | Description |
|--------|-------------| | ------ | ---------------- |
| 200 | Playlist deleted | | 200 | Playlist deleted |
| 401 | Unauthorized | | 401 | Unauthorized |
| 403 | Forbidden | | 403 | Forbidden |
@ -116,13 +116,13 @@ See `docs/api/openapi.json` for the full schema.
#### Parameters #### Parameters
| Name | In | Required | Description | | Name | In | Required | Description |
|------|----|----------|-------------| | ---- | ---- | -------- | ----------- |
| `id` | path | Yes | Playlist ID | | `id` | path | Yes | Playlist ID |
#### Responses #### Responses
| Status | Description | | Status | Description |
|--------|-------------| | ------ | -------------- |
| 200 | Playlist items | | 200 | Playlist items |
| 401 | Unauthorized | | 401 | Unauthorized |
| 403 | Forbidden | | 403 | Forbidden |
@ -137,7 +137,7 @@ See `docs/api/openapi.json` for the full schema.
#### Parameters #### Parameters
| Name | In | Required | Description | | Name | In | Required | Description |
|------|----|----------|-------------| | ---- | ---- | -------- | ----------- |
| `id` | path | Yes | Playlist ID | | `id` | path | Yes | Playlist ID |
#### Request Body #### Request Body
@ -149,7 +149,7 @@ See `docs/api/openapi.json` for the full schema.
#### Responses #### Responses
| Status | Description | | Status | Description |
|--------|-------------| | ------ | ------------ |
| 200 | Item added | | 200 | Item added |
| 401 | Unauthorized | | 401 | Unauthorized |
| 403 | Forbidden | | 403 | Forbidden |
@ -164,7 +164,7 @@ See `docs/api/openapi.json` for the full schema.
#### Parameters #### Parameters
| Name | In | Required | Description | | Name | In | Required | Description |
|------|----|----------|-------------| | ---- | ---- | -------- | ----------- |
| `id` | path | Yes | Playlist ID | | `id` | path | Yes | Playlist ID |
#### Request Body #### Request Body
@ -176,7 +176,7 @@ See `docs/api/openapi.json` for the full schema.
#### Responses #### Responses
| Status | Description | | Status | Description |
|--------|-------------| | ------ | -------------- |
| 200 | Item reordered | | 200 | Item reordered |
| 401 | Unauthorized | | 401 | Unauthorized |
| 403 | Forbidden | | 403 | Forbidden |
@ -191,14 +191,14 @@ See `docs/api/openapi.json` for the full schema.
#### Parameters #### Parameters
| Name | In | Required | Description | | Name | In | Required | Description |
|------|----|----------|-------------| | ---------- | ---- | -------- | ------------- |
| `id` | path | Yes | Playlist ID | | `id` | path | Yes | Playlist ID |
| `media_id` | path | Yes | Media item ID | | `media_id` | path | Yes | Media item ID |
#### Responses #### Responses
| Status | Description | | Status | Description |
|--------|-------------| | ------ | ------------ |
| 200 | Item removed | | 200 | Item removed |
| 401 | Unauthorized | | 401 | Unauthorized |
| 403 | Forbidden | | 403 | Forbidden |
@ -213,17 +213,16 @@ See `docs/api/openapi.json` for the full schema.
#### Parameters #### Parameters
| Name | In | Required | Description | | Name | In | Required | Description |
|------|----|----------|-------------| | ---- | ---- | -------- | ----------- |
| `id` | path | Yes | Playlist ID | | `id` | path | Yes | Playlist ID |
#### Responses #### Responses
| Status | Description | | Status | Description |
|--------|-------------| | ------ | ----------------------- |
| 200 | Shuffled playlist items | | 200 | Shuffled playlist items |
| 401 | Unauthorized | | 401 | Unauthorized |
| 403 | Forbidden | | 403 | Forbidden |
| 404 | Not found | | 404 | Not found |
--- ---

29
docs/api/plugins.md vendored
View file

@ -13,7 +13,7 @@ List all installed plugins
#### Responses #### Responses
| Status | Description | | Status | Description |
|--------|-------------| | ------ | --------------------- |
| 200 | List of plugins | | 200 | List of plugins |
| 401 | Unauthorized | | 401 | Unauthorized |
| 500 | Internal server error | | 500 | Internal server error |
@ -35,7 +35,7 @@ See `docs/api/openapi.json` for the full schema.
#### Responses #### Responses
| Status | Description | | Status | Description |
|--------|-------------| | ------ | ---------------- |
| 200 | Plugin installed | | 200 | Plugin installed |
| 400 | Bad request | | 400 | Bad request |
| 401 | Unauthorized | | 401 | Unauthorized |
@ -59,7 +59,7 @@ See `docs/api/openapi.json` for the full schema.
#### Responses #### Responses
| Status | Description | | Status | Description |
|--------|-------------| | ------ | -------------- |
| 200 | Event received | | 200 | Event received |
| 401 | Unauthorized | | 401 | Unauthorized |
@ -74,7 +74,7 @@ List all UI pages provided by loaded plugins
#### Responses #### Responses
| Status | Description | | Status | Description |
|--------|-------------| | ------ | --------------- |
| 200 | Plugin UI pages | | 200 | Plugin UI pages |
| 401 | Unauthorized | | 401 | Unauthorized |
@ -89,7 +89,7 @@ List merged CSS custom property overrides from all enabled plugins
#### Responses #### Responses
| Status | Description | | Status | Description |
|--------|-------------| | ------ | -------------------------- |
| 200 | Plugin UI theme extensions | | 200 | Plugin UI theme extensions |
| 401 | Unauthorized | | 401 | Unauthorized |
@ -104,7 +104,7 @@ List all UI widgets provided by loaded plugins
#### Responses #### Responses
| Status | Description | | Status | Description |
|--------|-------------| | ------ | ----------------- |
| 200 | Plugin UI widgets | | 200 | Plugin UI widgets |
| 401 | Unauthorized | | 401 | Unauthorized |
@ -119,13 +119,13 @@ Get a specific plugin by ID
#### Parameters #### Parameters
| Name | In | Required | Description | | Name | In | Required | Description |
|------|----|----------|-------------| | ---- | ---- | -------- | ----------- |
| `id` | path | Yes | Plugin ID | | `id` | path | Yes | Plugin ID |
#### Responses #### Responses
| Status | Description | | Status | Description |
|--------|-------------| | ------ | -------------- |
| 200 | Plugin details | | 200 | Plugin details |
| 401 | Unauthorized | | 401 | Unauthorized |
| 404 | Not found | | 404 | Not found |
@ -141,13 +141,13 @@ Uninstall a plugin
#### Parameters #### Parameters
| Name | In | Required | Description | | Name | In | Required | Description |
|------|----|----------|-------------| | ---- | ---- | -------- | ----------- |
| `id` | path | Yes | Plugin ID | | `id` | path | Yes | Plugin ID |
#### Responses #### Responses
| Status | Description | | Status | Description |
|--------|-------------| | ------ | ------------------ |
| 200 | Plugin uninstalled | | 200 | Plugin uninstalled |
| 401 | Unauthorized | | 401 | Unauthorized |
| 403 | Forbidden | | 403 | Forbidden |
@ -164,13 +164,13 @@ Reload a plugin (for development)
#### Parameters #### Parameters
| Name | In | Required | Description | | Name | In | Required | Description |
|------|----|----------|-------------| | ---- | ---- | -------- | ----------- |
| `id` | path | Yes | Plugin ID | | `id` | path | Yes | Plugin ID |
#### Responses #### Responses
| Status | Description | | Status | Description |
|--------|-------------| | ------ | --------------- |
| 200 | Plugin reloaded | | 200 | Plugin reloaded |
| 401 | Unauthorized | | 401 | Unauthorized |
| 403 | Forbidden | | 403 | Forbidden |
@ -187,7 +187,7 @@ Enable or disable a plugin
#### Parameters #### Parameters
| Name | In | Required | Description | | Name | In | Required | Description |
|------|----|----------|-------------| | ---- | ---- | -------- | ----------- |
| `id` | path | Yes | Plugin ID | | `id` | path | Yes | Plugin ID |
#### Request Body #### Request Body
@ -199,11 +199,10 @@ See `docs/api/openapi.json` for the full schema.
#### Responses #### Responses
| Status | Description | | Status | Description |
|--------|-------------| | ------ | -------------- |
| 200 | Plugin toggled | | 200 | Plugin toggled |
| 401 | Unauthorized | | 401 | Unauthorized |
| 403 | Forbidden | | 403 | Forbidden |
| 404 | Not found | | 404 | Not found |
--- ---

View file

@ -11,7 +11,7 @@ Saved search queries
#### Responses #### Responses
| Status | Description | | Status | Description |
|--------|-------------| | ------ | ---------------------- |
| 200 | List of saved searches | | 200 | List of saved searches |
| 401 | Unauthorized | | 401 | Unauthorized |
| 500 | Internal server error | | 500 | Internal server error |
@ -31,7 +31,7 @@ See `docs/api/openapi.json` for the full schema.
#### Responses #### Responses
| Status | Description | | Status | Description |
|--------|-------------| | ------ | --------------------- |
| 200 | Search saved | | 200 | Search saved |
| 400 | Bad request | | 400 | Bad request |
| 401 | Unauthorized | | 401 | Unauthorized |
@ -46,17 +46,16 @@ See `docs/api/openapi.json` for the full schema.
#### Parameters #### Parameters
| Name | In | Required | Description | | Name | In | Required | Description |
|------|----|----------|-------------| | ---- | ---- | -------- | --------------- |
| `id` | path | Yes | Saved search ID | | `id` | path | Yes | Saved search ID |
#### Responses #### Responses
| Status | Description | | Status | Description |
|--------|-------------| | ------ | --------------------- |
| 200 | Saved search deleted | | 200 | Saved search deleted |
| 401 | Unauthorized | | 401 | Unauthorized |
| 404 | Not found | | 404 | Not found |
| 500 | Internal server error | | 500 | Internal server error |
--- ---

5
docs/api/scan.md vendored
View file

@ -19,7 +19,7 @@ See `docs/api/openapi.json` for the full schema.
#### Responses #### Responses
| Status | Description | | Status | Description |
|--------|-------------| | ------ | --------------------- |
| 200 | Scan job submitted | | 200 | Scan job submitted |
| 401 | Unauthorized | | 401 | Unauthorized |
| 403 | Forbidden | | 403 | Forbidden |
@ -34,9 +34,8 @@ See `docs/api/openapi.json` for the full schema.
#### Responses #### Responses
| Status | Description | | Status | Description |
|--------|-------------| | ------ | ------------ |
| 200 | Scan status | | 200 | Scan status |
| 401 | Unauthorized | | 401 | Unauthorized |
--- ---

View file

@ -11,7 +11,7 @@ Scheduled background tasks
#### Responses #### Responses
| Status | Description | | Status | Description |
|--------|-------------| | ------ | ----------------------- |
| 200 | List of scheduled tasks | | 200 | List of scheduled tasks |
| 401 | Unauthorized | | 401 | Unauthorized |
| 403 | Forbidden | | 403 | Forbidden |
@ -25,13 +25,13 @@ Scheduled background tasks
#### Parameters #### Parameters
| Name | In | Required | Description | | Name | In | Required | Description |
|------|----|----------|-------------| | ---- | ---- | -------- | ----------- |
| `id` | path | Yes | Task ID | | `id` | path | Yes | Task ID |
#### Responses #### Responses
| Status | Description | | Status | Description |
|--------|-------------| | ------ | -------------- |
| 200 | Task triggered | | 200 | Task triggered |
| 401 | Unauthorized | | 401 | Unauthorized |
| 403 | Forbidden | | 403 | Forbidden |
@ -46,17 +46,16 @@ Scheduled background tasks
#### Parameters #### Parameters
| Name | In | Required | Description | | Name | In | Required | Description |
|------|----|----------|-------------| | ---- | ---- | -------- | ----------- |
| `id` | path | Yes | Task ID | | `id` | path | Yes | Task ID |
#### Responses #### Responses
| Status | Description | | Status | Description |
|--------|-------------| | ------ | ------------ |
| 200 | Task toggled | | 200 | Task toggled |
| 401 | Unauthorized | | 401 | Unauthorized |
| 403 | Forbidden | | 403 | Forbidden |
| 404 | Not found | | 404 | Not found |
--- ---

7
docs/api/search.md vendored
View file

@ -11,7 +11,7 @@ Full-text media search
#### Parameters #### Parameters
| Name | In | Required | Description | | Name | In | Required | Description |
|------|----|----------|-------------| | -------- | ----- | -------- | ----------------- |
| `q` | query | Yes | Search query | | `q` | query | Yes | Search query |
| `sort` | query | No | Sort order | | `sort` | query | No | Sort order |
| `offset` | query | No | Pagination offset | | `offset` | query | No | Pagination offset |
@ -20,7 +20,7 @@ Full-text media search
#### Responses #### Responses
| Status | Description | | Status | Description |
|--------|-------------| | ------ | --------------------- |
| 200 | Search results | | 200 | Search results |
| 400 | Bad request | | 400 | Bad request |
| 401 | Unauthorized | | 401 | Unauthorized |
@ -41,11 +41,10 @@ See `docs/api/openapi.json` for the full schema.
#### Responses #### Responses
| Status | Description | | Status | Description |
|--------|-------------| | ------ | --------------------- |
| 200 | Search results | | 200 | Search results |
| 400 | Bad request | | 400 | Bad request |
| 401 | Unauthorized | | 401 | Unauthorized |
| 500 | Internal server error | | 500 | Internal server error |
--- ---

77
docs/api/shares.md vendored
View file

@ -6,15 +6,14 @@ Media sharing and notifications
### GET /api/v1/notifications/shares ### GET /api/v1/notifications/shares
Get unread share notifications Get unread share notifications GET /api/notifications/shares
GET /api/notifications/shares
**Authentication:** Required (Bearer JWT) **Authentication:** Required (Bearer JWT)
#### Responses #### Responses
| Status | Description | | Status | Description |
|--------|-------------| | ------ | -------------------- |
| 200 | Unread notifications | | 200 | Unread notifications |
| 401 | Unauthorized | | 401 | Unauthorized |
@ -22,15 +21,14 @@ GET /api/notifications/shares
### POST /api/v1/notifications/shares/read-all ### POST /api/v1/notifications/shares/read-all
Mark all notifications as read Mark all notifications as read POST /api/notifications/shares/read-all
POST /api/notifications/shares/read-all
**Authentication:** Required (Bearer JWT) **Authentication:** Required (Bearer JWT)
#### Responses #### Responses
| Status | Description | | Status | Description |
|--------|-------------| | ------ | -------------------------------- |
| 200 | All notifications marked as read | | 200 | All notifications marked as read |
| 401 | Unauthorized | | 401 | Unauthorized |
@ -38,21 +36,20 @@ POST /api/notifications/shares/read-all
### POST /api/v1/notifications/shares/{id}/read ### POST /api/v1/notifications/shares/{id}/read
Mark a notification as read Mark a notification as read POST /api/notifications/shares/{id}/read
POST /api/notifications/shares/{id}/read
**Authentication:** Required (Bearer JWT) **Authentication:** Required (Bearer JWT)
#### Parameters #### Parameters
| Name | In | Required | Description | | Name | In | Required | Description |
|------|----|----------|-------------| | ---- | ---- | -------- | --------------- |
| `id` | path | Yes | Notification ID | | `id` | path | Yes | Notification ID |
#### Responses #### Responses
| Status | Description | | Status | Description |
|--------|-------------| | ------ | --------------------------- |
| 200 | Notification marked as read | | 200 | Notification marked as read |
| 401 | Unauthorized | | 401 | Unauthorized |
@ -60,22 +57,21 @@ POST /api/notifications/shares/{id}/read
### GET /api/v1/shared/{token} ### GET /api/v1/shared/{token}
Access a public shared resource Access a public shared resource GET /api/shared/{token}
GET /api/shared/{token}
**Authentication:** Required (Bearer JWT) **Authentication:** Required (Bearer JWT)
#### Parameters #### Parameters
| Name | In | Required | Description | | Name | In | Required | Description |
|------|----|----------|-------------| | ---------- | ----- | -------- | -------------------------- |
| `token` | path | Yes | Share token | | `token` | path | Yes | Share token |
| `password` | query | No | Share password if required | | `password` | query | No | Share password if required |
#### Responses #### Responses
| Status | Description | | Status | Description |
|--------|-------------| | ------ | -------------- |
| 200 | Shared content | | 200 | Shared content |
| 401 | Unauthorized | | 401 | Unauthorized |
| 404 | Not found | | 404 | Not found |
@ -84,8 +80,7 @@ GET /api/shared/{token}
### POST /api/v1/shares ### POST /api/v1/shares
Create a new share Create a new share POST /api/shares
POST /api/shares
**Authentication:** Required (Bearer JWT) **Authentication:** Required (Bearer JWT)
@ -98,7 +93,7 @@ See `docs/api/openapi.json` for the full schema.
#### Responses #### Responses
| Status | Description | | Status | Description |
|--------|-------------| | ------ | --------------------- |
| 200 | Share created | | 200 | Share created |
| 400 | Bad request | | 400 | Bad request |
| 401 | Unauthorized | | 401 | Unauthorized |
@ -108,8 +103,7 @@ See `docs/api/openapi.json` for the full schema.
### POST /api/v1/shares/batch/delete ### POST /api/v1/shares/batch/delete
Batch delete shares Batch delete shares POST /api/shares/batch/delete
POST /api/shares/batch/delete
**Authentication:** Required (Bearer JWT) **Authentication:** Required (Bearer JWT)
@ -122,7 +116,7 @@ See `docs/api/openapi.json` for the full schema.
#### Responses #### Responses
| Status | Description | | Status | Description |
|--------|-------------| | ------ | -------------- |
| 200 | Shares deleted | | 200 | Shares deleted |
| 400 | Bad request | | 400 | Bad request |
| 401 | Unauthorized | | 401 | Unauthorized |
@ -132,22 +126,21 @@ See `docs/api/openapi.json` for the full schema.
### GET /api/v1/shares/incoming ### GET /api/v1/shares/incoming
List incoming shares (shares shared with me) List incoming shares (shares shared with me) GET /api/shares/incoming
GET /api/shares/incoming
**Authentication:** Required (Bearer JWT) **Authentication:** Required (Bearer JWT)
#### Parameters #### Parameters
| Name | In | Required | Description | | Name | In | Required | Description |
|------|----|----------|-------------| | -------- | ----- | -------- | ----------------- |
| `offset` | query | No | Pagination offset | | `offset` | query | No | Pagination offset |
| `limit` | query | No | Pagination limit | | `limit` | query | No | Pagination limit |
#### Responses #### Responses
| Status | Description | | Status | Description |
|--------|-------------| | ------ | --------------- |
| 200 | Incoming shares | | 200 | Incoming shares |
| 401 | Unauthorized | | 401 | Unauthorized |
@ -155,22 +148,21 @@ GET /api/shares/incoming
### GET /api/v1/shares/outgoing ### GET /api/v1/shares/outgoing
List outgoing shares (shares I created) List outgoing shares (shares I created) GET /api/shares/outgoing
GET /api/shares/outgoing
**Authentication:** Required (Bearer JWT) **Authentication:** Required (Bearer JWT)
#### Parameters #### Parameters
| Name | In | Required | Description | | Name | In | Required | Description |
|------|----|----------|-------------| | -------- | ----- | -------- | ----------------- |
| `offset` | query | No | Pagination offset | | `offset` | query | No | Pagination offset |
| `limit` | query | No | Pagination limit | | `limit` | query | No | Pagination limit |
#### Responses #### Responses
| Status | Description | | Status | Description |
|--------|-------------| | ------ | --------------- |
| 200 | Outgoing shares | | 200 | Outgoing shares |
| 401 | Unauthorized | | 401 | Unauthorized |
@ -178,21 +170,20 @@ GET /api/shares/outgoing
### GET /api/v1/shares/{id} ### GET /api/v1/shares/{id}
Get share details Get share details GET /api/shares/{id}
GET /api/shares/{id}
**Authentication:** Required (Bearer JWT) **Authentication:** Required (Bearer JWT)
#### Parameters #### Parameters
| Name | In | Required | Description | | Name | In | Required | Description |
|------|----|----------|-------------| | ---- | ---- | -------- | ----------- |
| `id` | path | Yes | Share ID | | `id` | path | Yes | Share ID |
#### Responses #### Responses
| Status | Description | | Status | Description |
|--------|-------------| | ------ | ------------- |
| 200 | Share details | | 200 | Share details |
| 401 | Unauthorized | | 401 | Unauthorized |
| 403 | Forbidden | | 403 | Forbidden |
@ -202,15 +193,14 @@ GET /api/shares/{id}
### PATCH /api/v1/shares/{id} ### PATCH /api/v1/shares/{id}
Update a share Update a share PATCH /api/shares/{id}
PATCH /api/shares/{id}
**Authentication:** Required (Bearer JWT) **Authentication:** Required (Bearer JWT)
#### Parameters #### Parameters
| Name | In | Required | Description | | Name | In | Required | Description |
|------|----|----------|-------------| | ---- | ---- | -------- | ----------- |
| `id` | path | Yes | Share ID | | `id` | path | Yes | Share ID |
#### Request Body #### Request Body
@ -222,7 +212,7 @@ See `docs/api/openapi.json` for the full schema.
#### Responses #### Responses
| Status | Description | | Status | Description |
|--------|-------------| | ------ | ------------- |
| 200 | Share updated | | 200 | Share updated |
| 401 | Unauthorized | | 401 | Unauthorized |
| 403 | Forbidden | | 403 | Forbidden |
@ -232,21 +222,20 @@ See `docs/api/openapi.json` for the full schema.
### DELETE /api/v1/shares/{id} ### DELETE /api/v1/shares/{id}
Delete (revoke) a share Delete (revoke) a share DELETE /api/shares/{id}
DELETE /api/shares/{id}
**Authentication:** Required (Bearer JWT) **Authentication:** Required (Bearer JWT)
#### Parameters #### Parameters
| Name | In | Required | Description | | Name | In | Required | Description |
|------|----|----------|-------------| | ---- | ---- | -------- | ----------- |
| `id` | path | Yes | Share ID | | `id` | path | Yes | Share ID |
#### Responses #### Responses
| Status | Description | | Status | Description |
|--------|-------------| | ------ | ------------- |
| 204 | Share deleted | | 204 | Share deleted |
| 401 | Unauthorized | | 401 | Unauthorized |
| 403 | Forbidden | | 403 | Forbidden |
@ -256,15 +245,14 @@ DELETE /api/shares/{id}
### GET /api/v1/shares/{id}/activity ### GET /api/v1/shares/{id}/activity
Get share activity log Get share activity log GET /api/shares/{id}/activity
GET /api/shares/{id}/activity
**Authentication:** Required (Bearer JWT) **Authentication:** Required (Bearer JWT)
#### Parameters #### Parameters
| Name | In | Required | Description | | Name | In | Required | Description |
|------|----|----------|-------------| | -------- | ----- | -------- | ----------------- |
| `id` | path | Yes | Share ID | | `id` | path | Yes | Share ID |
| `offset` | query | No | Pagination offset | | `offset` | query | No | Pagination offset |
| `limit` | query | No | Pagination limit | | `limit` | query | No | Pagination limit |
@ -272,11 +260,10 @@ GET /api/shares/{id}/activity
#### Responses #### Responses
| Status | Description | | Status | Description |
|--------|-------------| | ------ | -------------- |
| 200 | Share activity | | 200 | Share activity |
| 401 | Unauthorized | | 401 | Unauthorized |
| 403 | Forbidden | | 403 | Forbidden |
| 404 | Not found | | 404 | Not found |
--- ---

31
docs/api/social.md vendored
View file

@ -11,7 +11,7 @@ Ratings, comments, favorites, and share links
#### Responses #### Responses
| Status | Description | | Status | Description |
|--------|-------------| | ------ | --------------------- |
| 200 | User favorites | | 200 | User favorites |
| 401 | Unauthorized | | 401 | Unauthorized |
| 500 | Internal server error | | 500 | Internal server error |
@ -31,7 +31,7 @@ See `docs/api/openapi.json` for the full schema.
#### Responses #### Responses
| Status | Description | | Status | Description |
|--------|-------------| | ------ | --------------------- |
| 200 | Added to favorites | | 200 | Added to favorites |
| 401 | Unauthorized | | 401 | Unauthorized |
| 500 | Internal server error | | 500 | Internal server error |
@ -45,13 +45,13 @@ See `docs/api/openapi.json` for the full schema.
#### Parameters #### Parameters
| Name | In | Required | Description | | Name | In | Required | Description |
|------|----|----------|-------------| | ---------- | ---- | -------- | ------------- |
| `media_id` | path | Yes | Media item ID | | `media_id` | path | Yes | Media item ID |
#### Responses #### Responses
| Status | Description | | Status | Description |
|--------|-------------| | ------ | ---------------------- |
| 200 | Removed from favorites | | 200 | Removed from favorites |
| 401 | Unauthorized | | 401 | Unauthorized |
| 500 | Internal server error | | 500 | Internal server error |
@ -71,7 +71,7 @@ See `docs/api/openapi.json` for the full schema.
#### Responses #### Responses
| Status | Description | | Status | Description |
|--------|-------------| | ------ | --------------------- |
| 200 | Share link created | | 200 | Share link created |
| 400 | Bad request | | 400 | Bad request |
| 401 | Unauthorized | | 401 | Unauthorized |
@ -86,13 +86,13 @@ See `docs/api/openapi.json` for the full schema.
#### Parameters #### Parameters
| Name | In | Required | Description | | Name | In | Required | Description |
|------|----|----------|-------------| | ---- | ---- | -------- | ------------- |
| `id` | path | Yes | Media item ID | | `id` | path | Yes | Media item ID |
#### Responses #### Responses
| Status | Description | | Status | Description |
|--------|-------------| | ------ | --------------------- |
| 200 | Media comments | | 200 | Media comments |
| 401 | Unauthorized | | 401 | Unauthorized |
| 500 | Internal server error | | 500 | Internal server error |
@ -106,7 +106,7 @@ See `docs/api/openapi.json` for the full schema.
#### Parameters #### Parameters
| Name | In | Required | Description | | Name | In | Required | Description |
|------|----|----------|-------------| | ---- | ---- | -------- | ------------- |
| `id` | path | Yes | Media item ID | | `id` | path | Yes | Media item ID |
#### Request Body #### Request Body
@ -118,7 +118,7 @@ See `docs/api/openapi.json` for the full schema.
#### Responses #### Responses
| Status | Description | | Status | Description |
|--------|-------------| | ------ | --------------------- |
| 200 | Comment added | | 200 | Comment added |
| 400 | Bad request | | 400 | Bad request |
| 401 | Unauthorized | | 401 | Unauthorized |
@ -133,7 +133,7 @@ See `docs/api/openapi.json` for the full schema.
#### Parameters #### Parameters
| Name | In | Required | Description | | Name | In | Required | Description |
|------|----|----------|-------------| | ---- | ---- | -------- | ------------- |
| `id` | path | Yes | Media item ID | | `id` | path | Yes | Media item ID |
#### Request Body #### Request Body
@ -145,7 +145,7 @@ See `docs/api/openapi.json` for the full schema.
#### Responses #### Responses
| Status | Description | | Status | Description |
|--------|-------------| | ------ | --------------------- |
| 200 | Rating saved | | 200 | Rating saved |
| 400 | Bad request | | 400 | Bad request |
| 401 | Unauthorized | | 401 | Unauthorized |
@ -160,13 +160,13 @@ See `docs/api/openapi.json` for the full schema.
#### Parameters #### Parameters
| Name | In | Required | Description | | Name | In | Required | Description |
|------|----|----------|-------------| | ---- | ---- | -------- | ------------- |
| `id` | path | Yes | Media item ID | | `id` | path | Yes | Media item ID |
#### Responses #### Responses
| Status | Description | | Status | Description |
|--------|-------------| | ------ | --------------------- |
| 200 | Media ratings | | 200 | Media ratings |
| 401 | Unauthorized | | 401 | Unauthorized |
| 500 | Internal server error | | 500 | Internal server error |
@ -180,17 +180,16 @@ See `docs/api/openapi.json` for the full schema.
#### Parameters #### Parameters
| Name | In | Required | Description | | Name | In | Required | Description |
|------|----|----------|-------------| | ---------- | ----- | -------- | -------------- |
| `token` | path | Yes | Share token | | `token` | path | Yes | Share token |
| `password` | query | No | Share password | | `password` | query | No | Share password |
#### Responses #### Responses
| Status | Description | | Status | Description |
|--------|-------------| | ------ | ------------ |
| 200 | Shared media | | 200 | Shared media |
| 401 | Unauthorized | | 401 | Unauthorized |
| 404 | Not found | | 404 | Not found |
--- ---

View file

@ -11,10 +11,9 @@ Library statistics
#### Responses #### Responses
| Status | Description | | Status | Description |
|--------|-------------| | ------ | --------------------- |
| 200 | Library statistics | | 200 | Library statistics |
| 401 | Unauthorized | | 401 | Unauthorized |
| 500 | Internal server error | | 500 | Internal server error |
--- ---

21
docs/api/streaming.md vendored
View file

@ -11,13 +11,13 @@ HLS and DASH adaptive streaming
#### Parameters #### Parameters
| Name | In | Required | Description | | Name | In | Required | Description |
|------|----|----------|-------------| | ---- | ---- | -------- | ------------- |
| `id` | path | Yes | Media item ID | | `id` | path | Yes | Media item ID |
#### Responses #### Responses
| Status | Description | | Status | Description |
|--------|-------------| | ------ | ------------- |
| 200 | DASH manifest | | 200 | DASH manifest |
| 400 | Bad request | | 400 | Bad request |
| 401 | Unauthorized | | 401 | Unauthorized |
@ -32,7 +32,7 @@ HLS and DASH adaptive streaming
#### Parameters #### Parameters
| Name | In | Required | Description | | Name | In | Required | Description |
|------|----|----------|-------------| | --------- | ---- | -------- | ---------------------- |
| `id` | path | Yes | Media item ID | | `id` | path | Yes | Media item ID |
| `profile` | path | Yes | Transcode profile name | | `profile` | path | Yes | Transcode profile name |
| `segment` | path | Yes | Segment filename | | `segment` | path | Yes | Segment filename |
@ -40,7 +40,7 @@ HLS and DASH adaptive streaming
#### Responses #### Responses
| Status | Description | | Status | Description |
|--------|-------------| | ------ | ------------------------- |
| 200 | DASH segment data | | 200 | DASH segment data |
| 202 | Segment not yet available | | 202 | Segment not yet available |
| 400 | Bad request | | 400 | Bad request |
@ -55,13 +55,13 @@ HLS and DASH adaptive streaming
#### Parameters #### Parameters
| Name | In | Required | Description | | Name | In | Required | Description |
|------|----|----------|-------------| | ---- | ---- | -------- | ------------- |
| `id` | path | Yes | Media item ID | | `id` | path | Yes | Media item ID |
#### Responses #### Responses
| Status | Description | | Status | Description |
|--------|-------------| | ------ | ------------------- |
| 200 | HLS master playlist | | 200 | HLS master playlist |
| 401 | Unauthorized | | 401 | Unauthorized |
| 404 | Not found | | 404 | Not found |
@ -75,14 +75,14 @@ HLS and DASH adaptive streaming
#### Parameters #### Parameters
| Name | In | Required | Description | | Name | In | Required | Description |
|------|----|----------|-------------| | --------- | ---- | -------- | ---------------------- |
| `id` | path | Yes | Media item ID | | `id` | path | Yes | Media item ID |
| `profile` | path | Yes | Transcode profile name | | `profile` | path | Yes | Transcode profile name |
#### Responses #### Responses
| Status | Description | | Status | Description |
|--------|-------------| | ------ | -------------------- |
| 200 | HLS variant playlist | | 200 | HLS variant playlist |
| 400 | Bad request | | 400 | Bad request |
| 401 | Unauthorized | | 401 | Unauthorized |
@ -97,7 +97,7 @@ HLS and DASH adaptive streaming
#### Parameters #### Parameters
| Name | In | Required | Description | | Name | In | Required | Description |
|------|----|----------|-------------| | --------- | ---- | -------- | ---------------------- |
| `id` | path | Yes | Media item ID | | `id` | path | Yes | Media item ID |
| `profile` | path | Yes | Transcode profile name | | `profile` | path | Yes | Transcode profile name |
| `segment` | path | Yes | Segment filename | | `segment` | path | Yes | Segment filename |
@ -105,11 +105,10 @@ HLS and DASH adaptive streaming
#### Responses #### Responses
| Status | Description | | Status | Description |
|--------|-------------| | ------ | ------------------------- |
| 200 | HLS segment data | | 200 | HLS segment data |
| 202 | Segment not yet available | | 202 | Segment not yet available |
| 400 | Bad request | | 400 | Bad request |
| 401 | Unauthorized | | 401 | Unauthorized |
--- ---

23
docs/api/subtitles.md vendored
View file

@ -11,14 +11,14 @@ Media subtitle management
#### Parameters #### Parameters
| Name | In | Required | Description | | Name | In | Required | Description |
|------|----|----------|-------------| | ---- | ---- | -------- | ------------- |
| `id` | path | Yes | Media item ID | | `id` | path | Yes | Media item ID |
#### Responses #### Responses
| Status | Description | | Status | Description |
|--------|-------------| | ------ | --------------------------------------- |
| 200 | Subtitles | | 200 | Subtitles and available embedded tracks |
| 401 | Unauthorized | | 401 | Unauthorized |
| 404 | Not found | | 404 | Not found |
@ -31,7 +31,7 @@ Media subtitle management
#### Parameters #### Parameters
| Name | In | Required | Description | | Name | In | Required | Description |
|------|----|----------|-------------| | ---- | ---- | -------- | ------------- |
| `id` | path | Yes | Media item ID | | `id` | path | Yes | Media item ID |
#### Request Body #### Request Body
@ -43,7 +43,7 @@ See `docs/api/openapi.json` for the full schema.
#### Responses #### Responses
| Status | Description | | Status | Description |
|--------|-------------| | ------ | --------------------- |
| 200 | Subtitle added | | 200 | Subtitle added |
| 400 | Bad request | | 400 | Bad request |
| 401 | Unauthorized | | 401 | Unauthorized |
@ -58,14 +58,14 @@ See `docs/api/openapi.json` for the full schema.
#### Parameters #### Parameters
| Name | In | Required | Description | | Name | In | Required | Description |
|------|----|----------|-------------| | ------------- | ---- | -------- | ------------- |
| `media_id` | path | Yes | Media item ID | | `media_id` | path | Yes | Media item ID |
| `subtitle_id` | path | Yes | Subtitle ID | | `subtitle_id` | path | Yes | Subtitle ID |
#### Responses #### Responses
| Status | Description | | Status | Description |
|--------|-------------| | ------ | ---------------- |
| 200 | Subtitle content | | 200 | Subtitle content |
| 401 | Unauthorized | | 401 | Unauthorized |
| 404 | Not found | | 404 | Not found |
@ -79,13 +79,13 @@ See `docs/api/openapi.json` for the full schema.
#### Parameters #### Parameters
| Name | In | Required | Description | | Name | In | Required | Description |
|------|----|----------|-------------| | ---- | ---- | -------- | ----------- |
| `id` | path | Yes | Subtitle ID | | `id` | path | Yes | Subtitle ID |
#### Responses #### Responses
| Status | Description | | Status | Description |
|--------|-------------| | ------ | ---------------- |
| 200 | Subtitle deleted | | 200 | Subtitle deleted |
| 401 | Unauthorized | | 401 | Unauthorized |
| 404 | Not found | | 404 | Not found |
@ -99,7 +99,7 @@ See `docs/api/openapi.json` for the full schema.
#### Parameters #### Parameters
| Name | In | Required | Description | | Name | In | Required | Description |
|------|----|----------|-------------| | ---- | ---- | -------- | ----------- |
| `id` | path | Yes | Subtitle ID | | `id` | path | Yes | Subtitle ID |
#### Request Body #### Request Body
@ -111,10 +111,9 @@ See `docs/api/openapi.json` for the full schema.
#### Responses #### Responses
| Status | Description | | Status | Description |
|--------|-------------| | ------ | -------------- |
| 200 | Offset updated | | 200 | Offset updated |
| 401 | Unauthorized | | 401 | Unauthorized |
| 404 | Not found | | 404 | Not found |
--- ---

111
docs/api/sync.md vendored
View file

@ -6,8 +6,7 @@ Multi-device library synchronization
### POST /api/v1/sync/ack ### POST /api/v1/sync/ack
Acknowledge processed changes Acknowledge processed changes POST /api/sync/ack
POST /api/sync/ack
**Authentication:** Required (Bearer JWT) **Authentication:** Required (Bearer JWT)
@ -20,7 +19,7 @@ See `docs/api/openapi.json` for the full schema.
#### Responses #### Responses
| Status | Description | | Status | Description |
|--------|-------------| | ------ | -------------------- |
| 200 | Changes acknowledged | | 200 | Changes acknowledged |
| 400 | Bad request | | 400 | Bad request |
| 401 | Unauthorized | | 401 | Unauthorized |
@ -29,22 +28,21 @@ See `docs/api/openapi.json` for the full schema.
### GET /api/v1/sync/changes ### GET /api/v1/sync/changes
Get changes since cursor Get changes since cursor GET /api/sync/changes
GET /api/sync/changes
**Authentication:** Required (Bearer JWT) **Authentication:** Required (Bearer JWT)
#### Parameters #### Parameters
| Name | In | Required | Description | | Name | In | Required | Description |
|------|----|----------|-------------| | -------- | ----- | -------- | ---------------------- |
| `cursor` | query | No | Sync cursor | | `cursor` | query | No | Sync cursor |
| `limit` | query | No | Max changes (max 1000) | | `limit` | query | No | Max changes (max 1000) |
#### Responses #### Responses
| Status | Description | | Status | Description |
|--------|-------------| | ------ | -------------------- |
| 200 | Changes since cursor | | 200 | Changes since cursor |
| 400 | Bad request | | 400 | Bad request |
| 401 | Unauthorized | | 401 | Unauthorized |
@ -53,15 +51,14 @@ GET /api/sync/changes
### GET /api/v1/sync/conflicts ### GET /api/v1/sync/conflicts
List unresolved conflicts List unresolved conflicts GET /api/sync/conflicts
GET /api/sync/conflicts
**Authentication:** Required (Bearer JWT) **Authentication:** Required (Bearer JWT)
#### Responses #### Responses
| Status | Description | | Status | Description |
|--------|-------------| | ------ | -------------------- |
| 200 | Unresolved conflicts | | 200 | Unresolved conflicts |
| 401 | Unauthorized | | 401 | Unauthorized |
@ -69,15 +66,14 @@ GET /api/sync/conflicts
### POST /api/v1/sync/conflicts/{id}/resolve ### POST /api/v1/sync/conflicts/{id}/resolve
Resolve a sync conflict Resolve a sync conflict POST /api/sync/conflicts/{id}/resolve
POST /api/sync/conflicts/{id}/resolve
**Authentication:** Required (Bearer JWT) **Authentication:** Required (Bearer JWT)
#### Parameters #### Parameters
| Name | In | Required | Description | | Name | In | Required | Description |
|------|----|----------|-------------| | ---- | ---- | -------- | ----------- |
| `id` | path | Yes | Conflict ID | | `id` | path | Yes | Conflict ID |
#### Request Body #### Request Body
@ -89,7 +85,7 @@ See `docs/api/openapi.json` for the full schema.
#### Responses #### Responses
| Status | Description | | Status | Description |
|--------|-------------| | ------ | ----------------- |
| 200 | Conflict resolved | | 200 | Conflict resolved |
| 400 | Bad request | | 400 | Bad request |
| 401 | Unauthorized | | 401 | Unauthorized |
@ -98,15 +94,14 @@ See `docs/api/openapi.json` for the full schema.
### GET /api/v1/sync/devices ### GET /api/v1/sync/devices
List user's sync devices List user's sync devices GET /api/sync/devices
GET /api/sync/devices
**Authentication:** Required (Bearer JWT) **Authentication:** Required (Bearer JWT)
#### Responses #### Responses
| Status | Description | | Status | Description |
|--------|-------------| | ------ | --------------- |
| 200 | List of devices | | 200 | List of devices |
| 401 | Unauthorized | | 401 | Unauthorized |
@ -114,8 +109,7 @@ GET /api/sync/devices
### POST /api/v1/sync/devices ### POST /api/v1/sync/devices
Register a new sync device Register a new sync device POST /api/sync/devices
POST /api/sync/devices
**Authentication:** Required (Bearer JWT) **Authentication:** Required (Bearer JWT)
@ -128,7 +122,7 @@ See `docs/api/openapi.json` for the full schema.
#### Responses #### Responses
| Status | Description | | Status | Description |
|--------|-------------| | ------ | --------------------- |
| 200 | Device registered | | 200 | Device registered |
| 400 | Bad request | | 400 | Bad request |
| 401 | Unauthorized | | 401 | Unauthorized |
@ -138,21 +132,20 @@ See `docs/api/openapi.json` for the full schema.
### GET /api/v1/sync/devices/{id} ### GET /api/v1/sync/devices/{id}
Get device details Get device details GET /api/sync/devices/{id}
GET /api/sync/devices/{id}
**Authentication:** Required (Bearer JWT) **Authentication:** Required (Bearer JWT)
#### Parameters #### Parameters
| Name | In | Required | Description | | Name | In | Required | Description |
|------|----|----------|-------------| | ---- | ---- | -------- | ----------- |
| `id` | path | Yes | Device ID | | `id` | path | Yes | Device ID |
#### Responses #### Responses
| Status | Description | | Status | Description |
|--------|-------------| | ------ | -------------- |
| 200 | Device details | | 200 | Device details |
| 401 | Unauthorized | | 401 | Unauthorized |
| 403 | Forbidden | | 403 | Forbidden |
@ -162,15 +155,14 @@ GET /api/sync/devices/{id}
### PUT /api/v1/sync/devices/{id} ### PUT /api/v1/sync/devices/{id}
Update a device Update a device PUT /api/sync/devices/{id}
PUT /api/sync/devices/{id}
**Authentication:** Required (Bearer JWT) **Authentication:** Required (Bearer JWT)
#### Parameters #### Parameters
| Name | In | Required | Description | | Name | In | Required | Description |
|------|----|----------|-------------| | ---- | ---- | -------- | ----------- |
| `id` | path | Yes | Device ID | | `id` | path | Yes | Device ID |
#### Request Body #### Request Body
@ -182,7 +174,7 @@ See `docs/api/openapi.json` for the full schema.
#### Responses #### Responses
| Status | Description | | Status | Description |
|--------|-------------| | ------ | -------------- |
| 200 | Device updated | | 200 | Device updated |
| 401 | Unauthorized | | 401 | Unauthorized |
| 403 | Forbidden | | 403 | Forbidden |
@ -192,21 +184,20 @@ See `docs/api/openapi.json` for the full schema.
### DELETE /api/v1/sync/devices/{id} ### DELETE /api/v1/sync/devices/{id}
Delete a device Delete a device DELETE /api/sync/devices/{id}
DELETE /api/sync/devices/{id}
**Authentication:** Required (Bearer JWT) **Authentication:** Required (Bearer JWT)
#### Parameters #### Parameters
| Name | In | Required | Description | | Name | In | Required | Description |
|------|----|----------|-------------| | ---- | ---- | -------- | ----------- |
| `id` | path | Yes | Device ID | | `id` | path | Yes | Device ID |
#### Responses #### Responses
| Status | Description | | Status | Description |
|--------|-------------| | ------ | -------------- |
| 204 | Device deleted | | 204 | Device deleted |
| 401 | Unauthorized | | 401 | Unauthorized |
| 403 | Forbidden | | 403 | Forbidden |
@ -216,21 +207,20 @@ DELETE /api/sync/devices/{id}
### POST /api/v1/sync/devices/{id}/token ### POST /api/v1/sync/devices/{id}/token
Regenerate device token Regenerate device token POST /api/sync/devices/{id}/token
POST /api/sync/devices/{id}/token
**Authentication:** Required (Bearer JWT) **Authentication:** Required (Bearer JWT)
#### Parameters #### Parameters
| Name | In | Required | Description | | Name | In | Required | Description |
|------|----|----------|-------------| | ---- | ---- | -------- | ----------- |
| `id` | path | Yes | Device ID | | `id` | path | Yes | Device ID |
#### Responses #### Responses
| Status | Description | | Status | Description |
|--------|-------------| | ------ | ----------------- |
| 200 | Token regenerated | | 200 | Token regenerated |
| 401 | Unauthorized | | 401 | Unauthorized |
| 403 | Forbidden | | 403 | Forbidden |
@ -240,21 +230,20 @@ POST /api/sync/devices/{id}/token
### GET /api/v1/sync/download/{path} ### GET /api/v1/sync/download/{path}
Download a file for sync (supports Range header) Download a file for sync (supports Range header) GET /api/sync/download/{*path}
GET /api/sync/download/{*path}
**Authentication:** Required (Bearer JWT) **Authentication:** Required (Bearer JWT)
#### Parameters #### Parameters
| Name | In | Required | Description | | Name | In | Required | Description |
|------|----|----------|-------------| | ------ | ---- | -------- | ----------- |
| `path` | path | Yes | File path | | `path` | path | Yes | File path |
#### Responses #### Responses
| Status | Description | | Status | Description |
|--------|-------------| | ------ | --------------- |
| 200 | File content | | 200 | File content |
| 206 | Partial content | | 206 | Partial content |
| 401 | Unauthorized | | 401 | Unauthorized |
@ -264,8 +253,7 @@ GET /api/sync/download/{*path}
### POST /api/v1/sync/report ### POST /api/v1/sync/report
Report local changes from client Report local changes from client POST /api/sync/report
POST /api/sync/report
**Authentication:** Required (Bearer JWT) **Authentication:** Required (Bearer JWT)
@ -278,7 +266,7 @@ See `docs/api/openapi.json` for the full schema.
#### Responses #### Responses
| Status | Description | | Status | Description |
|--------|-------------| | ------ | ----------------- |
| 200 | Changes processed | | 200 | Changes processed |
| 400 | Bad request | | 400 | Bad request |
| 401 | Unauthorized | | 401 | Unauthorized |
@ -287,8 +275,7 @@ See `docs/api/openapi.json` for the full schema.
### POST /api/v1/sync/upload ### POST /api/v1/sync/upload
Create an upload session for chunked upload Create an upload session for chunked upload POST /api/sync/upload
POST /api/sync/upload
**Authentication:** Required (Bearer JWT) **Authentication:** Required (Bearer JWT)
@ -301,7 +288,7 @@ See `docs/api/openapi.json` for the full schema.
#### Responses #### Responses
| Status | Description | | Status | Description |
|--------|-------------| | ------ | ---------------------- |
| 200 | Upload session created | | 200 | Upload session created |
| 400 | Bad request | | 400 | Bad request |
| 401 | Unauthorized | | 401 | Unauthorized |
@ -310,21 +297,20 @@ See `docs/api/openapi.json` for the full schema.
### GET /api/v1/sync/upload/{id} ### GET /api/v1/sync/upload/{id}
Get upload session status Get upload session status GET /api/sync/upload/{id}
GET /api/sync/upload/{id}
**Authentication:** Required (Bearer JWT) **Authentication:** Required (Bearer JWT)
#### Parameters #### Parameters
| Name | In | Required | Description | | Name | In | Required | Description |
|------|----|----------|-------------| | ---- | ---- | -------- | ----------------- |
| `id` | path | Yes | Upload session ID | | `id` | path | Yes | Upload session ID |
#### Responses #### Responses
| Status | Description | | Status | Description |
|--------|-------------| | ------ | --------------------- |
| 200 | Upload session status | | 200 | Upload session status |
| 401 | Unauthorized | | 401 | Unauthorized |
| 404 | Not found | | 404 | Not found |
@ -333,21 +319,20 @@ GET /api/sync/upload/{id}
### DELETE /api/v1/sync/upload/{id} ### DELETE /api/v1/sync/upload/{id}
Cancel an upload session Cancel an upload session DELETE /api/sync/upload/{id}
DELETE /api/sync/upload/{id}
**Authentication:** Required (Bearer JWT) **Authentication:** Required (Bearer JWT)
#### Parameters #### Parameters
| Name | In | Required | Description | | Name | In | Required | Description |
|------|----|----------|-------------| | ---- | ---- | -------- | ----------------- |
| `id` | path | Yes | Upload session ID | | `id` | path | Yes | Upload session ID |
#### Responses #### Responses
| Status | Description | | Status | Description |
|--------|-------------| | ------ | ---------------- |
| 204 | Upload cancelled | | 204 | Upload cancelled |
| 401 | Unauthorized | | 401 | Unauthorized |
| 404 | Not found | | 404 | Not found |
@ -356,29 +341,27 @@ DELETE /api/sync/upload/{id}
### PUT /api/v1/sync/upload/{id}/chunks/{index} ### PUT /api/v1/sync/upload/{id}/chunks/{index}
Upload a chunk Upload a chunk PUT /api/sync/upload/{id}/chunks/{index}
PUT /api/sync/upload/{id}/chunks/{index}
**Authentication:** Required (Bearer JWT) **Authentication:** Required (Bearer JWT)
#### Parameters #### Parameters
| Name | In | Required | Description | | Name | In | Required | Description |
|------|----|----------|-------------| | ------- | ---- | -------- | ----------------- |
| `id` | path | Yes | Upload session ID | | `id` | path | Yes | Upload session ID |
| `index` | path | Yes | Chunk index | | `index` | path | Yes | Chunk index |
#### Request Body #### Request Body
Chunk binary data Chunk binary data `Content-Type: application/octet-stream`
`Content-Type: application/octet-stream`
See `docs/api/openapi.json` for the full schema. See `docs/api/openapi.json` for the full schema.
#### Responses #### Responses
| Status | Description | | Status | Description |
|--------|-------------| | ------ | -------------- |
| 200 | Chunk received | | 200 | Chunk received |
| 400 | Bad request | | 400 | Bad request |
| 401 | Unauthorized | | 401 | Unauthorized |
@ -388,25 +371,23 @@ See `docs/api/openapi.json` for the full schema.
### POST /api/v1/sync/upload/{id}/complete ### POST /api/v1/sync/upload/{id}/complete
Complete an upload session Complete an upload session POST /api/sync/upload/{id}/complete
POST /api/sync/upload/{id}/complete
**Authentication:** Required (Bearer JWT) **Authentication:** Required (Bearer JWT)
#### Parameters #### Parameters
| Name | In | Required | Description | | Name | In | Required | Description |
|------|----|----------|-------------| | ---- | ---- | -------- | ----------------- |
| `id` | path | Yes | Upload session ID | | `id` | path | Yes | Upload session ID |
#### Responses #### Responses
| Status | Description | | Status | Description |
|--------|-------------| | ------ | ---------------- |
| 200 | Upload completed | | 200 | Upload completed |
| 400 | Bad request | | 400 | Bad request |
| 401 | Unauthorized | | 401 | Unauthorized |
| 404 | Not found | | 404 | Not found |
--- ---

25
docs/api/tags.md vendored
View file

@ -11,13 +11,13 @@ Media tag management
#### Parameters #### Parameters
| Name | In | Required | Description | | Name | In | Required | Description |
|------|----|----------|-------------| | ---------- | ---- | -------- | ------------- |
| `media_id` | path | Yes | Media item ID | | `media_id` | path | Yes | Media item ID |
#### Responses #### Responses
| Status | Description | | Status | Description |
|--------|-------------| | ------ | --------------------- |
| 200 | Media tags | | 200 | Media tags |
| 401 | Unauthorized | | 401 | Unauthorized |
| 404 | Not found | | 404 | Not found |
@ -32,7 +32,7 @@ Media tag management
#### Parameters #### Parameters
| Name | In | Required | Description | | Name | In | Required | Description |
|------|----|----------|-------------| | ---------- | ---- | -------- | ------------- |
| `media_id` | path | Yes | Media item ID | | `media_id` | path | Yes | Media item ID |
#### Request Body #### Request Body
@ -44,7 +44,7 @@ See `docs/api/openapi.json` for the full schema.
#### Responses #### Responses
| Status | Description | | Status | Description |
|--------|-------------| | ------ | --------------------- |
| 200 | Tag applied | | 200 | Tag applied |
| 401 | Unauthorized | | 401 | Unauthorized |
| 403 | Forbidden | | 403 | Forbidden |
@ -60,14 +60,14 @@ See `docs/api/openapi.json` for the full schema.
#### Parameters #### Parameters
| Name | In | Required | Description | | Name | In | Required | Description |
|------|----|----------|-------------| | ---------- | ---- | -------- | ------------- |
| `media_id` | path | Yes | Media item ID | | `media_id` | path | Yes | Media item ID |
| `tag_id` | path | Yes | Tag ID | | `tag_id` | path | Yes | Tag ID |
#### Responses #### Responses
| Status | Description | | Status | Description |
|--------|-------------| | ------ | --------------------- |
| 200 | Tag removed | | 200 | Tag removed |
| 401 | Unauthorized | | 401 | Unauthorized |
| 403 | Forbidden | | 403 | Forbidden |
@ -83,7 +83,7 @@ See `docs/api/openapi.json` for the full schema.
#### Responses #### Responses
| Status | Description | | Status | Description |
|--------|-------------| | ------ | --------------------- |
| 200 | List of tags | | 200 | List of tags |
| 401 | Unauthorized | | 401 | Unauthorized |
| 500 | Internal server error | | 500 | Internal server error |
@ -103,7 +103,7 @@ See `docs/api/openapi.json` for the full schema.
#### Responses #### Responses
| Status | Description | | Status | Description |
|--------|-------------| | ------ | --------------------- |
| 200 | Tag created | | 200 | Tag created |
| 400 | Bad request | | 400 | Bad request |
| 401 | Unauthorized | | 401 | Unauthorized |
@ -119,13 +119,13 @@ See `docs/api/openapi.json` for the full schema.
#### Parameters #### Parameters
| Name | In | Required | Description | | Name | In | Required | Description |
|------|----|----------|-------------| | ---- | ---- | -------- | ----------- |
| `id` | path | Yes | Tag ID | | `id` | path | Yes | Tag ID |
#### Responses #### Responses
| Status | Description | | Status | Description |
|--------|-------------| | ------ | --------------------- |
| 200 | Tag | | 200 | Tag |
| 401 | Unauthorized | | 401 | Unauthorized |
| 404 | Not found | | 404 | Not found |
@ -140,13 +140,13 @@ See `docs/api/openapi.json` for the full schema.
#### Parameters #### Parameters
| Name | In | Required | Description | | Name | In | Required | Description |
|------|----|----------|-------------| | ---- | ---- | -------- | ----------- |
| `id` | path | Yes | Tag ID | | `id` | path | Yes | Tag ID |
#### Responses #### Responses
| Status | Description | | Status | Description |
|--------|-------------| | ------ | --------------------- |
| 200 | Tag deleted | | 200 | Tag deleted |
| 401 | Unauthorized | | 401 | Unauthorized |
| 403 | Forbidden | | 403 | Forbidden |
@ -154,4 +154,3 @@ See `docs/api/openapi.json` for the full schema.
| 500 | Internal server error | | 500 | Internal server error |
--- ---

15
docs/api/transcode.md vendored
View file

@ -11,7 +11,7 @@ Video transcoding sessions
#### Parameters #### Parameters
| Name | In | Required | Description | | Name | In | Required | Description |
|------|----|----------|-------------| | ---- | ---- | -------- | ------------- |
| `id` | path | Yes | Media item ID | | `id` | path | Yes | Media item ID |
#### Request Body #### Request Body
@ -23,7 +23,7 @@ See `docs/api/openapi.json` for the full schema.
#### Responses #### Responses
| Status | Description | | Status | Description |
|--------|-------------| | ------ | ----------------------- |
| 200 | Transcode job submitted | | 200 | Transcode job submitted |
| 400 | Bad request | | 400 | Bad request |
| 401 | Unauthorized | | 401 | Unauthorized |
@ -38,7 +38,7 @@ See `docs/api/openapi.json` for the full schema.
#### Responses #### Responses
| Status | Description | | Status | Description |
|--------|-------------| | ------ | -------------------------- |
| 200 | List of transcode sessions | | 200 | List of transcode sessions |
| 401 | Unauthorized | | 401 | Unauthorized |
@ -51,13 +51,13 @@ See `docs/api/openapi.json` for the full schema.
#### Parameters #### Parameters
| Name | In | Required | Description | | Name | In | Required | Description |
|------|----|----------|-------------| | ---- | ---- | -------- | -------------------- |
| `id` | path | Yes | Transcode session ID | | `id` | path | Yes | Transcode session ID |
#### Responses #### Responses
| Status | Description | | Status | Description |
|--------|-------------| | ------ | ------------------------- |
| 200 | Transcode session details | | 200 | Transcode session details |
| 401 | Unauthorized | | 401 | Unauthorized |
| 404 | Not found | | 404 | Not found |
@ -71,16 +71,15 @@ See `docs/api/openapi.json` for the full schema.
#### Parameters #### Parameters
| Name | In | Required | Description | | Name | In | Required | Description |
|------|----|----------|-------------| | ---- | ---- | -------- | -------------------- |
| `id` | path | Yes | Transcode session ID | | `id` | path | Yes | Transcode session ID |
#### Responses #### Responses
| Status | Description | | Status | Description |
|--------|-------------| | ------ | --------------------------- |
| 200 | Transcode session cancelled | | 200 | Transcode session cancelled |
| 401 | Unauthorized | | 401 | Unauthorized |
| 404 | Not found | | 404 | Not found |
--- ---

25
docs/api/upload.md vendored
View file

@ -6,15 +6,14 @@ File upload and managed storage
### GET /api/v1/managed/stats ### GET /api/v1/managed/stats
Get managed storage statistics Get managed storage statistics GET /api/managed/stats
GET /api/managed/stats
**Authentication:** Required (Bearer JWT) **Authentication:** Required (Bearer JWT)
#### Responses #### Responses
| Status | Description | | Status | Description |
|--------|-------------| | ------ | -------------------------- |
| 200 | Managed storage statistics | | 200 | Managed storage statistics |
| 401 | Unauthorized | | 401 | Unauthorized |
| 500 | Internal server error | | 500 | Internal server error |
@ -23,21 +22,20 @@ GET /api/managed/stats
### GET /api/v1/media/{id}/download ### GET /api/v1/media/{id}/download
Download a managed file Download a managed file GET /api/media/{id}/download
GET /api/media/{id}/download
**Authentication:** Required (Bearer JWT) **Authentication:** Required (Bearer JWT)
#### Parameters #### Parameters
| Name | In | Required | Description | | Name | In | Required | Description |
|------|----|----------|-------------| | ---- | ---- | -------- | ------------- |
| `id` | path | Yes | Media item ID | | `id` | path | Yes | Media item ID |
#### Responses #### Responses
| Status | Description | | Status | Description |
|--------|-------------| | ------ | ------------ |
| 200 | File content | | 200 | File content |
| 400 | Bad request | | 400 | Bad request |
| 401 | Unauthorized | | 401 | Unauthorized |
@ -47,21 +45,20 @@ GET /api/media/{id}/download
### POST /api/v1/media/{id}/move-to-managed ### POST /api/v1/media/{id}/move-to-managed
Migrate an external file to managed storage Migrate an external file to managed storage POST /api/media/{id}/move-to-managed
POST /api/media/{id}/move-to-managed
**Authentication:** Required (Bearer JWT) **Authentication:** Required (Bearer JWT)
#### Parameters #### Parameters
| Name | In | Required | Description | | Name | In | Required | Description |
|------|----|----------|-------------| | ---- | ---- | -------- | ------------- |
| `id` | path | Yes | Media item ID | | `id` | path | Yes | Media item ID |
#### Responses #### Responses
| Status | Description | | Status | Description |
|--------|-------------| | ------ | --------------------- |
| 204 | File migrated | | 204 | File migrated |
| 400 | Bad request | | 400 | Bad request |
| 401 | Unauthorized | | 401 | Unauthorized |
@ -71,19 +68,17 @@ POST /api/media/{id}/move-to-managed
### POST /api/v1/upload ### POST /api/v1/upload
Upload a file to managed storage Upload a file to managed storage POST /api/upload
POST /api/upload
**Authentication:** Required (Bearer JWT) **Authentication:** Required (Bearer JWT)
#### Responses #### Responses
| Status | Description | | Status | Description |
|--------|-------------| | ------ | --------------------- |
| 200 | File uploaded | | 200 | File uploaded |
| 400 | Bad request | | 400 | Bad request |
| 401 | Unauthorized | | 401 | Unauthorized |
| 500 | Internal server error | | 500 | Internal server error |
--- ---

29
docs/api/users.md vendored
View file

@ -13,7 +13,7 @@ List all users (admin only)
#### Responses #### Responses
| Status | Description | | Status | Description |
|--------|-------------| | ------ | ------------- |
| 200 | List of users | | 200 | List of users |
| 401 | Unauthorized | | 401 | Unauthorized |
| 403 | Forbidden | | 403 | Forbidden |
@ -36,7 +36,7 @@ See `docs/api/openapi.json` for the full schema.
#### Responses #### Responses
| Status | Description | | Status | Description |
|--------|-------------| | ------ | --------------------- |
| 200 | User created | | 200 | User created |
| 400 | Bad request | | 400 | Bad request |
| 401 | Unauthorized | | 401 | Unauthorized |
@ -54,13 +54,13 @@ Get a specific user by ID
#### Parameters #### Parameters
| Name | In | Required | Description | | Name | In | Required | Description |
|------|----|----------|-------------| | ---- | ---- | -------- | ----------- |
| `id` | path | Yes | User ID | | `id` | path | Yes | User ID |
#### Responses #### Responses
| Status | Description | | Status | Description |
|--------|-------------| | ------ | ------------ |
| 200 | User details | | 200 | User details |
| 401 | Unauthorized | | 401 | Unauthorized |
| 403 | Forbidden | | 403 | Forbidden |
@ -77,7 +77,7 @@ Update a user
#### Parameters #### Parameters
| Name | In | Required | Description | | Name | In | Required | Description |
|------|----|----------|-------------| | ---- | ---- | -------- | ----------- |
| `id` | path | Yes | User ID | | `id` | path | Yes | User ID |
#### Request Body #### Request Body
@ -90,7 +90,7 @@ See `docs/api/openapi.json` for the full schema.
#### Responses #### Responses
| Status | Description | | Status | Description |
|--------|-------------| | ------ | ------------ |
| 200 | User updated | | 200 | User updated |
| 400 | Bad request | | 400 | Bad request |
| 401 | Unauthorized | | 401 | Unauthorized |
@ -108,13 +108,13 @@ Delete a user (admin only)
#### Parameters #### Parameters
| Name | In | Required | Description | | Name | In | Required | Description |
|------|----|----------|-------------| | ---- | ---- | -------- | ----------- |
| `id` | path | Yes | User ID | | `id` | path | Yes | User ID |
#### Responses #### Responses
| Status | Description | | Status | Description |
|--------|-------------| | ------ | ------------ |
| 200 | User deleted | | 200 | User deleted |
| 401 | Unauthorized | | 401 | Unauthorized |
| 403 | Forbidden | | 403 | Forbidden |
@ -131,13 +131,13 @@ Get user's accessible libraries
#### Parameters #### Parameters
| Name | In | Required | Description | | Name | In | Required | Description |
|------|----|----------|-------------| | ---- | ---- | -------- | ----------- |
| `id` | path | Yes | User ID | | `id` | path | Yes | User ID |
#### Responses #### Responses
| Status | Description | | Status | Description |
|--------|-------------| | ------ | -------------- |
| 200 | User libraries | | 200 | User libraries |
| 401 | Unauthorized | | 401 | Unauthorized |
| 403 | Forbidden | | 403 | Forbidden |
@ -153,7 +153,7 @@ Grant library access to a user (admin only)
#### Parameters #### Parameters
| Name | In | Required | Description | | Name | In | Required | Description |
|------|----|----------|-------------| | ---- | ---- | -------- | ----------- |
| `id` | path | Yes | User ID | | `id` | path | Yes | User ID |
#### Request Body #### Request Body
@ -165,7 +165,7 @@ See `docs/api/openapi.json` for the full schema.
#### Responses #### Responses
| Status | Description | | Status | Description |
|--------|-------------| | ------ | -------------- |
| 200 | Access granted | | 200 | Access granted |
| 400 | Bad request | | 400 | Bad request |
| 401 | Unauthorized | | 401 | Unauthorized |
@ -185,7 +185,7 @@ slashes that conflict with URL routing.
#### Parameters #### Parameters
| Name | In | Required | Description | | Name | In | Required | Description |
|------|----|----------|-------------| | ---- | ---- | -------- | ----------- |
| `id` | path | Yes | User ID | | `id` | path | Yes | User ID |
#### Request Body #### Request Body
@ -197,11 +197,10 @@ See `docs/api/openapi.json` for the full schema.
#### Responses #### Responses
| Status | Description | | Status | Description |
|--------|-------------| | ------ | -------------- |
| 200 | Access revoked | | 200 | Access revoked |
| 400 | Bad request | | 400 | Bad request |
| 401 | Unauthorized | | 401 | Unauthorized |
| 403 | Forbidden | | 403 | Forbidden |
--- ---

View file

@ -11,7 +11,7 @@ Webhook configuration
#### Responses #### Responses
| Status | Description | | Status | Description |
|--------|-------------| | ------ | --------------------------- |
| 200 | List of configured webhooks | | 200 | List of configured webhooks |
| 401 | Unauthorized | | 401 | Unauthorized |
| 403 | Forbidden | | 403 | Forbidden |
@ -25,10 +25,9 @@ Webhook configuration
#### Responses #### Responses
| Status | Description | | Status | Description |
|--------|-------------| | ------ | ----------------- |
| 200 | Test webhook sent | | 200 | Test webhook sent |
| 401 | Unauthorized | | 401 | Unauthorized |
| 403 | Forbidden | | 403 | Forbidden |
--- ---

View file

@ -1,518 +0,0 @@
# Pinakes Plugin Examples
This directory contains example plugins demonstrating the Pinakes plugin system.
## Overview
Pinakes supports extensibility through a WASM-based plugin system. Plugins can
extend Pinakes functionality by:
- **Media Type Providers**: Add support for new file formats
- **Metadata Extractors**: Extract metadata from files
- **Thumbnail Generators**: Generate thumbnails for media types
- **Search Backends**: Implement custom search algorithms
- **Event Handlers**: React to system events
- **Theme Providers**: Provide custom UI themes
## Example Plugins
### 1. Markdown Metadata Extractor
**Directory**: `markdown-metadata/`
Enhances markdown file support with advanced frontmatter parsing.
**Demonstrates**:
- Metadata extraction from files
- Plugin configuration via `plugin.toml`
- Minimal capability requirements
**Plugin Kind**: `metadata_extractor`
### 2. HEIF/HEIC Support
**Directory**: `heif-support/`
Adds support for HEIF and HEIC image formats.
**Demonstrates**:
- Media type registration
- Metadata extraction from binary formats
- Thumbnail generation
- Resource limits (memory, CPU time)
**Plugin Kinds**: `media_type`, `metadata_extractor`, `thumbnail_generator`
## Plugin Architecture
### Plugin Structure
```
my-plugin/
├── plugin.toml # Plugin manifest
├── Cargo.toml # Rust project configuration
├── src/
│ └── lib.rs # Plugin implementation
└── README.md # Plugin documentation
```
### Plugin Manifest (plugin.toml)
```toml
[plugin]
name = "my-plugin"
version = "1.0.0"
api_version = "1.0"
author = "Your Name"
description = "Description of your plugin"
kind = ["metadata_extractor"]
[plugin.binary]
wasm = "my_plugin.wasm"
[capabilities]
network = false
[capabilities.filesystem]
read = ["/path/to/read"]
write = ["/path/to/write"]
[config]
# Plugin-specific configuration
option1 = "value1"
option2 = 42
```
### Manifest Fields
#### [plugin] Section
- `name`: Plugin identifier (must be unique)
- `version`: Semantic version (e.g., "1.0.0")
- `api_version`: Pinakes Plugin API version (currently "1.0")
- `author`: Plugin author (optional)
- `description`: Short description (optional)
- `homepage`: Plugin homepage URL (optional)
- `license`: License identifier (optional)
- `kind`: Array of plugin kinds
- `dependencies`: Array of plugin names this plugin depends on (optional)
#### [plugin.binary] Section
- `wasm`: Path to WASM binary (relative to manifest)
- `entrypoint`: Custom entrypoint function name (optional, default: "_start")
#### [capabilities] Section
Capabilities define what the plugin can access:
**Filesystem**:
```toml
[capabilities.filesystem]
read = ["/tmp/cache", "/var/data"]
write = ["/tmp/output"]
```
**Network**:
```toml
[capabilities]
network = true # or false
```
**Environment**:
```toml
[capabilities]
environment = ["PATH", "HOME"] # or omit for no access
```
**Resource Limits**:
```toml
[capabilities]
max_memory_mb = 128
max_cpu_time_secs = 10
```
### Plugin Kinds
#### media_type
Register new media types with file extensions and MIME types.
**Trait**: `MediaTypeProvider`
**Methods**:
- `supported_media_types()`: Returns list of media type definitions
- `can_handle(path, mime_type)`: Check if plugin can handle a file
#### metadata_extractor
Extract metadata from files.
**Trait**: `MetadataExtractor`
**Methods**:
- `extract_metadata(path)`: Extract metadata from file
- `supported_types()`: Returns list of supported media type IDs
#### thumbnail_generator
Generate thumbnails for media files.
**Trait**: `ThumbnailGenerator`
**Methods**:
- `generate_thumbnail(path, output_path, options)`: Generate thumbnail
- `supported_types()`: Returns list of supported media type IDs
#### search_backend
Implement custom search algorithms.
**Trait**: `SearchBackend`
**Methods**:
- `index_item(item)`: Index a media item
- `remove_item(item_id)`: Remove item from index
- `search(query)`: Perform search
- `get_stats()`: Get index statistics
#### event_handler
React to system events.
**Trait**: `EventHandler`
**Methods**:
- `handle_event(event)`: Handle an event
- `interested_events()`: Returns list of event types to receive
#### theme_provider
Provide UI themes.
**Trait**: `ThemeProvider`
**Methods**:
- `get_themes()`: List available themes
- `load_theme(theme_id)`: Load theme data
## Creating a Plugin
### Step 1: Set Up Project
```bash
# Create new Rust library project
cargo new --lib my-plugin
cd my-plugin
# Add dependencies
cat >> Cargo.toml <<EOF
[dependencies]
pinakes-plugin-api = { path = "../../crates/pinakes-plugin-api" }
async-trait = "0.1"
serde = { version = "1.0", features = ["derive"] }
[lib]
crate-type = ["cdylib"]
[profile.release]
opt-level = "z"
lto = true
EOF
```
### Step 2: Implement Plugin
```rust
use async_trait::async_trait;
use pinakes_plugin_api::*;
use std::path::PathBuf;
pub struct MyPlugin {
context: Option<PluginContext>,
}
#[async_trait]
impl Plugin for MyPlugin {
fn metadata(&self) -> &PluginMetadata {
// Return plugin metadata
}
async fn initialize(&mut self, context: PluginContext) -> PluginResult<()> {
self.context = Some(context);
Ok(())
}
async fn shutdown(&mut self) -> PluginResult<()> {
Ok(())
}
async fn health_check(&self) -> PluginResult<HealthStatus> {
Ok(HealthStatus {
healthy: true,
message: None,
metrics: Default::default(),
})
}
}
#[async_trait]
impl MetadataExtractor for MyPlugin {
async fn extract_metadata(&self, path: &PathBuf) -> PluginResult<ExtractedMetadata> {
// Extract metadata from file
}
fn supported_types(&self) -> Vec<String> {
vec!["my_type".to_string()]
}
}
```
### Step 3: Build to WASM
```bash
# Install WASM target
rustup target add wasm32-wasi
# Build
cargo build --target wasm32-wasi --release
# Optimize (optional, wasm-tools provides wasm-strip functionality)
cargo install wasm-tools
wasm-tools strip target/wasm32-wasi/release/my_plugin.wasm -o target/wasm32-wasi/release/my_plugin.wasm
# Copy to plugin directory
cp target/wasm32-wasi/release/my_plugin.wasm .
```
### Step 4: Create Manifest
Create `plugin.toml` with appropriate configuration (see examples above).
### Step 5: Install Plugin
```bash
# Copy to plugins directory
cp -r my-plugin ~/.config/pinakes/plugins/
# Or use API
curl -X POST http://localhost:3000/api/v1/plugins/install \
-d '{"source": "/path/to/my-plugin"}'
```
## Testing Plugins
### Unit Tests
```rust
#[cfg(test)]
mod tests {
use super::*;
#[tokio::test]
async fn test_metadata_extraction() {
let mut plugin = MyPlugin::default();
let context = PluginContext {
data_dir: PathBuf::from("/tmp/data"),
cache_dir: PathBuf::from("/tmp/cache"),
config: Default::default(),
capabilities: Default::default(),
};
plugin.initialize(context).await.unwrap();
let metadata = plugin
.extract_metadata(&PathBuf::from("test.txt"))
.await
.unwrap();
assert!(metadata.title.is_some());
}
}
```
### Integration Tests
```bash
# Load plugin in test Pinakes instance
pinakes --config test-config.toml plugin load /path/to/plugin
# Verify plugin is loaded
pinakes plugin list
# Test plugin functionality
pinakes scan /path/to/test/files
```
## Security Considerations
### Capability-Based Security
Plugins operate in a sandbox with explicit capabilities. Only request the
minimum capabilities needed:
**Good**:
```toml
[capabilities.filesystem]
read = ["/tmp/cache"]
```
**Bad**:
```toml
[capabilities.filesystem]
read = ["/", "/home", "/etc"] # Too broad!
```
### Resource Limits
Always set appropriate resource limits:
```toml
[capabilities]
max_memory_mb = 128 # Reasonable for image processing
max_cpu_time_secs = 10 # Prevent runaway operations
```
### Input Validation
Always validate input in your plugin:
```rust
async fn extract_metadata(&self, path: &PathBuf) -> PluginResult<ExtractedMetadata> {
// Check file exists
if !path.exists() {
return Err(PluginError::InvalidInput("File not found".to_string()));
}
// Check file size
let metadata = std::fs::metadata(path)
.map_err(|e| PluginError::IoError(e.to_string()))?;
if metadata.len() > 10_000_000 { // 10MB limit
return Err(PluginError::InvalidInput("File too large".to_string()));
}
// Process file...
}
```
## Best Practices
### Error Handling
- Use descriptive error messages
- Return appropriate `PluginError` variants
- Don't panic - return errors instead
### Performance
- Avoid blocking operations in async functions
- Use streaming for large files
- Implement timeouts for external operations
- Cache results when appropriate
### Configuration
- Provide sensible defaults
- Document all configuration options
- Validate configuration during initialization
### Documentation
- Write clear README with examples
- Document all configuration options
- Include troubleshooting section
- Provide integration examples
## API Reference
See the
[pinakes-plugin-api documentation](../../crates/pinakes-plugin-api/README.md)
for detailed API reference.
## Plugin Distribution
### Plugin Registry (Future)
A centralized plugin registry is planned for future releases:
```bash
# Install from registry
pinakes plugin install markdown-metadata
# Search plugins
pinakes plugin search heif
# Update all plugins
pinakes plugin update --all
```
### Manual Distribution
Currently, plugins are distributed as directories containing:
- `plugin.toml` manifest
- WASM binary
- README and documentation
## Troubleshooting
### Plugin Won't Load
**Check manifest syntax**:
```bash
# Validate TOML syntax
taplo check plugin.toml
```
**Check API version**: Ensure `api_version = "1.0"` in manifest.
**Check binary path**: Verify WASM binary exists at path specified in
`plugin.binary.wasm`.
### Plugin Crashes
**Check resource limits**: Increase `max_memory_mb` or `max_cpu_time_secs` if
operations are timing out.
**Check capabilities**: Ensure plugin has necessary filesystem/network
capabilities.
**Check logs**:
```bash
# View plugin logs
tail -f ~/.local/share/pinakes/logs/plugin.log
```
### Permission Denied Errors
**Filesystem capabilities**: Add required paths to
`capabilities.filesystem.read` or `.write`.
**Network capabilities**: Set `capabilities.network = true` if plugin needs
network access.
## Support
- **Issues**: https://github.com/notashelf/pinakes/issues
- **Discussions**: https://github.com/notashelf/pinakes/discussions
- **Documentation**: https://pinakes.readthedocs.io
## License
All example plugins are licensed under MIT.

View file

@ -1,257 +0,0 @@
# HEIF/HEIC Support Plugin
This example plugin adds support for HEIF (High Efficiency Image Format) and HEIC (HEIF Container) to Pinakes.
## Overview
HEIF is a modern image format that provides better compression than JPEG while maintaining higher quality. This plugin enables Pinakes to:
- Recognize HEIF/HEIC files as a media type
- Extract metadata from HEIF images
- Generate thumbnails from HEIF images
## Features
- **Media Type Registration**: Registers `.heif`, `.heic`, `.hif` extensions as image media types
- **EXIF Extraction**: Extracts EXIF metadata including camera info, GPS coordinates, timestamps
- **Thumbnail Generation**: Generates thumbnails in JPEG, PNG, or WebP format
- **Resource Limits**: Configurable memory and CPU limits for safe processing
- **Large Image Support**: Handles images up to 8192x8192 pixels
## Supported Formats
- **HEIF**: High Efficiency Image Format (`.heif`, `.hif`)
- **HEIC**: HEIF Container format used by Apple devices (`.heic`)
- **HEIF Sequences**: Multi-image HEIF files
- **HEIF with Alpha**: HEIF images with transparency
## Implementation
The plugin implements three traits:
### MediaTypeProvider
```rust
#[async_trait]
impl MediaTypeProvider for HeifPlugin {
fn supported_media_types(&self) -> Vec<MediaTypeDefinition> {
vec![MediaTypeDefinition {
id: "heif".to_string(),
name: "HEIF Image".to_string(),
category: "image".to_string(),
extensions: vec!["heif".to_string(), "heic".to_string(), "hif".to_string()],
mime_types: vec!["image/heif".to_string(), "image/heic".to_string()],
icon: Some("image".to_string()),
}]
}
async fn can_handle(&self, path: &PathBuf, mime_type: Option<&str>) -> PluginResult<bool> {
// Check file extension and/or MIME type
}
}
```
### MetadataExtractor
```rust
#[async_trait]
impl MetadataExtractor for HeifPlugin {
async fn extract_metadata(&self, path: &PathBuf) -> PluginResult<ExtractedMetadata> {
// 1. Parse HEIF file structure
// 2. Extract EXIF metadata
// 3. Get image dimensions
// 4. Return ExtractedMetadata
}
fn supported_types(&self) -> Vec<String> {
vec!["heif".to_string()]
}
}
```
### ThumbnailGenerator
```rust
#[async_trait]
impl ThumbnailGenerator for HeifPlugin {
async fn generate_thumbnail(
&self,
path: &PathBuf,
output_path: &PathBuf,
options: ThumbnailOptions,
) -> PluginResult<ThumbnailInfo> {
// 1. Decode HEIF image
// 2. Resize to thumbnail dimensions
// 3. Encode to output format
// 4. Save to output_path
// 5. Return ThumbnailInfo
}
fn supported_types(&self) -> Vec<String> {
vec!["heif".to_string()]
}
}
```
## Dependencies
The plugin uses the following Rust crates (compiled to WASM):
- `libheif-rs`: HEIF decoding and encoding
- `image`: Image processing and thumbnail generation
- `kamadak-exif`: EXIF metadata parsing
## Building
### Prerequisites
```bash
# Install WASM target
rustup target add wasm32-wasi
# Install wasm-tools for optimization (provides strip functionality)
cargo install wasm-tools
```
### Build Process
```bash
# Build the plugin
cargo build --target wasm32-wasi --release
# Strip debug symbols to reduce size
wasm-tools strip target/wasm32-wasi/release/heif_support.wasm -o target/wasm32-wasi/release/heif_support.wasm
# Copy to plugin directory
cp target/wasm32-wasi/release/heif_support.wasm .
```
### Size Optimization
```bash
# Use wasm-opt for further optimization
wasm-opt -Oz heif_support.wasm -o heif_support_optimized.wasm
```
## Installation
### Manual Installation
```bash
# Copy plugin directory to Pinakes plugins directory
cp -r examples/plugins/heif-support ~/.config/pinakes/plugins/
```
### Via API
```bash
curl -X POST http://localhost:3000/api/v1/plugins/install \
-H "Content-Type: application/json" \
-d '{"source": "/path/to/heif-support"}'
```
### Via Plugin Manager
```bash
pinakes plugin install /path/to/heif-support
```
## Configuration
The plugin can be configured through the `config` section in `plugin.toml`:
### EXIF Extraction
- `extract_exif`: Enable EXIF metadata extraction (default: true)
### Thumbnail Generation
- `generate_thumbnails`: Enable thumbnail generation (default: true)
- `thumbnail_quality`: JPEG quality for thumbnails, 1-100 (default: 85)
- `thumbnail_format`: Output format - "jpeg", "png", or "webp" (default: "jpeg")
### Resource Limits
- `max_memory_mb`: Maximum memory the plugin can use in megabytes (default: 256, set in `[capabilities]`)
- `max_width`: Maximum image width to process (default: 8192)
- `max_height`: Maximum image height to process (default: 8192)
## Security
### Capabilities
- **Filesystem Read**: Only files being processed (via Pinakes)
- **Filesystem Write**: Thumbnail directory only
- **Network**: Disabled
- **Environment**: No access
### Resource Limits
- **Memory**: 256 MB maximum
- **CPU Time**: 30 seconds maximum per operation
### Sandboxing
The plugin runs in a WASM sandbox with:
- No access to host filesystem beyond granted paths
- No network access
- No arbitrary code execution
- Memory and CPU time limits enforced by runtime
## Performance
### Typical Performance
- **Metadata Extraction**: ~50-100ms for typical HEIF files
- **Thumbnail Generation**: ~200-500ms depending on source image size
- **Memory Usage**: 50-150 MB typical, 256 MB maximum
### Optimization Tips
1. Keep source images below 8192x8192 for best performance
2. Use JPEG thumbnail format for smaller file sizes
3. Adjust thumbnail quality vs file size tradeoff with `thumbnail_quality`
## Error Handling
The plugin handles:
- **Corrupted Files**: Returns descriptive error
- **Unsupported Variants**: Gracefully skips unsupported HEIF features
- **Memory Limits**: Fails safely if image too large
- **Timeout**: Returns error if processing exceeds CPU time limit
## Testing
```bash
# Run unit tests
cargo test
# Test with sample HEIF files
cargo test --test integration -- --nocapture
```
## Troubleshooting
### Plugin Fails to Load
- Check that `heif_support.wasm` exists in plugin directory
- Verify `plugin.toml` is valid TOML
- Check Pinakes logs for detailed error messages
### Thumbnails Not Generated
- Verify `generate_thumbnails = true` in config
- Check filesystem write permissions for thumbnail directory
- Ensure source image is below size limits
### Out of Memory Errors
- Reduce `max_width` and `max_height` in config
- Increase `max_memory_mb` if source images are large
- Check that source files aren't corrupted
## Future Enhancements
- Support for HEIF image sequences (burst photos)
- HDR metadata extraction
- Live Photo support
- AVIF format support (similar to HEIF)
## License
MIT

View file

@ -1,29 +0,0 @@
[plugin]
name = "heif-support"
version = "1.0.0"
api_version = "1.0"
author = "Pinakes Contributors"
description = "HEIF/HEIC image format support for Pinakes"
homepage = "https://github.com/notashelf/pinakes"
license = "MIT"
kind = ["media_type", "metadata_extractor", "thumbnail_generator"]
[plugin.binary]
wasm = "heif_support.wasm"
[capabilities]
network = false
max_memory_mb = 256
max_cpu_time_secs = 30
[capabilities.filesystem]
read = ["/media"]
write = ["/tmp/pinakes"]
[config]
extract_exif = true
generate_thumbnails = true
thumbnail_quality = 85
thumbnail_format = "jpeg"
max_width = 8192
max_height = 8192

View file

@ -1,103 +0,0 @@
# Markdown Metadata Extractor Plugin
This example plugin demonstrates how to create a metadata extractor plugin for Pinakes.
## Overview
The Markdown Metadata Extractor enhances Pinakes' built-in markdown support by:
- Parsing YAML and TOML frontmatter
- Extracting metadata from frontmatter fields
- Converting frontmatter tags to Pinakes media tags
- Extracting custom fields from frontmatter
## Features
- **Frontmatter Parsing**: Supports both YAML (`---`) and TOML (`+++`) frontmatter formats
- **Tag Extraction**: Automatically extracts tags from frontmatter and applies them to media items
- **Custom Fields**: Preserves all frontmatter fields as custom metadata
- **Configuration**: Configurable via `plugin.toml` config section
## Frontmatter Example
```markdown
---
title: "My Document"
author: "John Doe"
date: "2024-01-15"
tags: ["documentation", "example", "markdown"]
category: "tutorials"
draft: false
---
# My Document
Content goes here...
```
## Implementation
The plugin implements the `MetadataExtractor` trait from `pinakes-plugin-api`:
```rust
#[async_trait]
impl MetadataExtractor for MarkdownMetadataPlugin {
async fn extract_metadata(&self, path: &PathBuf) -> PluginResult<ExtractedMetadata> {
// 1. Read the file
// 2. Parse frontmatter
// 3. Extract metadata fields
// 4. Return ExtractedMetadata
}
fn supported_types(&self) -> Vec<String> {
vec!["markdown".to_string()]
}
}
```
## Building
The plugin is compiled to WebAssembly:
```bash
cargo build --target wasm32-wasi --release
wasm-strip target/wasm32-wasi/release/markdown_metadata.wasm
cp target/wasm32-wasi/release/markdown_metadata.wasm .
```
## Installation
```bash
# Copy plugin directory to Pinakes plugins directory
cp -r examples/plugins/markdown-metadata ~/.config/pinakes/plugins/
# Or via API
curl -X POST http://localhost:3000/api/v1/plugins/install \
-H "Content-Type: application/json" \
-d '{"source": "/path/to/markdown-metadata"}'
```
## Configuration
The plugin can be configured through the `config` section in `plugin.toml`:
- `extract_tags`: Extract tags from frontmatter (default: true)
- `parse_yaml`: Parse YAML frontmatter (default: true)
- `parse_toml`: Parse TOML frontmatter (default: true)
- `max_file_size`: Maximum file size to process in bytes (default: 10MB)
## Security
This plugin has minimal capabilities:
- **Filesystem**: No write access, read access only to files being processed
- **Network**: Disabled
- **Environment**: No access
## Testing
```bash
cargo test
```
## License
MIT

View file

@ -1,25 +0,0 @@
[plugin]
name = "markdown-metadata"
version = "1.0.0"
api_version = "1.0"
author = "Pinakes Contributors"
description = "Extract metadata from Markdown files with YAML frontmatter"
homepage = "https://github.com/notashelf/pinakes"
license = "MIT"
kind = ["metadata_extractor"]
[plugin.binary]
wasm = "markdown_metadata.wasm"
[capabilities]
network = false
[capabilities.filesystem]
read = []
write = []
[config]
extract_tags = true
parse_yaml = true
parse_toml = true
max_file_size = 10485760

Binary file not shown.

View file

@ -1,20 +0,0 @@
[workspace]
[package]
name = "media-stats-ui"
version = "1.0.0"
edition = "2024"
description = "Library statistics dashboard and tag manager, a UI-only Pinakes plugin"
license = "EUPL-1.2"
[lib]
name = "media_stats_ui"
crate-type = ["cdylib"]
[dependencies]
dlmalloc = { version = "0.2.12", features = ["global"] }
[profile.release]
opt-level = "s"
lto = true
strip = true

View file

@ -1,132 +0,0 @@
{
"id": "stats",
"title": "Library Statistics",
"route": "/plugins/media-stats-ui/stats",
"icon": "chart-bar",
"layout": {
"type": "tabs",
"default_tab": 0,
"tabs": [
{
"label": "Overview",
"content": {
"type": "container",
"gap": 24,
"children": [
{
"type": "heading",
"level": 2,
"content": "Library Statistics"
},
{
"type": "text",
"content": "Live summary of your media library. Refreshes every 30 seconds.",
"variant": "secondary"
},
{
"type": "card",
"title": "Summary",
"content": [
{
"type": "description_list",
"data": "stats",
"horizontal": true
}
]
},
{
"type": "chart",
"chart_type": "bar",
"data": "type-breakdown",
"title": "Files by Type",
"x_axis_label": "Media Type",
"y_axis_label": "Count",
"height": 280
}
]
}
},
{
"label": "Recent Files",
"content": {
"type": "container",
"gap": 16,
"children": [
{
"type": "heading",
"level": 2,
"content": "Recently Added"
},
{
"type": "data_table",
"data": "recent",
"sortable": true,
"filterable": true,
"page_size": 10,
"columns": [
{
"key": "file_name",
"header": "Filename"
},
{
"key": "title",
"header": "Title"
},
{
"key": "media_type",
"header": "Type"
},
{
"key": "file_size",
"header": "Size",
"data_type": "file_size"
},
{
"key": "created_at",
"header": "Added",
"data_type": "date_time"
}
]
}
]
}
},
{
"label": "Media Grid",
"content": {
"type": "container",
"gap": 16,
"children": [
{
"type": "heading",
"level": 2,
"content": "Browse Media"
},
{
"type": "media_grid",
"data": "recent",
"columns": 4,
"gap": 12
}
]
}
}
]
},
"data_sources": {
"stats": {
"type": "endpoint",
"path": "/api/v1/statistics",
"poll_interval": 30
},
"recent": {
"type": "endpoint",
"path": "/api/v1/media"
},
"type-breakdown": {
"type": "transform",
"source": "stats",
"expression": "stats.media_by_type"
}
}
}

View file

@ -1,126 +0,0 @@
{
"id": "tag-manager",
"title": "Tag Manager",
"route": "/plugins/media-stats-ui/tag-manager",
"icon": "tag",
"layout": {
"type": "tabs",
"default_tab": 0,
"tabs": [
{
"label": "All Tags",
"content": {
"type": "container",
"gap": 16,
"children": [
{
"type": "heading",
"level": 2,
"content": "Manage Tags"
},
{
"type": "conditional",
"condition": {
"op": "eq",
"left": { "function": "len", "args": ["tags"] },
"right": 0
},
"then": {
"type": "text",
"content": "No tags yet. Use the 'Create Tag' tab to add one.",
"variant": "secondary"
},
"else": {
"type": "data_table",
"data": "tags",
"sortable": true,
"filterable": true,
"page_size": 20,
"columns": [
{ "key": "name", "header": "Tag Name" },
{ "key": "color", "header": "Color" },
{ "key": "item_count", "header": "Items", "data_type": "number" }
]
}
}
]
}
},
{
"label": "Create Tag",
"content": {
"type": "container",
"gap": 24,
"children": [
{
"type": "heading",
"level": 2,
"content": "Create New Tag"
},
{
"type": "text",
"content": "Tags are used to organise media items. Choose a name and an optional colour.",
"variant": "secondary"
},
{
"type": "form",
"submit_label": "Create Tag",
"submit_action": "create-tag",
"cancel_label": "Reset",
"fields": [
{
"id": "name",
"label": "Tag Name",
"type": { "type": "text", "max_length": 64 },
"required": true,
"placeholder": "e.g. favourite, to-watch, archived",
"help_text": "Must be unique. Alphanumeric characters, spaces, and hyphens.",
"validation": [
{ "type": "min_length", "value": 1 },
{ "type": "max_length", "value": 64 },
{ "type": "pattern", "regex": "^[a-zA-Z0-9 \\-]+$" }
]
},
{
"id": "color",
"label": "Colour",
"type": {
"type": "select",
"options": [
{ "value": "#ef4444", "label": "Red" },
{ "value": "#f97316", "label": "Orange" },
{ "value": "#eab308", "label": "Yellow" },
{ "value": "#22c55e", "label": "Green" },
{ "value": "#3b82f6", "label": "Blue" },
{ "value": "#8b5cf6", "label": "Purple" },
{ "value": "#ec4899", "label": "Pink" },
{ "value": "#6b7280", "label": "Grey" }
]
},
"required": false,
"default_value": "#3b82f6",
"help_text": "Optional accent colour shown beside the tag."
}
]
}
]
}
}
]
},
"data_sources": {
"tags": {
"type": "endpoint",
"path": "/api/v1/tags",
"poll_interval": 0
}
},
"actions": {
"create-tag": {
"method": "POST",
"path": "/api/v1/tags",
"success_message": "Tag created successfully!",
"error_message": "Failed to create tag: the name may already be in use."
}
}
}

View file

@ -1,39 +0,0 @@
[plugin]
name = "media-stats-ui"
version = "1.0.0"
api_version = "1.0"
author = "Pinakes Contributors"
description = "Library statistics dashboard and tag manager UI plugin"
homepage = "https://github.com/notashelf/pinakes"
license = "EUPL-1.2"
kind = ["ui_page"]
[plugin.binary]
wasm = "target/wasm32-unknown-unknown/release/media_stats_ui.wasm"
[capabilities]
network = false
[capabilities.filesystem]
read = []
write = []
[ui]
required_endpoints = ["/api/v1/statistics", "/api/v1/media", "/api/v1/tags"]
# UI pages
[[ui.pages]]
file = "pages/stats.json"
[[ui.pages]]
file = "pages/tag-manager.json"
# Widgets injected into host views
[[ui.widgets]]
id = "stats-badge"
target = "library_header"
[ui.widgets.content]
type = "badge"
text = "Stats"
variant = "info"

View file

@ -1,101 +0,0 @@
//! Media Stats UI - Pinakes plugin
//!
//! A UI-only plugin that adds a library statistics dashboard and a tag manager
//! page. All UI definitions live in `pages/stats.json` and
//! `pages/tag-manager.json`; this WASM binary provides the minimum lifecycle
//! surface the host runtime requires.
//!
//! This plugin is kind = ["ui_page"]: no media-type, metadata, thumbnail, or
//! event-handler extension points are needed. The host will never call them,
//! but exporting them avoids linker warnings if the host performs capability
//! discovery via symbol inspection.
#![no_std]
extern crate alloc;
use core::alloc::Layout;
#[global_allocator]
static ALLOC: dlmalloc::GlobalDlmalloc = dlmalloc::GlobalDlmalloc;
#[panic_handler]
fn panic(_: &core::panic::PanicInfo) -> ! {
core::arch::wasm32::unreachable()
}
// Host functions provided by the Pinakes runtime.
unsafe extern "C" {
// Write a result value back to the host (ptr + byte length).
fn host_set_result(ptr: i32, len: i32);
// Emit a structured log message to the host logger.
// `level` mirrors tracing severity: 0=trace 1=debug 2=info 3=warn 4=error
fn host_log(level: i32, ptr: i32, len: i32);
}
/// # Safety
///
/// `json` is a valid slice; the host copies the bytes before
/// returning so there are no lifetime concerns.
fn set_response(json: &[u8]) {
unsafe { host_set_result(json.as_ptr() as i32, json.len() as i32) }
}
/// # Safety
///
/// Same as [`set_response`]
fn log_info(msg: &[u8]) {
unsafe { host_log(2, msg.as_ptr() as i32, msg.len() as i32) }
}
/// Allocate a buffer for the host to write request data into.
///
/// # Returns
///
/// The byte offset of the allocation, or -1 on failure.
///
/// # Safety
///
/// Size is positive; Layout construction cannot fail for align=1.
#[unsafe(no_mangle)]
pub extern "C" fn alloc(size: i32) -> i32 {
if size <= 0 {
return 0;
}
unsafe {
let layout = Layout::from_size_align_unchecked(size as usize, 1);
let ptr = alloc::alloc::alloc(layout);
if ptr.is_null() { -1 } else { ptr as i32 }
}
}
/// Called once after the plugin is loaded. Returns 0 on success.
#[unsafe(no_mangle)]
pub extern "C" fn initialize() -> i32 {
log_info(b"media-stats-ui: initialized");
0
}
/// Called before the plugin is unloaded. Returns 0 on success.
#[unsafe(no_mangle)]
pub extern "C" fn shutdown() -> i32 {
log_info(b"media-stats-ui: shutdown");
0
}
/// # Returns
///
/// an empty JSON array; this plugin adds no custom media types.
#[unsafe(no_mangle)]
pub extern "C" fn supported_media_types(_ptr: i32, _len: i32) {
set_response(b"[]");
}
/// # Returns
///
/// An empty JSON array; this plugin handles no event types.
#[unsafe(no_mangle)]
pub extern "C" fn interested_events(_ptr: i32, _len: i32) {
set_response(b"[]");
}

BIN
flake.lock generated

Binary file not shown.

View file

@ -21,5 +21,38 @@
in { in {
default = pkgs.callPackage ./nix/shell.nix {}; default = pkgs.callPackage ./nix/shell.nix {};
}); });
formatter = forEachSystem (system: let
pkgs = nixpkgs.legacyPackages.${system};
in
pkgs.writeShellApplication {
name = "nix3-fmt-wrapper";
runtimeInputs = [
pkgs.alejandra
pkgs.fd
pkgs.prettier
pkgs.deno
pkgs.taplo
pkgs.sql-formatter
];
text = ''
# Format Nix with Alejandra
fd "$@" -t f -e nix -x alejandra -q '{}'
# Format TOML with Taplo
fd "$@" -t f -e toml -x taplo fmt '{}'
# Format CSS with Prettier
fd "$@" -t f -e css -x prettier --write '{}'
# Format SQL with sql-format
fd "$@" -t f -e sql -x sql-formatter --fix '{}' -l postgresql
# Format Markdown with Deno
fd "$@" -t f -e md -x deno fmt -q '{}'
'';
});
}; };
} }

40
justfile Normal file
View file

@ -0,0 +1,40 @@
# Default recipe to show help
@default:
just --list
# Build all crates
build-all: build-server build-tui build-ui
# Build the server crate
build-server:
cargo build -p pinakes-server
# Build the TUI crate
build-tui:
cargo build -p pinakes-tui
# Build the UI using Dioxus CLI. The UI *has* to be built with `dx`, because CSS
# is not correctly embedded otherwise.
build-ui:
dx build -p pinakes-ui
# Generate REST API documentation using cargo xtask
@docs:
cargo xtask docs
# Run all tests
@test:
cargo nextest run --workspace
# Format code
@fmt:
cargo fmt
# Run clippy linting
@lint:
cargo clippy --workspace
# Clean build artifacts
@clean:
cargo clean
rm -rf target/dx/

View file

@ -1,10 +1,10 @@
-- Add file_mtime column to media_items table for incremental scanning -- Add file_mtime column to media_items table for incremental scanning
-- Stores Unix timestamp in seconds of the file's modification time -- Stores Unix timestamp in seconds of the file's modification time
ALTER TABLE media_items
ALTER TABLE media_items ADD COLUMN file_mtime BIGINT; ADD COLUMN file_mtime BIGINT;
-- Create index for quick mtime lookups -- Create index for quick mtime lookups
CREATE INDEX IF NOT EXISTS idx_media_items_file_mtime ON media_items(file_mtime); CREATE INDEX IF NOT EXISTS idx_media_items_file_mtime ON media_items (file_mtime);
-- Create a scan_history table to track when each directory was last scanned -- Create a scan_history table to track when each directory was last scanned
CREATE TABLE IF NOT EXISTS scan_history ( CREATE TABLE IF NOT EXISTS scan_history (
@ -16,4 +16,4 @@ CREATE TABLE IF NOT EXISTS scan_history (
scan_duration_ms INTEGER scan_duration_ms INTEGER
); );
CREATE INDEX IF NOT EXISTS idx_scan_history_directory ON scan_history(directory); CREATE INDEX IF NOT EXISTS idx_scan_history_directory ON scan_history (directory);

View file

@ -1,6 +1,5 @@
-- Session persistence for database-backed sessions -- Session persistence for database-backed sessions
-- Replaces in-memory session storage -- Replaces in-memory session storage
CREATE TABLE IF NOT EXISTS sessions ( CREATE TABLE IF NOT EXISTS sessions (
session_token TEXT PRIMARY KEY NOT NULL, session_token TEXT PRIMARY KEY NOT NULL,
user_id TEXT, user_id TEXT,
@ -12,7 +11,7 @@ CREATE TABLE IF NOT EXISTS sessions (
); );
-- Index for efficient cleanup of expired sessions -- Index for efficient cleanup of expired sessions
CREATE INDEX IF NOT EXISTS idx_sessions_expires_at ON sessions(expires_at); CREATE INDEX IF NOT EXISTS idx_sessions_expires_at ON sessions (expires_at);
-- Index for listing sessions by username -- Index for listing sessions by username
CREATE INDEX IF NOT EXISTS idx_sessions_username ON sessions(username); CREATE INDEX IF NOT EXISTS idx_sessions_username ON sessions (username);

View file

@ -1,9 +1,8 @@
-- V12: Book Management Schema (PostgreSQL) -- V12: Book Management Schema (PostgreSQL)
-- Adds comprehensive book metadata tracking, authors, and identifiers -- Adds comprehensive book metadata tracking, authors, and identifiers
-- Book metadata (supplements media_items for EPUB/PDF/MOBI) -- Book metadata (supplements media_items for EPUB/PDF/MOBI)
CREATE TABLE book_metadata ( CREATE TABLE book_metadata (
media_id UUID PRIMARY KEY REFERENCES media_items(id) ON DELETE CASCADE, media_id UUID PRIMARY KEY REFERENCES media_items (id) ON DELETE CASCADE,
isbn TEXT, isbn TEXT,
isbn13 TEXT, -- Normalized ISBN-13 for lookups isbn13 TEXT, -- Normalized ISBN-13 for lookups
publisher TEXT, publisher TEXT,
@ -17,14 +16,17 @@ CREATE TABLE book_metadata (
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW() updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
); );
CREATE INDEX idx_book_isbn13 ON book_metadata(isbn13); CREATE INDEX idx_book_isbn13 ON book_metadata (isbn13);
CREATE INDEX idx_book_series ON book_metadata(series_name, series_index);
CREATE INDEX idx_book_publisher ON book_metadata(publisher); CREATE INDEX idx_book_series ON book_metadata (series_name, series_index);
CREATE INDEX idx_book_language ON book_metadata(language);
CREATE INDEX idx_book_publisher ON book_metadata (publisher);
CREATE INDEX idx_book_language ON book_metadata (language);
-- Multiple authors per book (many-to-many) -- Multiple authors per book (many-to-many)
CREATE TABLE book_authors ( CREATE TABLE book_authors (
media_id UUID NOT NULL REFERENCES media_items(id) ON DELETE CASCADE, media_id UUID NOT NULL REFERENCES media_items (id) ON DELETE CASCADE,
author_name TEXT NOT NULL, author_name TEXT NOT NULL,
author_sort TEXT, -- "Last, First" for sorting author_sort TEXT, -- "Last, First" for sorting
role TEXT NOT NULL DEFAULT 'author', -- author, translator, editor, illustrator role TEXT NOT NULL DEFAULT 'author', -- author, translator, editor, illustrator
@ -32,29 +34,28 @@ CREATE TABLE book_authors (
PRIMARY KEY (media_id, author_name, role) PRIMARY KEY (media_id, author_name, role)
); );
CREATE INDEX idx_book_authors_name ON book_authors(author_name); CREATE INDEX idx_book_authors_name ON book_authors (author_name);
CREATE INDEX idx_book_authors_sort ON book_authors(author_sort);
CREATE INDEX idx_book_authors_sort ON book_authors (author_sort);
-- Multiple identifiers (ISBN variants, ASIN, DOI, etc.) -- Multiple identifiers (ISBN variants, ASIN, DOI, etc.)
CREATE TABLE book_identifiers ( CREATE TABLE book_identifiers (
media_id UUID NOT NULL REFERENCES media_items(id) ON DELETE CASCADE, media_id UUID NOT NULL REFERENCES media_items (id) ON DELETE CASCADE,
identifier_type TEXT NOT NULL, -- isbn, isbn13, asin, doi, lccn, oclc identifier_type TEXT NOT NULL, -- isbn, isbn13, asin, doi, lccn, oclc
identifier_value TEXT NOT NULL, identifier_value TEXT NOT NULL,
PRIMARY KEY (media_id, identifier_type, identifier_value) PRIMARY KEY (media_id, identifier_type, identifier_value)
); );
CREATE INDEX idx_book_identifiers ON book_identifiers(identifier_type, identifier_value); CREATE INDEX idx_book_identifiers ON book_identifiers (identifier_type, identifier_value);
-- Trigger to update updated_at on book_metadata changes -- Trigger to update updated_at on book_metadata changes
CREATE OR REPLACE FUNCTION update_book_metadata_timestamp() CREATE OR REPLACE FUNCTION update_book_metadata_timestamp () RETURNS TRIGGER AS $$
RETURNS TRIGGER AS $$
BEGIN BEGIN
NEW.updated_at = NOW(); NEW.updated_at = NOW();
RETURN NEW; RETURN NEW;
END; END;
$$ LANGUAGE plpgsql; $$ LANGUAGE plpgsql;
CREATE TRIGGER update_book_metadata_timestamp CREATE TRIGGER update_book_metadata_timestamp BEFORE
BEFORE UPDATE ON book_metadata UPDATE ON book_metadata FOR EACH ROW
FOR EACH ROW EXECUTE FUNCTION update_book_metadata_timestamp ();
EXECUTE FUNCTION update_book_metadata_timestamp();

View file

@ -1,15 +1,40 @@
-- V13: Enhanced photo metadata support -- V13: Enhanced photo metadata support
-- Add photo-specific fields to media_items table -- Add photo-specific fields to media_items table
ALTER TABLE media_items
ADD COLUMN date_taken TIMESTAMPTZ;
ALTER TABLE media_items ADD COLUMN date_taken TIMESTAMPTZ; ALTER TABLE media_items
ALTER TABLE media_items ADD COLUMN latitude DOUBLE PRECISION; ADD COLUMN latitude DOUBLE PRECISION;
ALTER TABLE media_items ADD COLUMN longitude DOUBLE PRECISION;
ALTER TABLE media_items ADD COLUMN camera_make TEXT; ALTER TABLE media_items
ALTER TABLE media_items ADD COLUMN camera_model TEXT; ADD COLUMN longitude DOUBLE PRECISION;
ALTER TABLE media_items ADD COLUMN rating INTEGER CHECK (rating >= 0 AND rating <= 5);
ALTER TABLE media_items
ADD COLUMN camera_make TEXT;
ALTER TABLE media_items
ADD COLUMN camera_model TEXT;
ALTER TABLE media_items
ADD COLUMN rating INTEGER CHECK (
rating >= 0
AND rating <= 5
);
-- Indexes for photo queries -- Indexes for photo queries
CREATE INDEX idx_media_date_taken ON media_items(date_taken) WHERE date_taken IS NOT NULL; CREATE INDEX idx_media_date_taken ON media_items (date_taken)
CREATE INDEX idx_media_location ON media_items(latitude, longitude) WHERE latitude IS NOT NULL AND longitude IS NOT NULL; WHERE
CREATE INDEX idx_media_camera ON media_items(camera_make) WHERE camera_make IS NOT NULL; date_taken IS NOT NULL;
CREATE INDEX idx_media_rating ON media_items(rating) WHERE rating IS NOT NULL;
CREATE INDEX idx_media_location ON media_items (latitude, longitude)
WHERE
latitude IS NOT NULL
AND longitude IS NOT NULL;
CREATE INDEX idx_media_camera ON media_items (camera_make)
WHERE
camera_make IS NOT NULL;
CREATE INDEX idx_media_rating ON media_items (rating)
WHERE
rating IS NOT NULL;

View file

@ -1,7 +1,9 @@
-- V14: Perceptual hash for duplicate detection -- V14: Perceptual hash for duplicate detection
-- Add perceptual hash column for image similarity detection -- Add perceptual hash column for image similarity detection
ALTER TABLE media_items
ALTER TABLE media_items ADD COLUMN perceptual_hash TEXT; ADD COLUMN perceptual_hash TEXT;
-- Index for perceptual hash lookups -- Index for perceptual hash lookups
CREATE INDEX idx_media_phash ON media_items(perceptual_hash) WHERE perceptual_hash IS NOT NULL; CREATE INDEX idx_media_phash ON media_items (perceptual_hash)
WHERE
perceptual_hash IS NOT NULL;

View file

@ -1,17 +1,20 @@
-- V15: Managed File Storage -- V15: Managed File Storage
-- Adds server-side content-addressable storage for uploaded files -- Adds server-side content-addressable storage for uploaded files
-- Add storage mode to media_items (external = file on disk, managed = in content-addressable storage) -- Add storage mode to media_items (external = file on disk, managed = in content-addressable storage)
ALTER TABLE media_items ADD COLUMN storage_mode TEXT NOT NULL DEFAULT 'external'; ALTER TABLE media_items
ADD COLUMN storage_mode TEXT NOT NULL DEFAULT 'external';
-- Original filename for managed uploads (preserved separately from file_name which may be normalized) -- Original filename for managed uploads (preserved separately from file_name which may be normalized)
ALTER TABLE media_items ADD COLUMN original_filename TEXT; ALTER TABLE media_items
ADD COLUMN original_filename TEXT;
-- When the file was uploaded to managed storage -- When the file was uploaded to managed storage
ALTER TABLE media_items ADD COLUMN uploaded_at TIMESTAMPTZ; ALTER TABLE media_items
ADD COLUMN uploaded_at TIMESTAMPTZ;
-- Storage key for looking up the blob (usually same as content_hash for deduplication) -- Storage key for looking up the blob (usually same as content_hash for deduplication)
ALTER TABLE media_items ADD COLUMN storage_key TEXT; ALTER TABLE media_items
ADD COLUMN storage_key TEXT;
-- Managed blobs table - tracks deduplicated file storage -- Managed blobs table - tracks deduplicated file storage
CREATE TABLE managed_blobs ( CREATE TABLE managed_blobs (
@ -24,7 +27,7 @@ CREATE TABLE managed_blobs (
); );
-- Index for finding managed media items -- Index for finding managed media items
CREATE INDEX idx_media_storage_mode ON media_items(storage_mode); CREATE INDEX idx_media_storage_mode ON media_items (storage_mode);
-- Index for finding orphaned blobs (reference_count = 0) -- Index for finding orphaned blobs (reference_count = 0)
CREATE INDEX idx_blobs_reference_count ON managed_blobs(reference_count); CREATE INDEX idx_blobs_reference_count ON managed_blobs (reference_count);

View file

@ -1,10 +1,9 @@
-- V16: Cross-Device Sync System -- V16: Cross-Device Sync System
-- Adds device registration, change tracking, and chunked upload support -- Adds device registration, change tracking, and chunked upload support
-- Sync devices table -- Sync devices table
CREATE TABLE sync_devices ( CREATE TABLE sync_devices (
id TEXT PRIMARY KEY NOT NULL, id TEXT PRIMARY KEY NOT NULL,
user_id TEXT NOT NULL REFERENCES users(id) ON DELETE CASCADE, user_id TEXT NOT NULL REFERENCES users (id) ON DELETE CASCADE,
name TEXT NOT NULL, name TEXT NOT NULL,
device_type TEXT NOT NULL, device_type TEXT NOT NULL,
client_version TEXT NOT NULL, client_version TEXT NOT NULL,
@ -18,37 +17,44 @@ CREATE TABLE sync_devices (
updated_at TIMESTAMPTZ NOT NULL updated_at TIMESTAMPTZ NOT NULL
); );
CREATE INDEX idx_sync_devices_user ON sync_devices(user_id); CREATE INDEX idx_sync_devices_user ON sync_devices (user_id);
CREATE INDEX idx_sync_devices_token ON sync_devices(device_token_hash);
CREATE INDEX idx_sync_devices_token ON sync_devices (device_token_hash);
-- Sync log table - tracks all changes for sync -- Sync log table - tracks all changes for sync
CREATE TABLE sync_log ( CREATE TABLE sync_log (
id TEXT PRIMARY KEY NOT NULL, id TEXT PRIMARY KEY NOT NULL,
sequence BIGSERIAL UNIQUE NOT NULL, sequence BIGSERIAL UNIQUE NOT NULL,
change_type TEXT NOT NULL, change_type TEXT NOT NULL,
media_id TEXT REFERENCES media_items(id) ON DELETE SET NULL, media_id TEXT REFERENCES media_items (id) ON DELETE SET NULL,
path TEXT NOT NULL, path TEXT NOT NULL,
content_hash TEXT, content_hash TEXT,
file_size BIGINT, file_size BIGINT,
metadata_json TEXT, metadata_json TEXT,
changed_by_device TEXT REFERENCES sync_devices(id) ON DELETE SET NULL, changed_by_device TEXT REFERENCES sync_devices (id) ON DELETE SET NULL,
timestamp TIMESTAMPTZ NOT NULL timestamp TIMESTAMPTZ NOT NULL
); );
CREATE INDEX idx_sync_log_sequence ON sync_log(sequence); CREATE INDEX idx_sync_log_sequence ON sync_log (sequence);
CREATE INDEX idx_sync_log_path ON sync_log(path);
CREATE INDEX idx_sync_log_timestamp ON sync_log(timestamp); CREATE INDEX idx_sync_log_path ON sync_log (path);
CREATE INDEX idx_sync_log_timestamp ON sync_log (timestamp);
-- Sequence counter for sync log -- Sequence counter for sync log
CREATE TABLE sync_sequence ( CREATE TABLE sync_sequence (
id INTEGER PRIMARY KEY CHECK (id = 1), id INTEGER PRIMARY KEY CHECK (id = 1),
current_value BIGINT NOT NULL DEFAULT 0 current_value BIGINT NOT NULL DEFAULT 0
); );
INSERT INTO sync_sequence (id, current_value) VALUES (1, 0);
INSERT INTO
sync_sequence (id, current_value)
VALUES
(1, 0);
-- Device sync state - tracks sync status per device per file -- Device sync state - tracks sync status per device per file
CREATE TABLE device_sync_state ( CREATE TABLE device_sync_state (
device_id TEXT NOT NULL REFERENCES sync_devices(id) ON DELETE CASCADE, device_id TEXT NOT NULL REFERENCES sync_devices (id) ON DELETE CASCADE,
path TEXT NOT NULL, path TEXT NOT NULL,
local_hash TEXT, local_hash TEXT,
server_hash TEXT, server_hash TEXT,
@ -60,12 +66,12 @@ CREATE TABLE device_sync_state (
PRIMARY KEY (device_id, path) PRIMARY KEY (device_id, path)
); );
CREATE INDEX idx_device_sync_status ON device_sync_state(device_id, sync_status); CREATE INDEX idx_device_sync_status ON device_sync_state (device_id, sync_status);
-- Upload sessions for chunked uploads -- Upload sessions for chunked uploads
CREATE TABLE upload_sessions ( CREATE TABLE upload_sessions (
id TEXT PRIMARY KEY NOT NULL, id TEXT PRIMARY KEY NOT NULL,
device_id TEXT NOT NULL REFERENCES sync_devices(id) ON DELETE CASCADE, device_id TEXT NOT NULL REFERENCES sync_devices (id) ON DELETE CASCADE,
target_path TEXT NOT NULL, target_path TEXT NOT NULL,
expected_hash TEXT NOT NULL, expected_hash TEXT NOT NULL,
expected_size BIGINT NOT NULL, expected_size BIGINT NOT NULL,
@ -77,15 +83,18 @@ CREATE TABLE upload_sessions (
last_activity TIMESTAMPTZ NOT NULL last_activity TIMESTAMPTZ NOT NULL
); );
CREATE INDEX idx_upload_sessions_device ON upload_sessions(device_id); CREATE INDEX idx_upload_sessions_device ON upload_sessions (device_id);
CREATE INDEX idx_upload_sessions_status ON upload_sessions(status);
CREATE INDEX idx_upload_sessions_expires ON upload_sessions(expires_at); CREATE INDEX idx_upload_sessions_status ON upload_sessions (status);
CREATE INDEX idx_upload_sessions_expires ON upload_sessions (expires_at);
-- Upload chunks - tracks received chunks -- Upload chunks - tracks received chunks
CREATE TABLE upload_chunks ( CREATE TABLE upload_chunks (
upload_id TEXT NOT NULL REFERENCES upload_sessions(id) ON DELETE CASCADE, upload_id TEXT NOT NULL REFERENCES upload_sessions (id) ON DELETE CASCADE,
chunk_index BIGINT NOT NULL, chunk_index BIGINT NOT NULL,
offset BIGINT NOT NULL, offset
BIGINT NOT NULL,
size BIGINT NOT NULL, size BIGINT NOT NULL,
hash TEXT NOT NULL, hash TEXT NOT NULL,
received_at TIMESTAMPTZ NOT NULL, received_at TIMESTAMPTZ NOT NULL,
@ -95,7 +104,7 @@ CREATE TABLE upload_chunks (
-- Sync conflicts -- Sync conflicts
CREATE TABLE sync_conflicts ( CREATE TABLE sync_conflicts (
id TEXT PRIMARY KEY NOT NULL, id TEXT PRIMARY KEY NOT NULL,
device_id TEXT NOT NULL REFERENCES sync_devices(id) ON DELETE CASCADE, device_id TEXT NOT NULL REFERENCES sync_devices (id) ON DELETE CASCADE,
path TEXT NOT NULL, path TEXT NOT NULL,
local_hash TEXT NOT NULL, local_hash TEXT NOT NULL,
local_mtime BIGINT NOT NULL, local_mtime BIGINT NOT NULL,
@ -106,5 +115,8 @@ CREATE TABLE sync_conflicts (
resolution TEXT resolution TEXT
); );
CREATE INDEX idx_sync_conflicts_device ON sync_conflicts(device_id); CREATE INDEX idx_sync_conflicts_device ON sync_conflicts (device_id);
CREATE INDEX idx_sync_conflicts_unresolved ON sync_conflicts(device_id) WHERE resolved_at IS NULL;
CREATE INDEX idx_sync_conflicts_unresolved ON sync_conflicts (device_id)
WHERE
resolved_at IS NULL;

View file

@ -1,14 +1,17 @@
-- V17: Enhanced Sharing System -- V17: Enhanced Sharing System
-- Replaces simple share_links with comprehensive sharing capabilities -- Replaces simple share_links with comprehensive sharing capabilities
-- Enhanced shares table -- Enhanced shares table
CREATE TABLE shares ( CREATE TABLE shares (
id TEXT PRIMARY KEY NOT NULL, id TEXT PRIMARY KEY NOT NULL,
target_type TEXT NOT NULL CHECK (target_type IN ('media', 'collection', 'tag', 'saved_search')), target_type TEXT NOT NULL CHECK (
target_type IN ('media', 'collection', 'tag', 'saved_search')
),
target_id TEXT NOT NULL, target_id TEXT NOT NULL,
owner_id TEXT NOT NULL REFERENCES users(id) ON DELETE CASCADE, owner_id TEXT NOT NULL REFERENCES users (id) ON DELETE CASCADE,
recipient_type TEXT NOT NULL CHECK (recipient_type IN ('public_link', 'user', 'group', 'federated')), recipient_type TEXT NOT NULL CHECK (
recipient_user_id TEXT REFERENCES users(id) ON DELETE CASCADE, recipient_type IN ('public_link', 'user', 'group', 'federated')
),
recipient_user_id TEXT REFERENCES users (id) ON DELETE CASCADE,
recipient_group_id TEXT, recipient_group_id TEXT,
recipient_federated_handle TEXT, recipient_federated_handle TEXT,
recipient_federated_server TEXT, recipient_federated_server TEXT,
@ -25,44 +28,58 @@ CREATE TABLE shares (
access_count BIGINT NOT NULL DEFAULT 0, access_count BIGINT NOT NULL DEFAULT 0,
last_accessed TIMESTAMPTZ, last_accessed TIMESTAMPTZ,
inherit_to_children BOOLEAN NOT NULL DEFAULT TRUE, inherit_to_children BOOLEAN NOT NULL DEFAULT TRUE,
parent_share_id TEXT REFERENCES shares(id) ON DELETE CASCADE, parent_share_id TEXT REFERENCES shares (id) ON DELETE CASCADE,
created_at TIMESTAMPTZ NOT NULL, created_at TIMESTAMPTZ NOT NULL,
updated_at TIMESTAMPTZ NOT NULL, updated_at TIMESTAMPTZ NOT NULL,
UNIQUE(owner_id, target_type, target_id, recipient_type, recipient_user_id) UNIQUE (
owner_id,
target_type,
target_id,
recipient_type,
recipient_user_id
)
); );
CREATE INDEX idx_shares_owner ON shares(owner_id); CREATE INDEX idx_shares_owner ON shares (owner_id);
CREATE INDEX idx_shares_recipient_user ON shares(recipient_user_id);
CREATE INDEX idx_shares_target ON shares(target_type, target_id); CREATE INDEX idx_shares_recipient_user ON shares (recipient_user_id);
CREATE INDEX idx_shares_token ON shares(public_token);
CREATE INDEX idx_shares_expires ON shares(expires_at); CREATE INDEX idx_shares_target ON shares (target_type, target_id);
CREATE INDEX idx_shares_token ON shares (public_token);
CREATE INDEX idx_shares_expires ON shares (expires_at);
-- Share activity log -- Share activity log
CREATE TABLE share_activity ( CREATE TABLE share_activity (
id TEXT PRIMARY KEY NOT NULL, id TEXT PRIMARY KEY NOT NULL,
share_id TEXT NOT NULL REFERENCES shares(id) ON DELETE CASCADE, share_id TEXT NOT NULL REFERENCES shares (id) ON DELETE CASCADE,
actor_id TEXT REFERENCES users(id) ON DELETE SET NULL, actor_id TEXT REFERENCES users (id) ON DELETE SET NULL,
actor_ip TEXT, actor_ip TEXT,
action TEXT NOT NULL, action TEXT NOT NULL,
details TEXT, details TEXT,
timestamp TIMESTAMPTZ NOT NULL timestamp TIMESTAMPTZ NOT NULL
); );
CREATE INDEX idx_share_activity_share ON share_activity(share_id); CREATE INDEX idx_share_activity_share ON share_activity (share_id);
CREATE INDEX idx_share_activity_timestamp ON share_activity(timestamp);
CREATE INDEX idx_share_activity_timestamp ON share_activity (timestamp);
-- Share notifications -- Share notifications
CREATE TABLE share_notifications ( CREATE TABLE share_notifications (
id TEXT PRIMARY KEY NOT NULL, id TEXT PRIMARY KEY NOT NULL,
user_id TEXT NOT NULL REFERENCES users(id) ON DELETE CASCADE, user_id TEXT NOT NULL REFERENCES users (id) ON DELETE CASCADE,
share_id TEXT NOT NULL REFERENCES shares(id) ON DELETE CASCADE, share_id TEXT NOT NULL REFERENCES shares (id) ON DELETE CASCADE,
notification_type TEXT NOT NULL, notification_type TEXT NOT NULL,
is_read BOOLEAN NOT NULL DEFAULT FALSE, is_read BOOLEAN NOT NULL DEFAULT FALSE,
created_at TIMESTAMPTZ NOT NULL created_at TIMESTAMPTZ NOT NULL
); );
CREATE INDEX idx_share_notifications_user ON share_notifications(user_id); CREATE INDEX idx_share_notifications_user ON share_notifications (user_id);
CREATE INDEX idx_share_notifications_unread ON share_notifications(user_id) WHERE is_read = FALSE;
CREATE INDEX idx_share_notifications_unread ON share_notifications (user_id)
WHERE
is_read = FALSE;
-- Migrate existing share_links to new shares table -- Migrate existing share_links to new shares table
DO $$ DO $$

View file

@ -1,11 +1,13 @@
-- V18: File Management (Rename, Move, Trash) -- V18: File Management (Rename, Move, Trash)
-- Adds soft delete support for trash/recycle bin functionality -- Adds soft delete support for trash/recycle bin functionality
-- Add deleted_at column for soft delete (trash) -- Add deleted_at column for soft delete (trash)
ALTER TABLE media_items ADD COLUMN deleted_at TIMESTAMPTZ; ALTER TABLE media_items
ADD COLUMN deleted_at TIMESTAMPTZ;
-- Index for efficient trash queries -- Index for efficient trash queries
CREATE INDEX idx_media_deleted_at ON media_items(deleted_at); CREATE INDEX idx_media_deleted_at ON media_items (deleted_at);
-- Partial index for listing non-deleted items (most common query pattern) -- Partial index for listing non-deleted items (most common query pattern)
CREATE INDEX idx_media_not_deleted ON media_items(id) WHERE deleted_at IS NULL; CREATE INDEX idx_media_not_deleted ON media_items (id)
WHERE
deleted_at IS NULL;

View file

@ -1,6 +1,5 @@
-- V19: Markdown Links (Obsidian-style bidirectional links) -- V19: Markdown Links (Obsidian-style bidirectional links)
-- Adds support for wikilinks, markdown links, embeds, and backlink tracking -- Adds support for wikilinks, markdown links, embeds, and backlink tracking
-- Table for storing extracted markdown links -- Table for storing extracted markdown links
CREATE TABLE IF NOT EXISTS markdown_links ( CREATE TABLE IF NOT EXISTS markdown_links (
id TEXT PRIMARY KEY NOT NULL, id TEXT PRIMARY KEY NOT NULL,
@ -12,24 +11,25 @@ CREATE TABLE IF NOT EXISTS markdown_links (
line_number INTEGER, -- line number in source file line_number INTEGER, -- line number in source file
context TEXT, -- surrounding text for preview context TEXT, -- surrounding text for preview
created_at TIMESTAMPTZ NOT NULL, created_at TIMESTAMPTZ NOT NULL,
FOREIGN KEY (source_media_id) REFERENCES media_items(id) ON DELETE CASCADE, FOREIGN KEY (source_media_id) REFERENCES media_items (id) ON DELETE CASCADE,
FOREIGN KEY (target_media_id) REFERENCES media_items(id) ON DELETE SET NULL FOREIGN KEY (target_media_id) REFERENCES media_items (id) ON DELETE SET NULL
); );
-- Index for efficient outgoing link queries (what does this note link to?) -- Index for efficient outgoing link queries (what does this note link to?)
CREATE INDEX idx_links_source ON markdown_links(source_media_id); CREATE INDEX idx_links_source ON markdown_links (source_media_id);
-- Index for efficient backlink queries (what links to this note?) -- Index for efficient backlink queries (what links to this note?)
CREATE INDEX idx_links_target ON markdown_links(target_media_id); CREATE INDEX idx_links_target ON markdown_links (target_media_id);
-- Index for path-based resolution (finding unresolved links) -- Index for path-based resolution (finding unresolved links)
CREATE INDEX idx_links_target_path ON markdown_links(target_path); CREATE INDEX idx_links_target_path ON markdown_links (target_path);
-- Index for link type filtering -- Index for link type filtering
CREATE INDEX idx_links_type ON markdown_links(link_type); CREATE INDEX idx_links_type ON markdown_links (link_type);
-- Track when links were last extracted from a media item -- Track when links were last extracted from a media item
ALTER TABLE media_items ADD COLUMN links_extracted_at TIMESTAMPTZ; ALTER TABLE media_items
ADD COLUMN links_extracted_at TIMESTAMPTZ;
-- Index for finding media items that need link extraction -- Index for finding media items that need link extraction
CREATE INDEX idx_media_links_extracted ON media_items(links_extracted_at); CREATE INDEX idx_media_links_extracted ON media_items (links_extracted_at);

View file

@ -1,9 +1,8 @@
CREATE EXTENSION IF NOT EXISTS "uuid-ossp"; CREATE EXTENSION IF NOT EXISTS "uuid-ossp";
CREATE EXTENSION IF NOT EXISTS pg_trgm; CREATE EXTENSION IF NOT EXISTS pg_trgm;
CREATE TABLE IF NOT EXISTS root_dirs ( CREATE TABLE IF NOT EXISTS root_dirs (path TEXT PRIMARY KEY NOT NULL);
path TEXT PRIMARY KEY NOT NULL
);
CREATE TABLE IF NOT EXISTS media_items ( CREATE TABLE IF NOT EXISTS media_items (
id UUID PRIMARY KEY NOT NULL, id UUID PRIMARY KEY NOT NULL,
@ -26,15 +25,18 @@ CREATE TABLE IF NOT EXISTS media_items (
CREATE TABLE IF NOT EXISTS tags ( CREATE TABLE IF NOT EXISTS tags (
id UUID PRIMARY KEY NOT NULL, id UUID PRIMARY KEY NOT NULL,
name TEXT NOT NULL, name TEXT NOT NULL,
parent_id UUID REFERENCES tags(id) ON DELETE SET NULL, parent_id UUID REFERENCES tags (id) ON DELETE SET NULL,
created_at TIMESTAMPTZ NOT NULL created_at TIMESTAMPTZ NOT NULL
); );
CREATE UNIQUE INDEX IF NOT EXISTS idx_tags_name_parent ON tags(name, COALESCE(parent_id, '00000000-0000-0000-0000-000000000000')); CREATE UNIQUE INDEX IF NOT EXISTS idx_tags_name_parent ON tags (
name,
COALESCE(parent_id, '00000000-0000-0000-0000-000000000000')
);
CREATE TABLE IF NOT EXISTS media_tags ( CREATE TABLE IF NOT EXISTS media_tags (
media_id UUID NOT NULL REFERENCES media_items(id) ON DELETE CASCADE, media_id UUID NOT NULL REFERENCES media_items (id) ON DELETE CASCADE,
tag_id UUID NOT NULL REFERENCES tags(id) ON DELETE CASCADE, tag_id UUID NOT NULL REFERENCES tags (id) ON DELETE CASCADE,
PRIMARY KEY (media_id, tag_id) PRIMARY KEY (media_id, tag_id)
); );
@ -49,8 +51,8 @@ CREATE TABLE IF NOT EXISTS collections (
); );
CREATE TABLE IF NOT EXISTS collection_members ( CREATE TABLE IF NOT EXISTS collection_members (
collection_id UUID NOT NULL REFERENCES collections(id) ON DELETE CASCADE, collection_id UUID NOT NULL REFERENCES collections (id) ON DELETE CASCADE,
media_id UUID NOT NULL REFERENCES media_items(id) ON DELETE CASCADE, media_id UUID NOT NULL REFERENCES media_items (id) ON DELETE CASCADE,
position INTEGER NOT NULL DEFAULT 0, position INTEGER NOT NULL DEFAULT 0,
added_at TIMESTAMPTZ NOT NULL, added_at TIMESTAMPTZ NOT NULL,
PRIMARY KEY (collection_id, media_id) PRIMARY KEY (collection_id, media_id)
@ -58,14 +60,14 @@ CREATE TABLE IF NOT EXISTS collection_members (
CREATE TABLE IF NOT EXISTS audit_log ( CREATE TABLE IF NOT EXISTS audit_log (
id UUID PRIMARY KEY NOT NULL, id UUID PRIMARY KEY NOT NULL,
media_id UUID REFERENCES media_items(id) ON DELETE SET NULL, media_id UUID REFERENCES media_items (id) ON DELETE SET NULL,
action TEXT NOT NULL, action TEXT NOT NULL,
details TEXT, details TEXT,
timestamp TIMESTAMPTZ NOT NULL timestamp TIMESTAMPTZ NOT NULL
); );
CREATE TABLE IF NOT EXISTS custom_fields ( CREATE TABLE IF NOT EXISTS custom_fields (
media_id UUID NOT NULL REFERENCES media_items(id) ON DELETE CASCADE, media_id UUID NOT NULL REFERENCES media_items (id) ON DELETE CASCADE,
field_name TEXT NOT NULL, field_name TEXT NOT NULL,
field_type TEXT NOT NULL, field_type TEXT NOT NULL,
field_value TEXT NOT NULL, field_value TEXT NOT NULL,

View file

@ -1,11 +1,12 @@
ALTER TABLE media_items ADD COLUMN IF NOT EXISTS search_vector tsvector ALTER TABLE media_items
GENERATED ALWAYS AS ( ADD COLUMN IF NOT EXISTS search_vector tsvector GENERATED ALWAYS AS (
setweight(to_tsvector('english', COALESCE(title, '')), 'A') || setweight(to_tsvector('english', COALESCE(title, '')), 'A') || setweight(to_tsvector('english', COALESCE(artist, '')), 'B') || setweight(to_tsvector('english', COALESCE(album, '')), 'B') || setweight(to_tsvector('english', COALESCE(genre, '')), 'C') || setweight(
setweight(to_tsvector('english', COALESCE(artist, '')), 'B') || to_tsvector('english', COALESCE(description, '')),
setweight(to_tsvector('english', COALESCE(album, '')), 'B') || 'C'
setweight(to_tsvector('english', COALESCE(genre, '')), 'C') || ) || setweight(
setweight(to_tsvector('english', COALESCE(description, '')), 'C') || to_tsvector('english', COALESCE(file_name, '')),
setweight(to_tsvector('english', COALESCE(file_name, '')), 'D') 'D'
) STORED; )
) STORED;
CREATE INDEX IF NOT EXISTS idx_media_search ON media_items USING GIN(search_vector); CREATE INDEX IF NOT EXISTS idx_media_search ON media_items USING GIN (search_vector);

View file

@ -1,8 +1,15 @@
CREATE INDEX IF NOT EXISTS idx_audit_media_id ON audit_log(media_id); CREATE INDEX IF NOT EXISTS idx_audit_media_id ON audit_log (media_id);
CREATE INDEX IF NOT EXISTS idx_audit_timestamp ON audit_log(timestamp);
CREATE INDEX IF NOT EXISTS idx_audit_action ON audit_log(action); CREATE INDEX IF NOT EXISTS idx_audit_timestamp ON audit_log (timestamp);
CREATE INDEX IF NOT EXISTS idx_media_content_hash ON media_items(content_hash);
CREATE INDEX IF NOT EXISTS idx_media_media_type ON media_items(media_type); CREATE INDEX IF NOT EXISTS idx_audit_action ON audit_log (action);
CREATE INDEX IF NOT EXISTS idx_media_created_at ON media_items(created_at);
CREATE INDEX IF NOT EXISTS idx_media_title_trgm ON media_items USING GIN(title gin_trgm_ops); CREATE INDEX IF NOT EXISTS idx_media_content_hash ON media_items (content_hash);
CREATE INDEX IF NOT EXISTS idx_media_artist_trgm ON media_items USING GIN(artist gin_trgm_ops);
CREATE INDEX IF NOT EXISTS idx_media_media_type ON media_items (media_type);
CREATE INDEX IF NOT EXISTS idx_media_created_at ON media_items (created_at);
CREATE INDEX IF NOT EXISTS idx_media_title_trgm ON media_items USING GIN (title gin_trgm_ops);
CREATE INDEX IF NOT EXISTS idx_media_artist_trgm ON media_items USING GIN (artist gin_trgm_ops);

View file

@ -1 +1,2 @@
ALTER TABLE media_items ADD COLUMN thumbnail_path TEXT; ALTER TABLE media_items
ADD COLUMN thumbnail_path TEXT;

View file

@ -1,6 +1,9 @@
-- Integrity tracking columns -- Integrity tracking columns
ALTER TABLE media_items ADD COLUMN last_verified_at TIMESTAMPTZ; ALTER TABLE media_items
ALTER TABLE media_items ADD COLUMN integrity_status TEXT DEFAULT 'unverified'; ADD COLUMN last_verified_at TIMESTAMPTZ;
ALTER TABLE media_items
ADD COLUMN integrity_status TEXT DEFAULT 'unverified';
-- Saved searches -- Saved searches
CREATE TABLE IF NOT EXISTS saved_searches ( CREATE TABLE IF NOT EXISTS saved_searches (

View file

@ -11,5 +11,6 @@ CREATE TABLE plugin_registry (
); );
-- Index for quick lookups -- Index for quick lookups
CREATE INDEX idx_plugin_registry_enabled ON plugin_registry(enabled); CREATE INDEX idx_plugin_registry_enabled ON plugin_registry (enabled);
CREATE INDEX idx_plugin_registry_name ON plugin_registry(name);
CREATE INDEX idx_plugin_registry_name ON plugin_registry (name);

View file

@ -16,7 +16,7 @@ CREATE TABLE user_profiles (
preferences_json JSONB NOT NULL DEFAULT '{}', preferences_json JSONB NOT NULL DEFAULT '{}',
created_at TIMESTAMP WITH TIME ZONE NOT NULL, created_at TIMESTAMP WITH TIME ZONE NOT NULL,
updated_at TIMESTAMP WITH TIME ZONE NOT NULL, updated_at TIMESTAMP WITH TIME ZONE NOT NULL,
FOREIGN KEY (user_id) REFERENCES users(id) FOREIGN KEY (user_id) REFERENCES users (id)
); );
-- User library access table -- User library access table
@ -26,10 +26,12 @@ CREATE TABLE user_libraries (
permission JSONB NOT NULL, permission JSONB NOT NULL,
granted_at TIMESTAMP WITH TIME ZONE NOT NULL, granted_at TIMESTAMP WITH TIME ZONE NOT NULL,
PRIMARY KEY (user_id, root_path), PRIMARY KEY (user_id, root_path),
FOREIGN KEY (user_id) REFERENCES users(id) FOREIGN KEY (user_id) REFERENCES users (id)
); );
-- Indexes for efficient lookups -- Indexes for efficient lookups
CREATE INDEX idx_users_username ON users(username); CREATE INDEX idx_users_username ON users (username);
CREATE INDEX idx_user_libraries_user_id ON user_libraries(user_id);
CREATE INDEX idx_user_libraries_root_path ON user_libraries(root_path); CREATE INDEX idx_user_libraries_user_id ON user_libraries (user_id);
CREATE INDEX idx_user_libraries_root_path ON user_libraries (root_path);

View file

@ -2,19 +2,22 @@
CREATE TABLE IF NOT EXISTS ratings ( CREATE TABLE IF NOT EXISTS ratings (
id UUID PRIMARY KEY, id UUID PRIMARY KEY,
user_id UUID NOT NULL, user_id UUID NOT NULL,
media_id UUID NOT NULL REFERENCES media_items(id) ON DELETE CASCADE, media_id UUID NOT NULL REFERENCES media_items (id) ON DELETE CASCADE,
stars INTEGER NOT NULL CHECK (stars >= 1 AND stars <= 5), stars INTEGER NOT NULL CHECK (
stars >= 1
AND stars <= 5
),
review_text TEXT, review_text TEXT,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
UNIQUE(user_id, media_id) UNIQUE (user_id, media_id)
); );
-- Comments -- Comments
CREATE TABLE IF NOT EXISTS comments ( CREATE TABLE IF NOT EXISTS comments (
id UUID PRIMARY KEY, id UUID PRIMARY KEY,
user_id UUID NOT NULL, user_id UUID NOT NULL,
media_id UUID NOT NULL REFERENCES media_items(id) ON DELETE CASCADE, media_id UUID NOT NULL REFERENCES media_items (id) ON DELETE CASCADE,
parent_comment_id UUID REFERENCES comments(id) ON DELETE CASCADE, parent_comment_id UUID REFERENCES comments (id) ON DELETE CASCADE,
text TEXT NOT NULL, text TEXT NOT NULL,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW() created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
); );
@ -22,7 +25,7 @@ CREATE TABLE IF NOT EXISTS comments (
-- Favorites -- Favorites
CREATE TABLE IF NOT EXISTS favorites ( CREATE TABLE IF NOT EXISTS favorites (
user_id UUID NOT NULL, user_id UUID NOT NULL,
media_id UUID NOT NULL REFERENCES media_items(id) ON DELETE CASCADE, media_id UUID NOT NULL REFERENCES media_items (id) ON DELETE CASCADE,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
PRIMARY KEY (user_id, media_id) PRIMARY KEY (user_id, media_id)
); );
@ -30,7 +33,7 @@ CREATE TABLE IF NOT EXISTS favorites (
-- Share links -- Share links
CREATE TABLE IF NOT EXISTS share_links ( CREATE TABLE IF NOT EXISTS share_links (
id UUID PRIMARY KEY, id UUID PRIMARY KEY,
media_id UUID NOT NULL REFERENCES media_items(id) ON DELETE CASCADE, media_id UUID NOT NULL REFERENCES media_items (id) ON DELETE CASCADE,
created_by UUID NOT NULL, created_by UUID NOT NULL,
token TEXT NOT NULL UNIQUE, token TEXT NOT NULL UNIQUE,
password_hash TEXT, password_hash TEXT,
@ -54,8 +57,8 @@ CREATE TABLE IF NOT EXISTS playlists (
-- Playlist items -- Playlist items
CREATE TABLE IF NOT EXISTS playlist_items ( CREATE TABLE IF NOT EXISTS playlist_items (
playlist_id UUID NOT NULL REFERENCES playlists(id) ON DELETE CASCADE, playlist_id UUID NOT NULL REFERENCES playlists (id) ON DELETE CASCADE,
media_id UUID NOT NULL REFERENCES media_items(id) ON DELETE CASCADE, media_id UUID NOT NULL REFERENCES media_items (id) ON DELETE CASCADE,
position INTEGER NOT NULL DEFAULT 0, position INTEGER NOT NULL DEFAULT 0,
added_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), added_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
PRIMARY KEY (playlist_id, media_id) PRIMARY KEY (playlist_id, media_id)
@ -64,7 +67,7 @@ CREATE TABLE IF NOT EXISTS playlist_items (
-- Usage events -- Usage events
CREATE TABLE IF NOT EXISTS usage_events ( CREATE TABLE IF NOT EXISTS usage_events (
id UUID PRIMARY KEY, id UUID PRIMARY KEY,
media_id UUID REFERENCES media_items(id) ON DELETE SET NULL, media_id UUID REFERENCES media_items (id) ON DELETE SET NULL,
user_id UUID, user_id UUID,
event_type TEXT NOT NULL, event_type TEXT NOT NULL,
timestamp TIMESTAMPTZ NOT NULL DEFAULT NOW(), timestamp TIMESTAMPTZ NOT NULL DEFAULT NOW(),
@ -72,24 +75,26 @@ CREATE TABLE IF NOT EXISTS usage_events (
context_json JSONB context_json JSONB
); );
CREATE INDEX IF NOT EXISTS idx_usage_events_media ON usage_events(media_id); CREATE INDEX IF NOT EXISTS idx_usage_events_media ON usage_events (media_id);
CREATE INDEX IF NOT EXISTS idx_usage_events_user ON usage_events(user_id);
CREATE INDEX IF NOT EXISTS idx_usage_events_timestamp ON usage_events(timestamp); CREATE INDEX IF NOT EXISTS idx_usage_events_user ON usage_events (user_id);
CREATE INDEX IF NOT EXISTS idx_usage_events_timestamp ON usage_events (timestamp);
-- Watch history / progress -- Watch history / progress
CREATE TABLE IF NOT EXISTS watch_history ( CREATE TABLE IF NOT EXISTS watch_history (
id UUID PRIMARY KEY, id UUID PRIMARY KEY,
user_id UUID NOT NULL, user_id UUID NOT NULL,
media_id UUID NOT NULL REFERENCES media_items(id) ON DELETE CASCADE, media_id UUID NOT NULL REFERENCES media_items (id) ON DELETE CASCADE,
progress_secs DOUBLE PRECISION NOT NULL DEFAULT 0, progress_secs DOUBLE PRECISION NOT NULL DEFAULT 0,
last_watched TIMESTAMPTZ NOT NULL DEFAULT NOW(), last_watched TIMESTAMPTZ NOT NULL DEFAULT NOW(),
UNIQUE(user_id, media_id) UNIQUE (user_id, media_id)
); );
-- Subtitles -- Subtitles
CREATE TABLE IF NOT EXISTS subtitles ( CREATE TABLE IF NOT EXISTS subtitles (
id UUID PRIMARY KEY, id UUID PRIMARY KEY,
media_id UUID NOT NULL REFERENCES media_items(id) ON DELETE CASCADE, media_id UUID NOT NULL REFERENCES media_items (id) ON DELETE CASCADE,
language TEXT, language TEXT,
format TEXT NOT NULL, format TEXT NOT NULL,
file_path TEXT, file_path TEXT,
@ -99,12 +104,12 @@ CREATE TABLE IF NOT EXISTS subtitles (
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW() created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
); );
CREATE INDEX IF NOT EXISTS idx_subtitles_media ON subtitles(media_id); CREATE INDEX IF NOT EXISTS idx_subtitles_media ON subtitles (media_id);
-- External metadata (enrichment) -- External metadata (enrichment)
CREATE TABLE IF NOT EXISTS external_metadata ( CREATE TABLE IF NOT EXISTS external_metadata (
id UUID PRIMARY KEY, id UUID PRIMARY KEY,
media_id UUID NOT NULL REFERENCES media_items(id) ON DELETE CASCADE, media_id UUID NOT NULL REFERENCES media_items (id) ON DELETE CASCADE,
source TEXT NOT NULL, source TEXT NOT NULL,
external_id TEXT, external_id TEXT,
metadata_json JSONB NOT NULL DEFAULT '{}', metadata_json JSONB NOT NULL DEFAULT '{}',
@ -112,12 +117,12 @@ CREATE TABLE IF NOT EXISTS external_metadata (
last_updated TIMESTAMPTZ NOT NULL DEFAULT NOW() last_updated TIMESTAMPTZ NOT NULL DEFAULT NOW()
); );
CREATE INDEX IF NOT EXISTS idx_external_metadata_media ON external_metadata(media_id); CREATE INDEX IF NOT EXISTS idx_external_metadata_media ON external_metadata (media_id);
-- Transcode sessions -- Transcode sessions
CREATE TABLE IF NOT EXISTS transcode_sessions ( CREATE TABLE IF NOT EXISTS transcode_sessions (
id UUID PRIMARY KEY, id UUID PRIMARY KEY,
media_id UUID NOT NULL REFERENCES media_items(id) ON DELETE CASCADE, media_id UUID NOT NULL REFERENCES media_items (id) ON DELETE CASCADE,
user_id UUID, user_id UUID,
profile TEXT NOT NULL, profile TEXT NOT NULL,
cache_path TEXT NOT NULL, cache_path TEXT NOT NULL,
@ -128,4 +133,4 @@ CREATE TABLE IF NOT EXISTS transcode_sessions (
expires_at TIMESTAMPTZ expires_at TIMESTAMPTZ
); );
CREATE INDEX IF NOT EXISTS idx_transcode_sessions_media ON transcode_sessions(media_id); CREATE INDEX IF NOT EXISTS idx_transcode_sessions_media ON transcode_sessions (media_id);

View file

@ -1,19 +1,26 @@
-- Drop redundant indexes (already covered by UNIQUE constraints) -- Drop redundant indexes (already covered by UNIQUE constraints)
DROP INDEX IF EXISTS idx_users_username; DROP INDEX IF EXISTS idx_users_username;
DROP INDEX IF EXISTS idx_user_libraries_user_id; DROP INDEX IF EXISTS idx_user_libraries_user_id;
-- Add missing indexes for comments table -- Add missing indexes for comments table
CREATE INDEX IF NOT EXISTS idx_comments_media ON comments(media_id); CREATE INDEX IF NOT EXISTS idx_comments_media ON comments (media_id);
CREATE INDEX IF NOT EXISTS idx_comments_parent ON comments(parent_comment_id);
CREATE INDEX IF NOT EXISTS idx_comments_parent ON comments (parent_comment_id);
-- Remove duplicates before adding unique constraint -- Remove duplicates before adding unique constraint
DELETE FROM external_metadata e1 DELETE FROM external_metadata e1
WHERE EXISTS ( WHERE
SELECT 1 FROM external_metadata e2 EXISTS (
WHERE e1.media_id = e2.media_id SELECT
1
FROM
external_metadata e2
WHERE
e1.media_id = e2.media_id
AND e1.source = e2.source AND e1.source = e2.source
AND e1.ctid < e2.ctid AND e1.ctid < e2.ctid
); );
-- Add unique constraint for external_metadata (idempotent) -- Add unique constraint for external_metadata (idempotent)
DO $$ DO $$

View file

@ -1,10 +1,10 @@
-- Add file_mtime column to media_items table for incremental scanning -- Add file_mtime column to media_items table for incremental scanning
-- Stores Unix timestamp in seconds of the file's modification time -- Stores Unix timestamp in seconds of the file's modification time
ALTER TABLE media_items
ALTER TABLE media_items ADD COLUMN file_mtime INTEGER; ADD COLUMN file_mtime INTEGER;
-- Create index for quick mtime lookups -- Create index for quick mtime lookups
CREATE INDEX IF NOT EXISTS idx_media_items_file_mtime ON media_items(file_mtime); CREATE INDEX IF NOT EXISTS idx_media_items_file_mtime ON media_items (file_mtime);
-- Create a scan_history table to track when each directory was last scanned -- Create a scan_history table to track when each directory was last scanned
CREATE TABLE IF NOT EXISTS scan_history ( CREATE TABLE IF NOT EXISTS scan_history (
@ -16,4 +16,4 @@ CREATE TABLE IF NOT EXISTS scan_history (
scan_duration_ms INTEGER scan_duration_ms INTEGER
); );
CREATE INDEX IF NOT EXISTS idx_scan_history_directory ON scan_history(directory); CREATE INDEX IF NOT EXISTS idx_scan_history_directory ON scan_history (directory);

View file

@ -1,6 +1,5 @@
-- Session persistence for database-backed sessions -- Session persistence for database-backed sessions
-- Replaces in-memory session storage -- Replaces in-memory session storage
CREATE TABLE IF NOT EXISTS sessions ( CREATE TABLE IF NOT EXISTS sessions (
session_token TEXT PRIMARY KEY NOT NULL, session_token TEXT PRIMARY KEY NOT NULL,
user_id TEXT, user_id TEXT,
@ -12,7 +11,7 @@ CREATE TABLE IF NOT EXISTS sessions (
); );
-- Index for efficient cleanup of expired sessions -- Index for efficient cleanup of expired sessions
CREATE INDEX IF NOT EXISTS idx_sessions_expires_at ON sessions(expires_at); CREATE INDEX IF NOT EXISTS idx_sessions_expires_at ON sessions (expires_at);
-- Index for listing sessions by username -- Index for listing sessions by username
CREATE INDEX IF NOT EXISTS idx_sessions_username ON sessions(username); CREATE INDEX IF NOT EXISTS idx_sessions_username ON sessions (username);

View file

@ -1,9 +1,8 @@
-- V12: Book Management Schema -- V12: Book Management Schema
-- Adds comprehensive book metadata tracking, authors, and identifiers -- Adds comprehensive book metadata tracking, authors, and identifiers
-- Book metadata (supplements media_items for EPUB/PDF/MOBI) -- Book metadata (supplements media_items for EPUB/PDF/MOBI)
CREATE TABLE book_metadata ( CREATE TABLE book_metadata (
media_id TEXT PRIMARY KEY REFERENCES media_items(id) ON DELETE CASCADE, media_id TEXT PRIMARY KEY REFERENCES media_items (id) ON DELETE CASCADE,
isbn TEXT, isbn TEXT,
isbn13 TEXT, -- Normalized ISBN-13 for lookups isbn13 TEXT, -- Normalized ISBN-13 for lookups
publisher TEXT, publisher TEXT,
@ -13,18 +12,21 @@ CREATE TABLE book_metadata (
series_name TEXT, series_name TEXT,
series_index REAL, -- Supports 1.5, etc. series_index REAL, -- Supports 1.5, etc.
format TEXT, -- 'epub', 'pdf', 'mobi', 'azw3' format TEXT, -- 'epub', 'pdf', 'mobi', 'azw3'
created_at TEXT NOT NULL DEFAULT (datetime('now')), created_at TEXT NOT NULL DEFAULT (datetime ('now')),
updated_at TEXT NOT NULL DEFAULT (datetime('now')) updated_at TEXT NOT NULL DEFAULT (datetime ('now'))
) STRICT; ) STRICT;
CREATE INDEX idx_book_isbn13 ON book_metadata(isbn13); CREATE INDEX idx_book_isbn13 ON book_metadata (isbn13);
CREATE INDEX idx_book_series ON book_metadata(series_name, series_index);
CREATE INDEX idx_book_publisher ON book_metadata(publisher); CREATE INDEX idx_book_series ON book_metadata (series_name, series_index);
CREATE INDEX idx_book_language ON book_metadata(language);
CREATE INDEX idx_book_publisher ON book_metadata (publisher);
CREATE INDEX idx_book_language ON book_metadata (language);
-- Multiple authors per book (many-to-many) -- Multiple authors per book (many-to-many)
CREATE TABLE book_authors ( CREATE TABLE book_authors (
media_id TEXT NOT NULL REFERENCES media_items(id) ON DELETE CASCADE, media_id TEXT NOT NULL REFERENCES media_items (id) ON DELETE CASCADE,
author_name TEXT NOT NULL, author_name TEXT NOT NULL,
author_sort TEXT, -- "Last, First" for sorting author_sort TEXT, -- "Last, First" for sorting
role TEXT NOT NULL DEFAULT 'author', -- author, translator, editor, illustrator role TEXT NOT NULL DEFAULT 'author', -- author, translator, editor, illustrator
@ -32,23 +34,29 @@ CREATE TABLE book_authors (
PRIMARY KEY (media_id, author_name, role) PRIMARY KEY (media_id, author_name, role)
) STRICT; ) STRICT;
CREATE INDEX idx_book_authors_name ON book_authors(author_name); CREATE INDEX idx_book_authors_name ON book_authors (author_name);
CREATE INDEX idx_book_authors_sort ON book_authors(author_sort);
CREATE INDEX idx_book_authors_sort ON book_authors (author_sort);
-- Multiple identifiers (ISBN variants, ASIN, DOI, etc.) -- Multiple identifiers (ISBN variants, ASIN, DOI, etc.)
CREATE TABLE book_identifiers ( CREATE TABLE book_identifiers (
media_id TEXT NOT NULL REFERENCES media_items(id) ON DELETE CASCADE, media_id TEXT NOT NULL REFERENCES media_items (id) ON DELETE CASCADE,
identifier_type TEXT NOT NULL, -- isbn, isbn13, asin, doi, lccn, oclc identifier_type TEXT NOT NULL, -- isbn, isbn13, asin, doi, lccn, oclc
identifier_value TEXT NOT NULL, identifier_value TEXT NOT NULL,
PRIMARY KEY (media_id, identifier_type, identifier_value) PRIMARY KEY (media_id, identifier_type, identifier_value)
) STRICT; ) STRICT;
CREATE INDEX idx_book_identifiers ON book_identifiers(identifier_type, identifier_value); CREATE INDEX idx_book_identifiers ON book_identifiers (identifier_type, identifier_value);
-- Trigger to update updated_at on book_metadata changes -- Trigger to update updated_at on book_metadata changes
CREATE TRIGGER update_book_metadata_timestamp CREATE TRIGGER update_book_metadata_timestamp
AFTER UPDATE ON book_metadata AFTER
FOR EACH ROW UPDATE ON book_metadata FOR EACH ROW
BEGIN BEGIN
UPDATE book_metadata SET updated_at = datetime('now') WHERE media_id = NEW.media_id; UPDATE book_metadata
SET
updated_at = datetime ('now')
WHERE
media_id = NEW.media_id;
END; END;

View file

@ -1,15 +1,40 @@
-- V13: Enhanced photo metadata support -- V13: Enhanced photo metadata support
-- Add photo-specific fields to media_items table -- Add photo-specific fields to media_items table
ALTER TABLE media_items
ADD COLUMN date_taken TIMESTAMP;
ALTER TABLE media_items ADD COLUMN date_taken TIMESTAMP; ALTER TABLE media_items
ALTER TABLE media_items ADD COLUMN latitude REAL; ADD COLUMN latitude REAL;
ALTER TABLE media_items ADD COLUMN longitude REAL;
ALTER TABLE media_items ADD COLUMN camera_make TEXT; ALTER TABLE media_items
ALTER TABLE media_items ADD COLUMN camera_model TEXT; ADD COLUMN longitude REAL;
ALTER TABLE media_items ADD COLUMN rating INTEGER CHECK (rating >= 0 AND rating <= 5);
ALTER TABLE media_items
ADD COLUMN camera_make TEXT;
ALTER TABLE media_items
ADD COLUMN camera_model TEXT;
ALTER TABLE media_items
ADD COLUMN rating INTEGER CHECK (
rating >= 0
AND rating <= 5
);
-- Indexes for photo queries -- Indexes for photo queries
CREATE INDEX idx_media_date_taken ON media_items(date_taken) WHERE date_taken IS NOT NULL; CREATE INDEX idx_media_date_taken ON media_items (date_taken)
CREATE INDEX idx_media_location ON media_items(latitude, longitude) WHERE latitude IS NOT NULL AND longitude IS NOT NULL; WHERE
CREATE INDEX idx_media_camera ON media_items(camera_make) WHERE camera_make IS NOT NULL; date_taken IS NOT NULL;
CREATE INDEX idx_media_rating ON media_items(rating) WHERE rating IS NOT NULL;
CREATE INDEX idx_media_location ON media_items (latitude, longitude)
WHERE
latitude IS NOT NULL
AND longitude IS NOT NULL;
CREATE INDEX idx_media_camera ON media_items (camera_make)
WHERE
camera_make IS NOT NULL;
CREATE INDEX idx_media_rating ON media_items (rating)
WHERE
rating IS NOT NULL;

View file

@ -1,7 +1,9 @@
-- V14: Perceptual hash for duplicate detection -- V14: Perceptual hash for duplicate detection
-- Add perceptual hash column for image similarity detection -- Add perceptual hash column for image similarity detection
ALTER TABLE media_items
ALTER TABLE media_items ADD COLUMN perceptual_hash TEXT; ADD COLUMN perceptual_hash TEXT;
-- Index for perceptual hash lookups -- Index for perceptual hash lookups
CREATE INDEX idx_media_phash ON media_items(perceptual_hash) WHERE perceptual_hash IS NOT NULL; CREATE INDEX idx_media_phash ON media_items (perceptual_hash)
WHERE
perceptual_hash IS NOT NULL;

View file

@ -1,17 +1,20 @@
-- V15: Managed File Storage -- V15: Managed File Storage
-- Adds server-side content-addressable storage for uploaded files -- Adds server-side content-addressable storage for uploaded files
-- Add storage mode to media_items (external = file on disk, managed = in content-addressable storage) -- Add storage mode to media_items (external = file on disk, managed = in content-addressable storage)
ALTER TABLE media_items ADD COLUMN storage_mode TEXT NOT NULL DEFAULT 'external'; ALTER TABLE media_items
ADD COLUMN storage_mode TEXT NOT NULL DEFAULT 'external';
-- Original filename for managed uploads (preserved separately from file_name which may be normalized) -- Original filename for managed uploads (preserved separately from file_name which may be normalized)
ALTER TABLE media_items ADD COLUMN original_filename TEXT; ALTER TABLE media_items
ADD COLUMN original_filename TEXT;
-- When the file was uploaded to managed storage -- When the file was uploaded to managed storage
ALTER TABLE media_items ADD COLUMN uploaded_at TEXT; ALTER TABLE media_items
ADD COLUMN uploaded_at TEXT;
-- Storage key for looking up the blob (usually same as content_hash for deduplication) -- Storage key for looking up the blob (usually same as content_hash for deduplication)
ALTER TABLE media_items ADD COLUMN storage_key TEXT; ALTER TABLE media_items
ADD COLUMN storage_key TEXT;
-- Managed blobs table - tracks deduplicated file storage -- Managed blobs table - tracks deduplicated file storage
CREATE TABLE managed_blobs ( CREATE TABLE managed_blobs (
@ -24,7 +27,7 @@ CREATE TABLE managed_blobs (
); );
-- Index for finding managed media items -- Index for finding managed media items
CREATE INDEX idx_media_storage_mode ON media_items(storage_mode); CREATE INDEX idx_media_storage_mode ON media_items (storage_mode);
-- Index for finding orphaned blobs (reference_count = 0) -- Index for finding orphaned blobs (reference_count = 0)
CREATE INDEX idx_blobs_reference_count ON managed_blobs(reference_count); CREATE INDEX idx_blobs_reference_count ON managed_blobs (reference_count);

View file

@ -1,6 +1,5 @@
-- V16: Cross-Device Sync System -- V16: Cross-Device Sync System
-- Adds device registration, change tracking, and chunked upload support -- Adds device registration, change tracking, and chunked upload support
-- Sync devices table -- Sync devices table
CREATE TABLE sync_devices ( CREATE TABLE sync_devices (
id TEXT PRIMARY KEY NOT NULL, id TEXT PRIMARY KEY NOT NULL,
@ -16,11 +15,12 @@ CREATE TABLE sync_devices (
enabled INTEGER NOT NULL DEFAULT 1, enabled INTEGER NOT NULL DEFAULT 1,
created_at TEXT NOT NULL, created_at TEXT NOT NULL,
updated_at TEXT NOT NULL, updated_at TEXT NOT NULL,
FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE FOREIGN KEY (user_id) REFERENCES users (id) ON DELETE CASCADE
); );
CREATE INDEX idx_sync_devices_user ON sync_devices(user_id); CREATE INDEX idx_sync_devices_user ON sync_devices (user_id);
CREATE INDEX idx_sync_devices_token ON sync_devices(device_token_hash);
CREATE INDEX idx_sync_devices_token ON sync_devices (device_token_hash);
-- Sync log table - tracks all changes for sync -- Sync log table - tracks all changes for sync
CREATE TABLE sync_log ( CREATE TABLE sync_log (
@ -34,20 +34,26 @@ CREATE TABLE sync_log (
metadata_json TEXT, metadata_json TEXT,
changed_by_device TEXT, changed_by_device TEXT,
timestamp TEXT NOT NULL, timestamp TEXT NOT NULL,
FOREIGN KEY (media_id) REFERENCES media_items(id) ON DELETE SET NULL, FOREIGN KEY (media_id) REFERENCES media_items (id) ON DELETE SET NULL,
FOREIGN KEY (changed_by_device) REFERENCES sync_devices(id) ON DELETE SET NULL FOREIGN KEY (changed_by_device) REFERENCES sync_devices (id) ON DELETE SET NULL
); );
CREATE INDEX idx_sync_log_sequence ON sync_log(sequence); CREATE INDEX idx_sync_log_sequence ON sync_log (sequence);
CREATE INDEX idx_sync_log_path ON sync_log(path);
CREATE INDEX idx_sync_log_timestamp ON sync_log(timestamp); CREATE INDEX idx_sync_log_path ON sync_log (path);
CREATE INDEX idx_sync_log_timestamp ON sync_log (timestamp);
-- Sequence counter for sync log -- Sequence counter for sync log
CREATE TABLE sync_sequence ( CREATE TABLE sync_sequence (
id INTEGER PRIMARY KEY CHECK (id = 1), id INTEGER PRIMARY KEY CHECK (id = 1),
current_value INTEGER NOT NULL DEFAULT 0 current_value INTEGER NOT NULL DEFAULT 0
); );
INSERT INTO sync_sequence (id, current_value) VALUES (1, 0);
INSERT INTO
sync_sequence (id, current_value)
VALUES
(1, 0);
-- Device sync state - tracks sync status per device per file -- Device sync state - tracks sync status per device per file
CREATE TABLE device_sync_state ( CREATE TABLE device_sync_state (
@ -61,10 +67,10 @@ CREATE TABLE device_sync_state (
last_synced_at TEXT, last_synced_at TEXT,
conflict_info_json TEXT, conflict_info_json TEXT,
PRIMARY KEY (device_id, path), PRIMARY KEY (device_id, path),
FOREIGN KEY (device_id) REFERENCES sync_devices(id) ON DELETE CASCADE FOREIGN KEY (device_id) REFERENCES sync_devices (id) ON DELETE CASCADE
); );
CREATE INDEX idx_device_sync_status ON device_sync_state(device_id, sync_status); CREATE INDEX idx_device_sync_status ON device_sync_state (device_id, sync_status);
-- Upload sessions for chunked uploads -- Upload sessions for chunked uploads
CREATE TABLE upload_sessions ( CREATE TABLE upload_sessions (
@ -79,23 +85,26 @@ CREATE TABLE upload_sessions (
created_at TEXT NOT NULL, created_at TEXT NOT NULL,
expires_at TEXT NOT NULL, expires_at TEXT NOT NULL,
last_activity TEXT NOT NULL, last_activity TEXT NOT NULL,
FOREIGN KEY (device_id) REFERENCES sync_devices(id) ON DELETE CASCADE FOREIGN KEY (device_id) REFERENCES sync_devices (id) ON DELETE CASCADE
); );
CREATE INDEX idx_upload_sessions_device ON upload_sessions(device_id); CREATE INDEX idx_upload_sessions_device ON upload_sessions (device_id);
CREATE INDEX idx_upload_sessions_status ON upload_sessions(status);
CREATE INDEX idx_upload_sessions_expires ON upload_sessions(expires_at); CREATE INDEX idx_upload_sessions_status ON upload_sessions (status);
CREATE INDEX idx_upload_sessions_expires ON upload_sessions (expires_at);
-- Upload chunks - tracks received chunks -- Upload chunks - tracks received chunks
CREATE TABLE upload_chunks ( CREATE TABLE upload_chunks (
upload_id TEXT NOT NULL, upload_id TEXT NOT NULL,
chunk_index INTEGER NOT NULL, chunk_index INTEGER NOT NULL,
offset INTEGER NOT NULL, offset
INTEGER NOT NULL,
size INTEGER NOT NULL, size INTEGER NOT NULL,
hash TEXT NOT NULL, hash TEXT NOT NULL,
received_at TEXT NOT NULL, received_at TEXT NOT NULL,
PRIMARY KEY (upload_id, chunk_index), PRIMARY KEY (upload_id, chunk_index),
FOREIGN KEY (upload_id) REFERENCES upload_sessions(id) ON DELETE CASCADE FOREIGN KEY (upload_id) REFERENCES upload_sessions (id) ON DELETE CASCADE
); );
-- Sync conflicts -- Sync conflicts
@ -110,8 +119,11 @@ CREATE TABLE sync_conflicts (
detected_at TEXT NOT NULL, detected_at TEXT NOT NULL,
resolved_at TEXT, resolved_at TEXT,
resolution TEXT, resolution TEXT,
FOREIGN KEY (device_id) REFERENCES sync_devices(id) ON DELETE CASCADE FOREIGN KEY (device_id) REFERENCES sync_devices (id) ON DELETE CASCADE
); );
CREATE INDEX idx_sync_conflicts_device ON sync_conflicts(device_id); CREATE INDEX idx_sync_conflicts_device ON sync_conflicts (device_id);
CREATE INDEX idx_sync_conflicts_unresolved ON sync_conflicts(device_id, resolved_at) WHERE resolved_at IS NULL;
CREATE INDEX idx_sync_conflicts_unresolved ON sync_conflicts (device_id, resolved_at)
WHERE
resolved_at IS NULL;

View file

@ -1,13 +1,16 @@
-- V17: Enhanced Sharing System -- V17: Enhanced Sharing System
-- Replaces simple share_links with comprehensive sharing capabilities -- Replaces simple share_links with comprehensive sharing capabilities
-- Enhanced shares table -- Enhanced shares table
CREATE TABLE shares ( CREATE TABLE shares (
id TEXT PRIMARY KEY NOT NULL, id TEXT PRIMARY KEY NOT NULL,
target_type TEXT NOT NULL CHECK (target_type IN ('media', 'collection', 'tag', 'saved_search')), target_type TEXT NOT NULL CHECK (
target_type IN ('media', 'collection', 'tag', 'saved_search')
),
target_id TEXT NOT NULL, target_id TEXT NOT NULL,
owner_id TEXT NOT NULL, owner_id TEXT NOT NULL,
recipient_type TEXT NOT NULL CHECK (recipient_type IN ('public_link', 'user', 'group', 'federated')), recipient_type TEXT NOT NULL CHECK (
recipient_type IN ('public_link', 'user', 'group', 'federated')
),
recipient_user_id TEXT, recipient_user_id TEXT,
recipient_group_id TEXT, recipient_group_id TEXT,
recipient_federated_handle TEXT, recipient_federated_handle TEXT,
@ -28,17 +31,27 @@ CREATE TABLE shares (
parent_share_id TEXT, parent_share_id TEXT,
created_at TEXT NOT NULL, created_at TEXT NOT NULL,
updated_at TEXT NOT NULL, updated_at TEXT NOT NULL,
FOREIGN KEY (owner_id) REFERENCES users(id) ON DELETE CASCADE, FOREIGN KEY (owner_id) REFERENCES users (id) ON DELETE CASCADE,
FOREIGN KEY (recipient_user_id) REFERENCES users(id) ON DELETE CASCADE, FOREIGN KEY (recipient_user_id) REFERENCES users (id) ON DELETE CASCADE,
FOREIGN KEY (parent_share_id) REFERENCES shares(id) ON DELETE CASCADE, FOREIGN KEY (parent_share_id) REFERENCES shares (id) ON DELETE CASCADE,
UNIQUE(owner_id, target_type, target_id, recipient_type, recipient_user_id) UNIQUE (
owner_id,
target_type,
target_id,
recipient_type,
recipient_user_id
)
); );
CREATE INDEX idx_shares_owner ON shares(owner_id); CREATE INDEX idx_shares_owner ON shares (owner_id);
CREATE INDEX idx_shares_recipient_user ON shares(recipient_user_id);
CREATE INDEX idx_shares_target ON shares(target_type, target_id); CREATE INDEX idx_shares_recipient_user ON shares (recipient_user_id);
CREATE INDEX idx_shares_token ON shares(public_token);
CREATE INDEX idx_shares_expires ON shares(expires_at); CREATE INDEX idx_shares_target ON shares (target_type, target_id);
CREATE INDEX idx_shares_token ON shares (public_token);
CREATE INDEX idx_shares_expires ON shares (expires_at);
-- Share activity log -- Share activity log
CREATE TABLE share_activity ( CREATE TABLE share_activity (
@ -49,12 +62,13 @@ CREATE TABLE share_activity (
action TEXT NOT NULL, action TEXT NOT NULL,
details TEXT, details TEXT,
timestamp TEXT NOT NULL, timestamp TEXT NOT NULL,
FOREIGN KEY (share_id) REFERENCES shares(id) ON DELETE CASCADE, FOREIGN KEY (share_id) REFERENCES shares (id) ON DELETE CASCADE,
FOREIGN KEY (actor_id) REFERENCES users(id) ON DELETE SET NULL FOREIGN KEY (actor_id) REFERENCES users (id) ON DELETE SET NULL
); );
CREATE INDEX idx_share_activity_share ON share_activity(share_id); CREATE INDEX idx_share_activity_share ON share_activity (share_id);
CREATE INDEX idx_share_activity_timestamp ON share_activity(timestamp);
CREATE INDEX idx_share_activity_timestamp ON share_activity (timestamp);
-- Share notifications -- Share notifications
CREATE TABLE share_notifications ( CREATE TABLE share_notifications (
@ -64,22 +78,56 @@ CREATE TABLE share_notifications (
notification_type TEXT NOT NULL, notification_type TEXT NOT NULL,
is_read INTEGER NOT NULL DEFAULT 0, is_read INTEGER NOT NULL DEFAULT 0,
created_at TEXT NOT NULL, created_at TEXT NOT NULL,
FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE, FOREIGN KEY (user_id) REFERENCES users (id) ON DELETE CASCADE,
FOREIGN KEY (share_id) REFERENCES shares(id) ON DELETE CASCADE FOREIGN KEY (share_id) REFERENCES shares (id) ON DELETE CASCADE
); );
CREATE INDEX idx_share_notifications_user ON share_notifications(user_id); CREATE INDEX idx_share_notifications_user ON share_notifications (user_id);
CREATE INDEX idx_share_notifications_unread ON share_notifications(user_id, is_read) WHERE is_read = 0;
CREATE INDEX idx_share_notifications_unread ON share_notifications (user_id, is_read)
WHERE
is_read = 0;
-- Migrate existing share_links to new shares table (if share_links exists) -- Migrate existing share_links to new shares table (if share_links exists)
INSERT OR IGNORE INTO shares ( INSERT
id, target_type, target_id, owner_id, recipient_type, OR IGNORE INTO shares (
public_token, public_password_hash, perm_view, perm_download, id,
access_count, expires_at, created_at, updated_at target_type,
target_id,
owner_id,
recipient_type,
public_token,
public_password_hash,
perm_view,
perm_download,
access_count,
expires_at,
created_at,
updated_at
) )
SELECT SELECT
id, 'media', media_id, created_by, 'public_link', id,
token, password_hash, 1, 1, 'media',
view_count, expires_at, created_at, created_at media_id,
FROM share_links created_by,
WHERE EXISTS (SELECT 1 FROM sqlite_master WHERE type='table' AND name='share_links'); 'public_link',
token,
password_hash,
1,
1,
view_count,
expires_at,
created_at,
created_at
FROM
share_links
WHERE
EXISTS (
SELECT
1
FROM
sqlite_master
WHERE
type = 'table'
AND name = 'share_links'
);

View file

@ -1,11 +1,13 @@
-- V18: File Management (Rename, Move, Trash) -- V18: File Management (Rename, Move, Trash)
-- Adds soft delete support for trash/recycle bin functionality -- Adds soft delete support for trash/recycle bin functionality
-- Add deleted_at column for soft delete (trash) -- Add deleted_at column for soft delete (trash)
ALTER TABLE media_items ADD COLUMN deleted_at TEXT; ALTER TABLE media_items
ADD COLUMN deleted_at TEXT;
-- Index for efficient trash queries -- Index for efficient trash queries
CREATE INDEX idx_media_deleted_at ON media_items(deleted_at); CREATE INDEX idx_media_deleted_at ON media_items (deleted_at);
-- Index for listing non-deleted items (most common query pattern) -- Index for listing non-deleted items (most common query pattern)
CREATE INDEX idx_media_not_deleted ON media_items(id) WHERE deleted_at IS NULL; CREATE INDEX idx_media_not_deleted ON media_items (id)
WHERE
deleted_at IS NULL;

View file

@ -1,6 +1,5 @@
-- V19: Markdown Links (Obsidian-style bidirectional links) -- V19: Markdown Links (Obsidian-style bidirectional links)
-- Adds support for wikilinks, markdown links, embeds, and backlink tracking -- Adds support for wikilinks, markdown links, embeds, and backlink tracking
-- Table for storing extracted markdown links -- Table for storing extracted markdown links
CREATE TABLE IF NOT EXISTS markdown_links ( CREATE TABLE IF NOT EXISTS markdown_links (
id TEXT PRIMARY KEY NOT NULL, id TEXT PRIMARY KEY NOT NULL,
@ -12,24 +11,25 @@ CREATE TABLE IF NOT EXISTS markdown_links (
line_number INTEGER, -- line number in source file line_number INTEGER, -- line number in source file
context TEXT, -- surrounding text for preview context TEXT, -- surrounding text for preview
created_at TEXT NOT NULL, created_at TEXT NOT NULL,
FOREIGN KEY (source_media_id) REFERENCES media_items(id) ON DELETE CASCADE, FOREIGN KEY (source_media_id) REFERENCES media_items (id) ON DELETE CASCADE,
FOREIGN KEY (target_media_id) REFERENCES media_items(id) ON DELETE SET NULL FOREIGN KEY (target_media_id) REFERENCES media_items (id) ON DELETE SET NULL
); );
-- Index for efficient outgoing link queries (what does this note link to?) -- Index for efficient outgoing link queries (what does this note link to?)
CREATE INDEX idx_links_source ON markdown_links(source_media_id); CREATE INDEX idx_links_source ON markdown_links (source_media_id);
-- Index for efficient backlink queries (what links to this note?) -- Index for efficient backlink queries (what links to this note?)
CREATE INDEX idx_links_target ON markdown_links(target_media_id); CREATE INDEX idx_links_target ON markdown_links (target_media_id);
-- Index for path-based resolution (finding unresolved links) -- Index for path-based resolution (finding unresolved links)
CREATE INDEX idx_links_target_path ON markdown_links(target_path); CREATE INDEX idx_links_target_path ON markdown_links (target_path);
-- Index for link type filtering -- Index for link type filtering
CREATE INDEX idx_links_type ON markdown_links(link_type); CREATE INDEX idx_links_type ON markdown_links (link_type);
-- Track when links were last extracted from a media item -- Track when links were last extracted from a media item
ALTER TABLE media_items ADD COLUMN links_extracted_at TEXT; ALTER TABLE media_items
ADD COLUMN links_extracted_at TEXT;
-- Index for finding media items that need link extraction -- Index for finding media items that need link extraction
CREATE INDEX idx_media_links_extracted ON media_items(links_extracted_at); CREATE INDEX idx_media_links_extracted ON media_items (links_extracted_at);

View file

@ -1,6 +1,4 @@
CREATE TABLE IF NOT EXISTS root_dirs ( CREATE TABLE IF NOT EXISTS root_dirs (path TEXT PRIMARY KEY NOT NULL);
path TEXT PRIMARY KEY NOT NULL
);
CREATE TABLE IF NOT EXISTS media_items ( CREATE TABLE IF NOT EXISTS media_items (
id TEXT PRIMARY KEY NOT NULL, id TEXT PRIMARY KEY NOT NULL,
@ -25,17 +23,17 @@ CREATE TABLE IF NOT EXISTS tags (
name TEXT NOT NULL, name TEXT NOT NULL,
parent_id TEXT, parent_id TEXT,
created_at TEXT NOT NULL, created_at TEXT NOT NULL,
FOREIGN KEY (parent_id) REFERENCES tags(id) ON DELETE SET NULL FOREIGN KEY (parent_id) REFERENCES tags (id) ON DELETE SET NULL
); );
CREATE UNIQUE INDEX IF NOT EXISTS idx_tags_name_parent ON tags(name, parent_id); CREATE UNIQUE INDEX IF NOT EXISTS idx_tags_name_parent ON tags (name, parent_id);
CREATE TABLE IF NOT EXISTS media_tags ( CREATE TABLE IF NOT EXISTS media_tags (
media_id TEXT NOT NULL, media_id TEXT NOT NULL,
tag_id TEXT NOT NULL, tag_id TEXT NOT NULL,
PRIMARY KEY (media_id, tag_id), PRIMARY KEY (media_id, tag_id),
FOREIGN KEY (media_id) REFERENCES media_items(id) ON DELETE CASCADE, FOREIGN KEY (media_id) REFERENCES media_items (id) ON DELETE CASCADE,
FOREIGN KEY (tag_id) REFERENCES tags(id) ON DELETE CASCADE FOREIGN KEY (tag_id) REFERENCES tags (id) ON DELETE CASCADE
); );
CREATE TABLE IF NOT EXISTS collections ( CREATE TABLE IF NOT EXISTS collections (
@ -54,8 +52,8 @@ CREATE TABLE IF NOT EXISTS collection_members (
position INTEGER NOT NULL DEFAULT 0, position INTEGER NOT NULL DEFAULT 0,
added_at TEXT NOT NULL, added_at TEXT NOT NULL,
PRIMARY KEY (collection_id, media_id), PRIMARY KEY (collection_id, media_id),
FOREIGN KEY (collection_id) REFERENCES collections(id) ON DELETE CASCADE, FOREIGN KEY (collection_id) REFERENCES collections (id) ON DELETE CASCADE,
FOREIGN KEY (media_id) REFERENCES media_items(id) ON DELETE CASCADE FOREIGN KEY (media_id) REFERENCES media_items (id) ON DELETE CASCADE
); );
CREATE TABLE IF NOT EXISTS audit_log ( CREATE TABLE IF NOT EXISTS audit_log (
@ -64,7 +62,7 @@ CREATE TABLE IF NOT EXISTS audit_log (
action TEXT NOT NULL, action TEXT NOT NULL,
details TEXT, details TEXT,
timestamp TEXT NOT NULL, timestamp TEXT NOT NULL,
FOREIGN KEY (media_id) REFERENCES media_items(id) ON DELETE SET NULL FOREIGN KEY (media_id) REFERENCES media_items (id) ON DELETE SET NULL
); );
CREATE TABLE IF NOT EXISTS custom_fields ( CREATE TABLE IF NOT EXISTS custom_fields (
@ -73,5 +71,5 @@ CREATE TABLE IF NOT EXISTS custom_fields (
field_type TEXT NOT NULL, field_type TEXT NOT NULL,
field_value TEXT NOT NULL, field_value TEXT NOT NULL,
PRIMARY KEY (media_id, field_name), PRIMARY KEY (media_id, field_name),
FOREIGN KEY (media_id) REFERENCES media_items(id) ON DELETE CASCADE FOREIGN KEY (media_id) REFERENCES media_items (id) ON DELETE CASCADE
); );

View file

@ -1,27 +1,114 @@
CREATE VIRTUAL TABLE IF NOT EXISTS media_fts USING fts5( CREATE VIRTUAL TABLE IF NOT EXISTS media_fts USING fts5 (
title, title,
artist, artist,
album, album,
genre, genre,
description, description,
file_name, file_name,
content='media_items', content = 'media_items',
content_rowid='rowid' content_rowid = 'rowid'
); );
CREATE TRIGGER IF NOT EXISTS media_fts_insert AFTER INSERT ON media_items BEGIN CREATE TRIGGER IF NOT EXISTS media_fts_insert
INSERT INTO media_fts(rowid, title, artist, album, genre, description, file_name) AFTER INSERT ON media_items
VALUES (new.rowid, new.title, new.artist, new.album, new.genre, new.description, new.file_name); BEGIN
INSERT INTO
media_fts (
rowid,
title,
artist,
album,
genre,
description,
file_name
)
VALUES
(
new.rowid,
new.title,
new.artist,
new.album,
new.genre,
new.description,
new.file_name
);
END; END;
CREATE TRIGGER IF NOT EXISTS media_fts_update AFTER UPDATE ON media_items BEGIN CREATE TRIGGER IF NOT EXISTS media_fts_update
INSERT INTO media_fts(media_fts, rowid, title, artist, album, genre, description, file_name) AFTER
VALUES ('delete', old.rowid, old.title, old.artist, old.album, old.genre, old.description, old.file_name); UPDATE ON media_items
INSERT INTO media_fts(rowid, title, artist, album, genre, description, file_name) BEGIN
VALUES (new.rowid, new.title, new.artist, new.album, new.genre, new.description, new.file_name); INSERT INTO
media_fts (
media_fts,
rowid,
title,
artist,
album,
genre,
description,
file_name
)
VALUES
(
'delete',
old.rowid,
old.title,
old.artist,
old.album,
old.genre,
old.description,
old.file_name
);
INSERT INTO
media_fts (
rowid,
title,
artist,
album,
genre,
description,
file_name
)
VALUES
(
new.rowid,
new.title,
new.artist,
new.album,
new.genre,
new.description,
new.file_name
);
END; END;
CREATE TRIGGER IF NOT EXISTS media_fts_delete AFTER DELETE ON media_items BEGIN CREATE TRIGGER IF NOT EXISTS media_fts_delete
INSERT INTO media_fts(media_fts, rowid, title, artist, album, genre, description, file_name) AFTER DELETE ON media_items
VALUES ('delete', old.rowid, old.title, old.artist, old.album, old.genre, old.description, old.file_name); BEGIN
INSERT INTO
media_fts (
media_fts,
rowid,
title,
artist,
album,
genre,
description,
file_name
)
VALUES
(
'delete',
old.rowid,
old.title,
old.artist,
old.album,
old.genre,
old.description,
old.file_name
);
END; END;

View file

@ -1,6 +1,11 @@
CREATE INDEX IF NOT EXISTS idx_audit_media_id ON audit_log(media_id); CREATE INDEX IF NOT EXISTS idx_audit_media_id ON audit_log (media_id);
CREATE INDEX IF NOT EXISTS idx_audit_timestamp ON audit_log(timestamp);
CREATE INDEX IF NOT EXISTS idx_audit_action ON audit_log(action); CREATE INDEX IF NOT EXISTS idx_audit_timestamp ON audit_log (timestamp);
CREATE INDEX IF NOT EXISTS idx_media_content_hash ON media_items(content_hash);
CREATE INDEX IF NOT EXISTS idx_media_media_type ON media_items(media_type); CREATE INDEX IF NOT EXISTS idx_audit_action ON audit_log (action);
CREATE INDEX IF NOT EXISTS idx_media_created_at ON media_items(created_at);
CREATE INDEX IF NOT EXISTS idx_media_content_hash ON media_items (content_hash);
CREATE INDEX IF NOT EXISTS idx_media_media_type ON media_items (media_type);
CREATE INDEX IF NOT EXISTS idx_media_created_at ON media_items (created_at);

View file

@ -1 +1,2 @@
ALTER TABLE media_items ADD COLUMN thumbnail_path TEXT; ALTER TABLE media_items
ADD COLUMN thumbnail_path TEXT;

View file

@ -1,6 +1,9 @@
-- Integrity tracking columns -- Integrity tracking columns
ALTER TABLE media_items ADD COLUMN last_verified_at TEXT; ALTER TABLE media_items
ALTER TABLE media_items ADD COLUMN integrity_status TEXT DEFAULT 'unverified'; ADD COLUMN last_verified_at TEXT;
ALTER TABLE media_items
ADD COLUMN integrity_status TEXT DEFAULT 'unverified';
-- Saved searches -- Saved searches
CREATE TABLE IF NOT EXISTS saved_searches ( CREATE TABLE IF NOT EXISTS saved_searches (

View file

@ -11,5 +11,6 @@ CREATE TABLE plugin_registry (
); );
-- Index for quick lookups -- Index for quick lookups
CREATE INDEX idx_plugin_registry_enabled ON plugin_registry(enabled); CREATE INDEX idx_plugin_registry_enabled ON plugin_registry (enabled);
CREATE INDEX idx_plugin_registry_name ON plugin_registry(name);
CREATE INDEX idx_plugin_registry_name ON plugin_registry (name);

View file

@ -16,7 +16,7 @@ CREATE TABLE user_profiles (
preferences_json TEXT NOT NULL DEFAULT '{}', preferences_json TEXT NOT NULL DEFAULT '{}',
created_at TEXT NOT NULL, created_at TEXT NOT NULL,
updated_at TEXT NOT NULL, updated_at TEXT NOT NULL,
FOREIGN KEY (user_id) REFERENCES users(id) FOREIGN KEY (user_id) REFERENCES users (id)
); );
-- User library access table -- User library access table
@ -26,10 +26,12 @@ CREATE TABLE user_libraries (
permission TEXT NOT NULL, permission TEXT NOT NULL,
granted_at TEXT NOT NULL, granted_at TEXT NOT NULL,
PRIMARY KEY (user_id, root_path), PRIMARY KEY (user_id, root_path),
FOREIGN KEY (user_id) REFERENCES users(id) FOREIGN KEY (user_id) REFERENCES users (id)
); );
-- Indexes for efficient lookups -- Indexes for efficient lookups
CREATE INDEX idx_users_username ON users(username); CREATE INDEX idx_users_username ON users (username);
CREATE INDEX idx_user_libraries_user_id ON user_libraries(user_id);
CREATE INDEX idx_user_libraries_root_path ON user_libraries(root_path); CREATE INDEX idx_user_libraries_user_id ON user_libraries (user_id);
CREATE INDEX idx_user_libraries_root_path ON user_libraries (root_path);

View file

@ -3,11 +3,14 @@ CREATE TABLE IF NOT EXISTS ratings (
id TEXT PRIMARY KEY, id TEXT PRIMARY KEY,
user_id TEXT NOT NULL, user_id TEXT NOT NULL,
media_id TEXT NOT NULL, media_id TEXT NOT NULL,
stars INTEGER NOT NULL CHECK (stars >= 1 AND stars <= 5), stars INTEGER NOT NULL CHECK (
stars >= 1
AND stars <= 5
),
review_text TEXT, review_text TEXT,
created_at TEXT NOT NULL DEFAULT (datetime('now')), created_at TEXT NOT NULL DEFAULT (datetime ('now')),
UNIQUE(user_id, media_id), UNIQUE (user_id, media_id),
FOREIGN KEY (media_id) REFERENCES media_items(id) ON DELETE CASCADE FOREIGN KEY (media_id) REFERENCES media_items (id) ON DELETE CASCADE
); );
-- Comments -- Comments
@ -17,18 +20,18 @@ CREATE TABLE IF NOT EXISTS comments (
media_id TEXT NOT NULL, media_id TEXT NOT NULL,
parent_comment_id TEXT, parent_comment_id TEXT,
text TEXT NOT NULL, text TEXT NOT NULL,
created_at TEXT NOT NULL DEFAULT (datetime('now')), created_at TEXT NOT NULL DEFAULT (datetime ('now')),
FOREIGN KEY (media_id) REFERENCES media_items(id) ON DELETE CASCADE, FOREIGN KEY (media_id) REFERENCES media_items (id) ON DELETE CASCADE,
FOREIGN KEY (parent_comment_id) REFERENCES comments(id) ON DELETE CASCADE FOREIGN KEY (parent_comment_id) REFERENCES comments (id) ON DELETE CASCADE
); );
-- Favorites -- Favorites
CREATE TABLE IF NOT EXISTS favorites ( CREATE TABLE IF NOT EXISTS favorites (
user_id TEXT NOT NULL, user_id TEXT NOT NULL,
media_id TEXT NOT NULL, media_id TEXT NOT NULL,
created_at TEXT NOT NULL DEFAULT (datetime('now')), created_at TEXT NOT NULL DEFAULT (datetime ('now')),
PRIMARY KEY (user_id, media_id), PRIMARY KEY (user_id, media_id),
FOREIGN KEY (media_id) REFERENCES media_items(id) ON DELETE CASCADE FOREIGN KEY (media_id) REFERENCES media_items (id) ON DELETE CASCADE
); );
-- Share links -- Share links
@ -40,8 +43,8 @@ CREATE TABLE IF NOT EXISTS share_links (
password_hash TEXT, password_hash TEXT,
expires_at TEXT, expires_at TEXT,
view_count INTEGER NOT NULL DEFAULT 0, view_count INTEGER NOT NULL DEFAULT 0,
created_at TEXT NOT NULL DEFAULT (datetime('now')), created_at TEXT NOT NULL DEFAULT (datetime ('now')),
FOREIGN KEY (media_id) REFERENCES media_items(id) ON DELETE CASCADE FOREIGN KEY (media_id) REFERENCES media_items (id) ON DELETE CASCADE
); );
-- Playlists -- Playlists
@ -53,8 +56,8 @@ CREATE TABLE IF NOT EXISTS playlists (
is_public INTEGER NOT NULL DEFAULT 0, is_public INTEGER NOT NULL DEFAULT 0,
is_smart INTEGER NOT NULL DEFAULT 0, is_smart INTEGER NOT NULL DEFAULT 0,
filter_query TEXT, filter_query TEXT,
created_at TEXT NOT NULL DEFAULT (datetime('now')), created_at TEXT NOT NULL DEFAULT (datetime ('now')),
updated_at TEXT NOT NULL DEFAULT (datetime('now')) updated_at TEXT NOT NULL DEFAULT (datetime ('now'))
); );
-- Playlist items -- Playlist items
@ -62,10 +65,10 @@ CREATE TABLE IF NOT EXISTS playlist_items (
playlist_id TEXT NOT NULL, playlist_id TEXT NOT NULL,
media_id TEXT NOT NULL, media_id TEXT NOT NULL,
position INTEGER NOT NULL DEFAULT 0, position INTEGER NOT NULL DEFAULT 0,
added_at TEXT NOT NULL DEFAULT (datetime('now')), added_at TEXT NOT NULL DEFAULT (datetime ('now')),
PRIMARY KEY (playlist_id, media_id), PRIMARY KEY (playlist_id, media_id),
FOREIGN KEY (playlist_id) REFERENCES playlists(id) ON DELETE CASCADE, FOREIGN KEY (playlist_id) REFERENCES playlists (id) ON DELETE CASCADE,
FOREIGN KEY (media_id) REFERENCES media_items(id) ON DELETE CASCADE FOREIGN KEY (media_id) REFERENCES media_items (id) ON DELETE CASCADE
); );
-- Usage events -- Usage events
@ -74,15 +77,17 @@ CREATE TABLE IF NOT EXISTS usage_events (
media_id TEXT, media_id TEXT,
user_id TEXT, user_id TEXT,
event_type TEXT NOT NULL, event_type TEXT NOT NULL,
timestamp TEXT NOT NULL DEFAULT (datetime('now')), timestamp TEXT NOT NULL DEFAULT (datetime ('now')),
duration_secs REAL, duration_secs REAL,
context_json TEXT, context_json TEXT,
FOREIGN KEY (media_id) REFERENCES media_items(id) ON DELETE SET NULL FOREIGN KEY (media_id) REFERENCES media_items (id) ON DELETE SET NULL
); );
CREATE INDEX IF NOT EXISTS idx_usage_events_media ON usage_events(media_id); CREATE INDEX IF NOT EXISTS idx_usage_events_media ON usage_events (media_id);
CREATE INDEX IF NOT EXISTS idx_usage_events_user ON usage_events(user_id);
CREATE INDEX IF NOT EXISTS idx_usage_events_timestamp ON usage_events(timestamp); CREATE INDEX IF NOT EXISTS idx_usage_events_user ON usage_events (user_id);
CREATE INDEX IF NOT EXISTS idx_usage_events_timestamp ON usage_events (timestamp);
-- Watch history / progress -- Watch history / progress
CREATE TABLE IF NOT EXISTS watch_history ( CREATE TABLE IF NOT EXISTS watch_history (
@ -90,9 +95,9 @@ CREATE TABLE IF NOT EXISTS watch_history (
user_id TEXT NOT NULL, user_id TEXT NOT NULL,
media_id TEXT NOT NULL, media_id TEXT NOT NULL,
progress_secs REAL NOT NULL DEFAULT 0, progress_secs REAL NOT NULL DEFAULT 0,
last_watched TEXT NOT NULL DEFAULT (datetime('now')), last_watched TEXT NOT NULL DEFAULT (datetime ('now')),
UNIQUE(user_id, media_id), UNIQUE (user_id, media_id),
FOREIGN KEY (media_id) REFERENCES media_items(id) ON DELETE CASCADE FOREIGN KEY (media_id) REFERENCES media_items (id) ON DELETE CASCADE
); );
-- Subtitles -- Subtitles
@ -105,11 +110,11 @@ CREATE TABLE IF NOT EXISTS subtitles (
is_embedded INTEGER NOT NULL DEFAULT 0, is_embedded INTEGER NOT NULL DEFAULT 0,
track_index INTEGER, track_index INTEGER,
offset_ms INTEGER NOT NULL DEFAULT 0, offset_ms INTEGER NOT NULL DEFAULT 0,
created_at TEXT NOT NULL DEFAULT (datetime('now')), created_at TEXT NOT NULL DEFAULT (datetime ('now')),
FOREIGN KEY (media_id) REFERENCES media_items(id) ON DELETE CASCADE FOREIGN KEY (media_id) REFERENCES media_items (id) ON DELETE CASCADE
); );
CREATE INDEX IF NOT EXISTS idx_subtitles_media ON subtitles(media_id); CREATE INDEX IF NOT EXISTS idx_subtitles_media ON subtitles (media_id);
-- External metadata (enrichment) -- External metadata (enrichment)
CREATE TABLE IF NOT EXISTS external_metadata ( CREATE TABLE IF NOT EXISTS external_metadata (
@ -119,11 +124,11 @@ CREATE TABLE IF NOT EXISTS external_metadata (
external_id TEXT, external_id TEXT,
metadata_json TEXT NOT NULL DEFAULT '{}', metadata_json TEXT NOT NULL DEFAULT '{}',
confidence REAL NOT NULL DEFAULT 0.0, confidence REAL NOT NULL DEFAULT 0.0,
last_updated TEXT NOT NULL DEFAULT (datetime('now')), last_updated TEXT NOT NULL DEFAULT (datetime ('now')),
FOREIGN KEY (media_id) REFERENCES media_items(id) ON DELETE CASCADE FOREIGN KEY (media_id) REFERENCES media_items (id) ON DELETE CASCADE
); );
CREATE INDEX IF NOT EXISTS idx_external_metadata_media ON external_metadata(media_id); CREATE INDEX IF NOT EXISTS idx_external_metadata_media ON external_metadata (media_id);
-- Transcode sessions -- Transcode sessions
CREATE TABLE IF NOT EXISTS transcode_sessions ( CREATE TABLE IF NOT EXISTS transcode_sessions (
@ -135,9 +140,9 @@ CREATE TABLE IF NOT EXISTS transcode_sessions (
status TEXT NOT NULL DEFAULT 'pending', status TEXT NOT NULL DEFAULT 'pending',
progress REAL NOT NULL DEFAULT 0.0, progress REAL NOT NULL DEFAULT 0.0,
error_message TEXT, error_message TEXT,
created_at TEXT NOT NULL DEFAULT (datetime('now')), created_at TEXT NOT NULL DEFAULT (datetime ('now')),
expires_at TEXT, expires_at TEXT,
FOREIGN KEY (media_id) REFERENCES media_items(id) ON DELETE CASCADE FOREIGN KEY (media_id) REFERENCES media_items (id) ON DELETE CASCADE
); );
CREATE INDEX IF NOT EXISTS idx_transcode_sessions_media ON transcode_sessions(media_id); CREATE INDEX IF NOT EXISTS idx_transcode_sessions_media ON transcode_sessions (media_id);

View file

@ -1,18 +1,25 @@
-- Drop redundant indexes (already covered by UNIQUE constraints) -- Drop redundant indexes (already covered by UNIQUE constraints)
DROP INDEX IF EXISTS idx_users_username; DROP INDEX IF EXISTS idx_users_username;
DROP INDEX IF EXISTS idx_user_libraries_user_id; DROP INDEX IF EXISTS idx_user_libraries_user_id;
-- Add missing indexes for comments table -- Add missing indexes for comments table
CREATE INDEX IF NOT EXISTS idx_comments_media ON comments(media_id); CREATE INDEX IF NOT EXISTS idx_comments_media ON comments (media_id);
CREATE INDEX IF NOT EXISTS idx_comments_parent ON comments(parent_comment_id);
CREATE INDEX IF NOT EXISTS idx_comments_parent ON comments (parent_comment_id);
-- Remove duplicates before adding unique index (keep the first row) -- Remove duplicates before adding unique index (keep the first row)
DELETE FROM external_metadata DELETE FROM external_metadata
WHERE rowid NOT IN ( WHERE
SELECT MIN(rowid) rowid NOT IN (
FROM external_metadata SELECT
GROUP BY media_id, source MIN(rowid)
); FROM
external_metadata
GROUP BY
media_id,
source
);
-- Add unique index for external_metadata to prevent duplicates -- Add unique index for external_metadata to prevent duplicates
CREATE UNIQUE INDEX IF NOT EXISTS uq_external_metadata_source ON external_metadata(media_id, source); CREATE UNIQUE INDEX IF NOT EXISTS uq_external_metadata_source ON external_metadata (media_id, source);

View file

@ -26,21 +26,22 @@ in
name = "pinakes-dev"; name = "pinakes-dev";
packages = packages =
[ [
# Build tools
# We use the rust-overlay to get the stable Rust toolchain for various targets. # We use the rust-overlay to get the stable Rust toolchain for various targets.
# This is not exactly necessary, but it allows for compiling for various targets # This is not exactly necessary, but it allows for compiling for various targets
# with the least amount of friction. # with the least amount of friction. The extensions are to make sure all tooling
# uses the same Rust version and the general surrounding tooling.
(rust-bin.nightly.latest.default.override { (rust-bin.nightly.latest.default.override {
extensions = ["rustfmt" "rust-src" "rust-analyzer" "clippy" "rust-analyzer"]; extensions = ["rustfmt" "rust-src" "rust-analyzer" "clippy" "rust-analyzer"];
targets = ["wasm32-unknown-unknown" "wasm32-wasip1"]; # web + plugins targets = ["wasm32-unknown-unknown" "wasm32-wasip1"]; # web + plugins
}) })
# Modern, LLVM based linking pipeline # Modern, LLVM based linking pipeline. Kind of sucks on Windows, though.
llvmPackages.lld llvmPackages.lld
llvmPackages.clang llvmPackages.clang
# Handy CLI for packaging Dioxus apps and such # CLI helpers
pkgs.dioxus-cli pkgs.dioxus-cli # for packaging Dioxus apps and such
pkgs.just # general command runner for everything
# Additional Cargo Tooling # Additional Cargo Tooling
pkgs.cargo-nextest pkgs.cargo-nextest
@ -49,29 +50,9 @@ in
# Other tools # Other tools
pkgs.taplo # TOML formatter pkgs.taplo # TOML formatter
pkgs.lldb # debugger pkgs.lldb # debugger
pkgs.sccache # distributed Rust cache
] ]
++ runtimeDeps; ++ runtimeDeps;
# We could do this in the NixOS configuration via ~/.config/cargo.toml
# or something, but this is better because it lets everyone benefit
# from sccache while working with Pinakes. The reason those variables
# are not in 'env' are that we have to use Bash, which doesn't mix well
# with Nix strings.
shellHook = ''
if [ -n "$SCCACHE_BIN" ]; then
export RUSTC_WRAPPER="$SCCACHE_BIN"
export SCCACHE_DIR="''${HOME}/.cache/sccache"
export SCCACHE_CACHE_SIZE="50G"
mkdir -p "$SCCACHE_DIR"
echo "sccache setup complete!"
fi
# Start the daemon early for slightly faster startup.
"$SCCACHE_BIN" --start-server >/dev/null 2>&1 || true
'';
env = { env = {
# Allow Cargo to use lld and clang properly # Allow Cargo to use lld and clang properly
LIBCLANG_PATH = "${llvmPackages.libclang.lib}/lib"; LIBCLANG_PATH = "${llvmPackages.libclang.lib}/lib";
@ -84,8 +65,5 @@ in
# Runtime library path for GTK/WebKit/xdotool # Runtime library path for GTK/WebKit/xdotool
LD_LIBRARY_PATH = "${lib.makeLibraryPath runtimeDeps}"; LD_LIBRARY_PATH = "${lib.makeLibraryPath runtimeDeps}";
# Enable sccache for local nix develop shell
SCCACHE_BIN = "${pkgs.sccache}/bin/sccache";
}; };
} }

View file

@ -36,10 +36,10 @@ utoipa = { workspace = true }
utoipa-axum = { workspace = true } utoipa-axum = { workspace = true }
utoipa-swagger-ui = { workspace = true } utoipa-swagger-ui = { workspace = true }
[lints]
workspace = true
[dev-dependencies] [dev-dependencies]
http-body-util = "0.1.3" http-body-util = { workspace = true }
reqwest = { workspace = true } reqwest = { workspace = true }
tempfile = { workspace = true } tempfile = { workspace = true }
[lints]
workspace = true

Some files were not shown because too many files have changed in this diff Show more